• .Net 5.0 通过IdentityServer4实现单点登录之id4部分源码解析


    前文.Net 5.0 通过IdentityServer4实现单点登录之oidc认证部分源码解析介绍了oidc组件整合了相关的配置信息和从id4服务配置节点拉去了相关的配置信息和一些默认的信息,生成了OpenIdConnectMessage实例,内容如下:

    ,通过该实例生成了跳转url,内容如下:

    http://localhost:5001/connect/authorize?client_id=mvc&redirect_uri=http%3A%2F%2Flocalhost%3A5002%2Fsignin-oidc&response_type=code&scope=openid%20profile&code_challenge=Ur1nNYQMb92VuIDvgeN9mJCvQRWyspeUvEjWDToyHqg&code_challenge_method=S256&response_mode=form_post&nonce=637914152486923476.OGM4MTZlNjktODgyYi00MDk3LThmYjMtMThhZjA2Y2I1NTRmZDI1NzIxYzYtZjkzNS00YzhjLTgzODctNGQyMmJhNmRhNGM4&state=CfDJ8HpC1EPIyftOtkkyJFkl1v9AcTjtWAadkF-ERJUSWQun-BBX0VMyqB5FFwNfPPTDI8B_17mXRXOCH_G55jpkiMMjer5IV1T5Skt2nDxn8WGS_inRbRntd04agnYBGCxXyIT6cuspg0sXcOvorCManimIgsxsg5tHNSYrh8dWtdJ1FvOknWcfYhbqR5QzZ44WZKEEdxUNn-9CB6FJnulndq_5CwkqjPMux2TsnE3Wok1MsSC8kKAoHTuvBwrxd1Su_xmooEg64NJCI4_ZbB9h9lBuv9YUSraDDUzAOzPA8zqwRlYA2SCevtIcmXxaT23bQ63Zv0dJ3kCoyTsoxf5OYoaOs8JkDzXl7cqglBb21cJ7CHQMW1IXdku6bHo1-BSHuw&x-client-SKU=ID_NETSTANDARD2_0&x-client-ver=1.0.0.0

    最后调用Response.Redirect跳转到上面的url,id4的认证终结点,这里因为配置节点的相关源码比较简单,本文不做介绍.

    所以这里会进入到id4的认证终结点,这里关于id4如果跳转终结点的因为源码比较简单,这里也不做介绍.大致逻辑事通过配置访问url,跳转到对应的处理终结点.url和终结点通过id4默认配置产生.接着看下id4demo服务的StartUp文件的调用代码如下:

    // Copyright (c) Brock Allen & Dominick Baier. All rights reserved.
    // Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information.
    
    
    using IdentityServer4;
    using IdentityServerHost.Quickstart.UI;
    using Microsoft.AspNetCore.Builder;
    using Microsoft.AspNetCore.Hosting;
    using Microsoft.AspNetCore.Http;
    using Microsoft.Extensions.DependencyInjection;
    using Microsoft.Extensions.Hosting;
    using Microsoft.IdentityModel.Tokens;
    
    namespace IdentityServer
    {
        public class Startup
        {
            void CheckSameSite(HttpContext httpContext, CookieOptions options)
            {
                if (options.SameSite == SameSiteMode.None)
                {
                    var userAgent = httpContext.Request.Headers["User-Agent"].ToString();
                    if (true)
                    {
                        options.SameSite = SameSiteMode.Unspecified;
                    }
                }
            }
    
            public void ConfigureServices(IServiceCollection services)
            {
                services.AddControllersWithViews();
              
                var builder = services.AddIdentityServer()
                    .AddInMemoryIdentityResources(Config.IdentityResources)
                    .AddInMemoryApiScopes(Config.ApiScopes)
                    .AddInMemoryClients(Config.Clients)
                    .AddTestUsers(TestUsers.Users);
    
                builder.AddDeveloperSigningCredential();
    
                services.AddAuthentication()
                    .AddGoogle("Google", options =>
                    {
                        options.SignInScheme = IdentityServerConstants.ExternalCookieAuthenticationScheme;
    
                        options.ClientId = "<insert here>";
                        options.ClientSecret = "<insert here>";
                    })
                    .AddOpenIdConnect("oidc", "Demo IdentityServer", options =>
                    {
                        options.SignInScheme = IdentityServerConstants.ExternalCookieAuthenticationScheme;
                        options.SignOutScheme = IdentityServerConstants.SignoutScheme;
                        options.SaveTokens = true;
    
                        options.Authority = "https://demo.identityserver.io/";
                        options.ClientId = "interactive.confidential";
                        options.ClientSecret = "secret";
                        options.ResponseType = "code";
    
                        options.TokenValidationParameters = new TokenValidationParameters
                        {
                            NameClaimType = "name",
                            RoleClaimType = "role"
                        };
                    });
                services.Configure<CookiePolicyOptions>(options =>
                {
                    options.MinimumSameSitePolicy = SameSiteMode.Unspecified;
                    options.OnAppendCookie = cookieContext =>
                    CheckSameSite(cookieContext.Context, cookieContext.CookieOptions);
                    options.OnDeleteCookie = cookieContext =>
                    CheckSameSite(cookieContext.Context, cookieContext.CookieOptions);
                });
            }
    
            public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
            {
                if (env.IsDevelopment())
                {
                    app.UseDeveloperExceptionPage();
                }
    
                app.UseStaticFiles();
                app.UseRouting();
    
                app.UseIdentityServer();
                app.UseCookiePolicy();
                app.UseAuthorization();
    
                app.UseEndpoints(endpoints =>
                {
                    endpoints.MapDefaultControllerRoute();
                });
            }
        }
    }

    接着分析认证终结点执行逻辑,源码如下:

            public override async Task<IEndpointResult> ProcessAsync(HttpContext context)
            {
                Logger.LogDebug("Start authorize request");
    
                NameValueCollection values;
    
                if (HttpMethods.IsGet(context.Request.Method))
                {
                    values = context.Request.Query.AsNameValueCollection();
                }
                else if (HttpMethods.IsPost(context.Request.Method))
                {
                    if (!context.Request.HasApplicationFormContentType())
                    {
                        return new StatusCodeResult(HttpStatusCode.UnsupportedMediaType);
                    }
    
                    values = context.Request.Form.AsNameValueCollection();
                }
                else
                {
                    return new StatusCodeResult(HttpStatusCode.MethodNotAllowed);
                }
    
                var user = await UserSession.GetUserAsync();
                var result = await ProcessAuthorizeRequestAsync(values, user, null);
    
                Logger.LogTrace("End authorize request. result type: {0}", result?.GetType().ToString() ?? "-none-");
    
                return result;
            }

    首先通过跳转时通过get方式,所以看下内部方法(将querystring转换成键值对集合),如下:

            public static NameValueCollection AsNameValueCollection(this IEnumerable<KeyValuePair<string, StringValues>> collection)
            {
                var nv = new NameValueCollection();
    
                foreach (var field in collection)
                {
                    nv.Add(field.Key, field.Value.First());
                }
    
                return nv;
            }

    转换后的内容如下:

     看过上文应该知道这就是OpenIdConnectMessage实例的值.

    接着看认证终结点的源码:

    var user = await UserSession.GetUserAsync();

    这里尝试从用户绘画中获取httpcontext上下文的用户信息,接着解析:

            protected virtual async Task AuthenticateAsync()
            {
                if (Principal == null || Properties == null)
                {
                    var scheme = await HttpContext.GetCookieAuthenticationSchemeAsync();
    
                    var handler = await Handlers.GetHandlerAsync(HttpContext, scheme);
                    if (handler == null)
                    {
                        throw new InvalidOperationException($"No authentication handler is configured to authenticate for the scheme: {scheme}");
                    }
    
                    var result = await handler.AuthenticateAsync();
                    if (result != null && result.Succeeded)
                    {
                        Principal = result.Principal;
                        Properties = result.Properties;
                    }
                }
            }

    这里进入认证解析流程,获取默认配置的cookie认证方案.源码如下:

            internal static async Task<string> GetCookieAuthenticationSchemeAsync(this HttpContext context)
            {
                var options = context.RequestServices.GetRequiredService<IdentityServerOptions>();
                //获取配置中的认证方案,如果配置了,则采用自定义的认证方案
                if (options.Authentication.CookieAuthenticationScheme != null)
                {
                    return options.Authentication.CookieAuthenticationScheme;
                }
    
                //这里默认时名称为idsrv的Cookie认证方案
                var schemes = context.RequestServices.GetRequiredService<IAuthenticationSchemeProvider>();
                var scheme = await schemes.GetDefaultAuthenticateSchemeAsync();
                if (scheme == null)
                {
                    throw new InvalidOperationException("No DefaultAuthenticateScheme found or no CookieAuthenticationScheme configured on IdentityServerOptions.");
                }
    
                return scheme.Name;
            }

    这里获取IdentityServerOptions配置的认证方案,说明这里认证方案是可以自定义的,但是demo中并没有配置,且在StratUp类中ConfigureServices方法中配置IdentityServer4时,默认采用的就是Cookie认证方案,其认证方案名称为idsrv,源码如下:

            public static IIdentityServerBuilder AddIdentityServer(this IServiceCollection services)
            {
                var builder = services.AddIdentityServerBuilder();
    
                builder
                    .AddRequiredPlatformServices()
                    .AddCookieAuthentication()
                    .AddCoreServices()
                    .AddDefaultEndpoints()
                    .AddPluggableServices()
                    .AddValidators()
                    .AddResponseGenerators()
                    .AddDefaultSecretParsers()
                    .AddDefaultSecretValidators();
    
                // provide default in-memory implementation, not suitable for most production scenarios
                builder.AddInMemoryPersistedGrants();
    
                return builder;
            }
            public static IIdentityServerBuilder AddCookieAuthentication(this IIdentityServerBuilder builder)
            {
                builder.Services.AddAuthentication(IdentityServerConstants.DefaultCookieAuthenticationScheme)
                    .AddCookie(IdentityServerConstants.DefaultCookieAuthenticationScheme)
                    .AddCookie(IdentityServerConstants.ExternalCookieAuthenticationScheme);
    
                builder.Services.AddSingleton<IConfigureOptions<CookieAuthenticationOptions>, ConfigureInternalCookieOptions>();
                builder.Services.AddSingleton<IPostConfigureOptions<CookieAuthenticationOptions>, PostConfigureInternalCookieOptions>();
                builder.Services.AddTransientDecorator<IAuthenticationService, IdentityServerAuthenticationService>();
                builder.Services.AddTransientDecorator<IAuthenticationHandlerProvider, FederatedSignoutAuthenticationHandlerProvider>();
    
                return builder;
            }

    ok,这里返回了默认的cookie认证方案后,回到认证流程如下代码:

                   var handler = await Handlers.GetHandlerAsync(HttpContext, scheme);
                    if (handler == null)
                    {
                        throw new InvalidOperationException($"No authentication handler is configured to authenticate for the scheme: {scheme}");
                    }
    
                    var result = await handler.AuthenticateAsync();
                    if (result != null && result.Succeeded)
                    {
                        Principal = result.Principal;
                        Properties = result.Properties;
                    }

    根据认证方案名称获取认证处理器,逻辑如下:

            public async Task<IAuthenticationHandler> GetHandlerAsync(HttpContext context, string authenticationScheme)
            {
                var handler = await _provider.GetHandlerAsync(context, authenticationScheme);
                if (handler is IAuthenticationRequestHandler requestHandler)
                {
                    if (requestHandler is IAuthenticationSignInHandler signinHandler)
                    {
                        return new AuthenticationRequestSignInHandlerWrapper(signinHandler, _httpContextAccessor);
                    }
    
                    if (requestHandler is IAuthenticationSignOutHandler signoutHandler)
                    {
                        return new AuthenticationRequestSignOutHandlerWrapper(signoutHandler, _httpContextAccessor);
                    }
    
                    return new AuthenticationRequestHandlerWrapper(requestHandler, _httpContextAccessor);
                }
    
                return handler;
            }

    这里因为.CookieAuthenticationHandler处理器不是认证请求处理器,所以直接返回该处理器实例.接处理器实例的AuthenticateAsync从客户端加密的cookie中解析出用户信息写入到上下文中,应为这里是第一次调用,所以必然用户信息为空.关于cookie认证方案如果不清楚请参考https://www.cnblogs.com/GreenLeaves/p/12100568.html

     接着回到认证终结点源码如下:

                var user = await UserSession.GetUserAsync();
                var result = await ProcessAuthorizeRequestAsync(values, user, null);
    
                Logger.LogTrace("End authorize request. result type: {0}", result?.GetType().ToString() ?? "-none-");
    
                return result;

    这里user为空,原因说了,接着分析ProcessAuthorizeRequestAsync方法:

           internal async Task<IEndpointResult> ProcessAuthorizeRequestAsync(NameValueCollection parameters, ClaimsPrincipal user, ConsentResponse consent)
            {
                if (user != null)
                {
                    Logger.LogDebug("User in authorize request: {subjectId}", user.GetSubjectId());
                }
                else
                {
                    Logger.LogDebug("No user present in authorize request");
                }
    
                // validate request
                var result = await _validator.ValidateAsync(parameters, user);
                if (result.IsError)
                {
                    return await CreateErrorResultAsync(
                        "Request validation failed",
                        result.ValidatedRequest,
                        result.Error,
                        result.ErrorDescription);
                }
    
                var request = result.ValidatedRequest;
                LogRequest(request);
    
                // determine user interaction
                var interactionResult = await _interactionGenerator.ProcessInteractionAsync(request, consent);
                if (interactionResult.IsError)
                {
                    return await CreateErrorResultAsync("Interaction generator error", request, interactionResult.Error, interactionResult.ErrorDescription, false);
                }
                if (interactionResult.IsLogin)
                {
                    return new LoginPageResult(request);
                }
                if (interactionResult.IsConsent)
                {
                    return new ConsentPageResult(request);
                }
                if (interactionResult.IsRedirect)
                {
                    return new CustomRedirectResult(request, interactionResult.RedirectUrl);
                }
    
                var response = await _authorizeResponseGenerator.CreateResponseAsync(request);
    
                await RaiseResponseEventAsync(response);
    
                LogResponse(response);
    
                return new AuthorizeResult(response);
            }

    这里根据id4服务的配置和客户端传入的OpenIdConnectMessage实例值,校验传入OpenIdConnectMessage实例值是否符合要求,通过如下代码:

    var result = await _validator.ValidateAsync(parameters, user);

    开启检验,方法如下:

            public async Task<AuthorizeRequestValidationResult> ValidateAsync(NameValueCollection parameters, ClaimsPrincipal subject = null)
            {
                _logger.LogDebug("Start authorize request protocol validation");
    
                var request = new ValidatedAuthorizeRequest
                {
                    Options = _options,
                    Subject = subject ?? Principal.Anonymous,
                    Raw = parameters ?? throw new ArgumentNullException(nameof(parameters))
                };
                
                // load client_id
                // client_id must always be present on the request
                var loadClientResult = await LoadClientAsync(request);
                if (loadClientResult.IsError)
                {
                    return loadClientResult;
                }
    
                // load request object
                var roLoadResult = await LoadRequestObjectAsync(request);
                if (roLoadResult.IsError)
                {
                    return roLoadResult;
                }
    
                // validate request object
                var roValidationResult = await ValidateRequestObjectAsync(request);
                if (roValidationResult.IsError)
                {
                    return roValidationResult;
                }
    
                // validate client_id and redirect_uri
                var clientResult = await ValidateClientAsync(request);
                if (clientResult.IsError)
                {
                    return clientResult;
                }
    
                // state, response_type, response_mode
                var mandatoryResult = ValidateCoreParameters(request);
                if (mandatoryResult.IsError)
                {
                    return mandatoryResult;
                }
    
                // scope, scope restrictions and plausability
                var scopeResult = await ValidateScopeAsync(request);
                if (scopeResult.IsError)
                {
                    return scopeResult;
                }
    
                // nonce, prompt, acr_values, login_hint etc.
                var optionalResult = await ValidateOptionalParametersAsync(request);
                if (optionalResult.IsError)
                {
                    return optionalResult;
                }
    
                // custom validator
                _logger.LogDebug("Calling into custom validator: {type}", _customValidator.GetType().FullName);
                var context = new CustomAuthorizeRequestValidationContext
                {
                    Result = new AuthorizeRequestValidationResult(request)
                };
                await _customValidator.ValidateAsync(context);
    
                var customResult = context.Result;
                if (customResult.IsError)
                {
                    LogError("Error in custom validation", customResult.Error, request);
                    return Invalid(request, customResult.Error, customResult.ErrorDescription);
                }
    
                _logger.LogTrace("Authorize request protocol validation successful");
    
                return Valid(request);
            }

    这里首先生成了ValidatedAuthorizeRequest实例

    (1)、检验客户端是否有效 设置ClientId和Client信息

            private async Task<AuthorizeRequestValidationResult> LoadClientAsync(ValidatedAuthorizeRequest request)
            {
                //从请求的QuerString获取客户端id
                var clientId = request.Raw.Get(OidcConstants.AuthorizeRequest.ClientId);
    
                //clientId值长度和非空检验 默认clientId不能超过100
                if (clientId.IsMissingOrTooLong(_options.InputLengthRestrictions.ClientId))
                {
                    LogError("client_id is missing or too long", request);
                    return Invalid(request, description: "Invalid client_id");
                }
    
                request.ClientId = clientId;
    
                //判断客户端是否在仓储中是否存在 demo中采用内存仓储
                var client = await _clients.FindEnabledClientByIdAsync(request.ClientId);
                if (client == null)
                {
                    LogError("Unknown client or not enabled", request.ClientId, request);
                    return Invalid(request, OidcConstants.AuthorizeErrors.UnauthorizedClient, "Unknown client or client not enabled");
                }
    
                //设置请求的客户端信息
                request.SetClient(client);
    
                return Valid(request);
            }

    关于id4客户端的配置代码如下:

                var builder = services.AddIdentityServer()
                    .AddInMemoryIdentityResources(Config.IdentityResources)
                    .AddInMemoryApiScopes(Config.ApiScopes)
                    .AddInMemoryClients(Config.Clients)
                    .AddTestUsers(TestUsers.Users);

    通过.AddInMemoryClients(Config.Clients)配置,id4官方提供了ef core实现,当然这里可以选择重写,如Dapper.

            public static IEnumerable<Client> Clients =>
                new List<Client>
                {
                    // machine to machine client
                    new Client
                    {
                        ClientId = "client",
                        ClientSecrets = { new Secret("secret".Sha256()) },
    
                        AllowedGrantTypes = GrantTypes.ClientCredentials,
                        // scopes that client has access to
                        AllowedScopes = { "api1" }
                    },
                    
                    // interactive ASP.NET Core MVC client
                    new Client
                    {
                        ClientId = "mvc",
                        ClientSecrets = { new Secret("secret".Sha256()) },
    
                        AllowedGrantTypes = GrantTypes.Code,
                        
                        // where to redirect to after login
                        RedirectUris = { "http://localhost:5002/signin-oidc" },
    
                        // where to redirect to after logout
                        PostLogoutRedirectUris = { "http://localhost:5002/signout-callback-oidc" },
    
                        AllowedScopes = new List<string>
                        {
                            IdentityServerConstants.StandardScopes.OpenId,
                            IdentityServerConstants.StandardScopes.Profile,
                            "api1"
                        }
                    }
                };

    可以看到这里确实配置了ClientId为mvc的客户端信息.

    (2)、检验传入的redirectUri和id4客户端配置的是否一致,并设置RedirectUri,如下代码:

            private async Task<AuthorizeRequestValidationResult> ValidateClientAsync(ValidatedAuthorizeRequest request)
            {
                //////////////////////////////////////////////////////////
                // check request object requirement
                //////////////////////////////////////////////////////////
                if (request.Client.RequireRequestObject)
                {
                    if (!request.RequestObjectValues.Any())
                    {
                        return Invalid(request, description: "Client must use request object, but no request or request_uri parameter present");
                    }
                }
    
                //////////////////////////////////////////////////////////
                // redirect_uri must be present, and a valid uri
                //////////////////////////////////////////////////////////
                var redirectUri = request.Raw.Get(OidcConstants.AuthorizeRequest.RedirectUri);
    
                if (redirectUri.IsMissingOrTooLong(_options.InputLengthRestrictions.RedirectUri))
                {
                    LogError("redirect_uri is missing or too long", request);
                    return Invalid(request, description: "Invalid redirect_uri");
                }
    
                if (!Uri.TryCreate(redirectUri, UriKind.Absolute, out _))
                {
                    LogError("malformed redirect_uri", redirectUri, request);
                    return Invalid(request, description: "Invalid redirect_uri");
                }
    
                //////////////////////////////////////////////////////////
                // check if client protocol type is oidc
                //////////////////////////////////////////////////////////
                if (request.Client.ProtocolType != IdentityServerConstants.ProtocolTypes.OpenIdConnect)
                {
                    LogError("Invalid protocol type for OIDC authorize endpoint", request.Client.ProtocolType, request);
                    return Invalid(request, OidcConstants.AuthorizeErrors.UnauthorizedClient, description: "Invalid protocol");
                }
    
                //////////////////////////////////////////////////////////
                // check if redirect_uri is valid
                //////////////////////////////////////////////////////////
                if (await _uriValidator.IsRedirectUriValidAsync(redirectUri, request.Client) == false)
                {
                    LogError("Invalid redirect_uri", redirectUri, request);
                    return Invalid(request, OidcConstants.AuthorizeErrors.InvalidRequest, "Invalid redirect_uri");
                }
    
                request.RedirectUri = redirectUri;
    
                return Valid(request);
            }

     (3)、检验核心参数,代码如下:

            private AuthorizeRequestValidationResult ValidateCoreParameters(ValidatedAuthorizeRequest request)
            {
                //设置State值
                var state = request.Raw.Get(OidcConstants.AuthorizeRequest.State);
                if (state.IsPresent())
                {
                    request.State = state;
                }
    
                //检验responseType长度
                var responseType = request.Raw.Get(OidcConstants.AuthorizeRequest.ResponseType);
                if (responseType.IsMissing())
                {
                    LogError("Missing response_type", request);
                    return Invalid(request, OidcConstants.AuthorizeErrors.UnsupportedResponseType, "Missing response_type");
                }
    
                // The responseType may come in in an unconventional order.
                // Use an IEqualityComparer that doesn't care about the order of multiple values.
                // Per https://tools.ietf.org/html/rfc6749#section-3.1.1 -
                // 'Extension response types MAY contain a space-delimited (%x20) list of
                // values, where the order of values does not matter (e.g., response
                // type "a b" is the same as "b a").'
                // http://openid.net/specs/oauth-v2-multiple-response-types-1_0-03.html#terminology -
                // 'If a response type contains one of more space characters (%20), it is compared
                // as a space-delimited list of values in which the order of values does not matter.'
    
                //判断responseType是否支持
                if (!Constants.SupportedResponseTypes.Contains(responseType, _responseTypeEqualityComparer))
                {
                    LogError("Response type not supported", responseType, request);
                    return Invalid(request, OidcConstants.AuthorizeErrors.UnsupportedResponseType, "Response type not supported");
                }
    
                // Even though the responseType may have come in in an unconventional order,
                // we still need the request's ResponseType property to be set to the
                // conventional, supported response type.
    
                //判断ResponseType是否支持
                request.ResponseType = Constants.SupportedResponseTypes.First(
                    supportedResponseType => _responseTypeEqualityComparer.Equals(supportedResponseType, responseType));
    
                //////////////////////////////////////////////////////////
                // match response_type to grant type
                //////////////////////////////////////////////////////////
    
                //设置GrantType
                request.GrantType = Constants.ResponseTypeToGrantTypeMapping[request.ResponseType];
    
                // set default response mode for flow; this is needed for any client error processing below
    
                //设置ResponseMode
                request.ResponseMode = Constants.AllowedResponseModesForGrantType[request.GrantType].First();
    
                //////////////////////////////////////////////////////////
                // check if flow is allowed at authorize endpoint
                //////////////////////////////////////////////////////////
    
                //判断下GrantType是否支持
                if (!Constants.AllowedGrantTypesForAuthorizeEndpoint.Contains(request.GrantType))
                {
                    LogError("Invalid grant type", request.GrantType, request);
                    return Invalid(request, description: "Invalid response_type");
                }
    
                //////////////////////////////////////////////////////////
                // check if PKCE is required and validate parameters
                //////////////////////////////////////////////////////////
                
                //设置Pkce模式
                if (request.GrantType == GrantType.AuthorizationCode || request.GrantType == GrantType.Hybrid)
                {
                    _logger.LogDebug("Checking for PKCE parameters");
    
                    /////////////////////////////////////////////////////////////////////////////
                    // validate code_challenge and code_challenge_method
                    /////////////////////////////////////////////////////////////////////////////
                    var proofKeyResult = ValidatePkceParameters(request);
    
                    if (proofKeyResult.IsError)
                    {
                        return proofKeyResult;
                    }
                }
    
                //////////////////////////////////////////////////////////
                // check response_mode parameter and set response_mode
                //////////////////////////////////////////////////////////
    
                // check if response_mode parameter is present and valid
    
                //判断responseMode是否支持
                var responseMode = request.Raw.Get(OidcConstants.AuthorizeRequest.ResponseMode);
                if (responseMode.IsPresent())
                {
                    if (Constants.SupportedResponseModes.Contains(responseMode))
                    {
                        if (Constants.AllowedResponseModesForGrantType[request.GrantType].Contains(responseMode))
                        {
                            request.ResponseMode = responseMode;
                        }
                        else
                        {
                            LogError("Invalid response_mode for response_type", responseMode, request);
                            return Invalid(request, OidcConstants.AuthorizeErrors.InvalidRequest, description: "Invalid response_mode for response_type");
                        }
                    }
                    else
                    {
                        LogError("Unsupported response_mode", responseMode, request);
                        return Invalid(request, OidcConstants.AuthorizeErrors.UnsupportedResponseType, description: "Invalid response_mode");
                    }
                }
    
    
                //////////////////////////////////////////////////////////
                // check if grant type is allowed for client
                //////////////////////////////////////////////////////////
    
                //判断传入id4服务端配置的GrantType集合是否支持客户端传入的GrantType
                if (!request.Client.AllowedGrantTypes.Contains(request.GrantType))
                {
                    LogError("Invalid grant type for client", request.GrantType, request);
                    return Invalid(request, OidcConstants.AuthorizeErrors.UnauthorizedClient, "Invalid grant type for client");
                }
    
                //////////////////////////////////////////////////////////
                // check if response type contains an access token,
                // and if client is allowed to request access token via browser
                //////////////////////////////////////////////////////////
                
                //这里demo调用中不涉及
                var responseTypes = responseType.FromSpaceSeparatedString();
                if (responseTypes.Contains(OidcConstants.ResponseTypes.Token))
                {
                    if (!request.Client.AllowAccessTokensViaBrowser)
                    {
                        LogError("Client requested access token - but client is not configured to receive access tokens via browser", request);
                        return Invalid(request, description: "Client not configured to receive access tokens via browser");
                    }
                }
    
                return Valid(request);
            }

    这里注意下pkce模式,关于pkce模式前文中有介绍,其检验逻辑如下:

            private AuthorizeRequestValidationResult ValidatePkceParameters(ValidatedAuthorizeRequest request)
            {
                var fail = Invalid(request);
    
                //获取codeChallenge值
                var codeChallenge = request.Raw.Get(OidcConstants.AuthorizeRequest.CodeChallenge);
                if (codeChallenge.IsMissing())
                {
                    if (request.Client.RequirePkce)
                    {
                        LogError("code_challenge is missing", request);
                        fail.ErrorDescription = "code challenge required";
                    }
                    else
                    {
                        _logger.LogDebug("No PKCE used.");
                        return Valid(request);
                    }
    
                    return fail;
                }
    
                //判断codeChallenge值长度是否合法
                if (codeChallenge.Length < _options.InputLengthRestrictions.CodeChallengeMinLength ||
                    codeChallenge.Length > _options.InputLengthRestrictions.CodeChallengeMaxLength)
                {
                    LogError("code_challenge is either too short or too long", request);
                    fail.ErrorDescription = "Invalid code_challenge";
                    return fail;
                }
    
                //设置CodeChallenge值
                request.CodeChallenge = codeChallenge;
    
                //获取pkce CodeChallenge值的加密方式
                var codeChallengeMethod = request.Raw.Get(OidcConstants.AuthorizeRequest.CodeChallengeMethod);
                if (codeChallengeMethod.IsMissing())
                {
                    _logger.LogDebug("Missing code_challenge_method, defaulting to plain");
                    codeChallengeMethod = OidcConstants.CodeChallengeMethods.Plain;
                }
    
                //判断CodeChallenge加密方式是否合法  除了sha256还支持plain
                if (!Constants.SupportedCodeChallengeMethods.Contains(codeChallengeMethod))
                {
                    LogError("Unsupported code_challenge_method", codeChallengeMethod, request);
                    fail.ErrorDescription = "Transform algorithm not supported";
                    return fail;
                }
    
                // check if plain method is allowed
                if (codeChallengeMethod == OidcConstants.CodeChallengeMethods.Plain)
                {
                    if (!request.Client.AllowPlainTextPkce)
                    {
                        LogError("code_challenge_method of plain is not allowed", request);
                        fail.ErrorDescription = "Transform algorithm not supported";
                        return fail;
                    }
                }
    
                //设置CodeChallenge的加密方法
                request.CodeChallengeMethod = codeChallengeMethod;
    
                return Valid(request);
            }

    (4)、校验客户端传入scope 代码如下: 

            private async Task<AuthorizeRequestValidationResult> ValidateScopeAsync(ValidatedAuthorizeRequest request)
            {
                //////////////////////////////////////////////////////////
                // scope must be present
                //////////////////////////////////////////////////////////
                
                //scope非空检验
                var scope = request.Raw.Get(OidcConstants.AuthorizeRequest.Scope);
                if (scope.IsMissing())
                {
                    LogError("scope is missing", request);
                    return Invalid(request, description: "Invalid scope");
                }
    
                //scope长度检验
                if (scope.Length > _options.InputLengthRestrictions.Scope)
                {
                    LogError("scopes too long.", request);
                    return Invalid(request, description: "Invalid scope");
                }
    
                //写入scope信息
                request.RequestedScopes = scope.FromSpaceSeparatedString().Distinct().ToList();
    
                //如果客户端传入的scope中包含opid,那设置当前请求是openid请求,这里客户端调用oidc组件默认的设置的openid和profile
                if (request.RequestedScopes.Contains(IdentityServerConstants.StandardScopes.OpenId))
                {
                    request.IsOpenIdRequest = true;
                }
    
                //////////////////////////////////////////////////////////
                // check scope vs response_type plausability
                //////////////////////////////////////////////////////////
                var requirement = Constants.ResponseTypeToScopeRequirement[request.ResponseType];
                if (requirement == Constants.ScopeRequirement.Identity ||
                    requirement == Constants.ScopeRequirement.IdentityOnly)
                {
                    if (request.IsOpenIdRequest == false)
                    {
                        LogError("response_type requires the openid scope", request);
                        return Invalid(request, description: "Missing openid scope");
                    }
                }
    
                //////////////////////////////////////////////////////////
                // check if scopes are valid/supported and check for resource scopes
                //////////////////////////////////////////////////////////
                
                //根据传入的scope和id4服务端配置的客户端的相关资源进行校验
                var validatedResources = await _resourceValidator.ValidateRequestedResourcesAsync(new ResourceValidationRequest
                {
                    Client = request.Client,
                    Scopes = request.RequestedScopes
                });
    
                if (!validatedResources.Succeeded)
                {
                    return Invalid(request, OidcConstants.AuthorizeErrors.InvalidScope, "Invalid scope");
                }
    
                //如果id4服务端配置的客户端存在Identity Resource,那么调用客户端必须传递OpenId和profile的scope
                if (validatedResources.Resources.IdentityResources.Any() && !request.IsOpenIdRequest)
                {
                    LogError("Identity related scope requests, but no openid scope", request);
                    return Invalid(request, OidcConstants.AuthorizeErrors.InvalidScope, "Identity scopes requested, but openid scope is missing");
                }
    
                //如果scope中包含api scope,设置当前请求位apirescouce请求  
                if (validatedResources.Resources.ApiScopes.Any())
                {
                    request.IsApiResourceRequest = true;
                }
                    
                //////////////////////////////////////////////////////////
                // check id vs resource scopes and response types plausability
                //////////////////////////////////////////////////////////
                var responseTypeValidationCheck = true;
                switch (requirement)
                {
                    case Constants.ScopeRequirement.Identity:
                        if (!validatedResources.Resources.IdentityResources.Any())
                        {
                            _logger.LogError("Requests for id_token response type must include identity scopes");
                            responseTypeValidationCheck = false;
                        }
                        break;
                    case Constants.ScopeRequirement.IdentityOnly:
                        if (!validatedResources.Resources.IdentityResources.Any() || validatedResources.Resources.ApiScopes.Any())
                        {
                            _logger.LogError("Requests for id_token response type only must not include resource scopes");
                            responseTypeValidationCheck = false;
                        }
                        break;
                    case Constants.ScopeRequirement.ResourceOnly:
                        if (validatedResources.Resources.IdentityResources.Any() || !validatedResources.Resources.ApiScopes.Any())
                        {
                            _logger.LogError("Requests for token response type only must include resource scopes, but no identity scopes.");
                            responseTypeValidationCheck = false;
                        }
                        break;
                }
    
                if (!responseTypeValidationCheck)
                {
                    return Invalid(request, OidcConstants.AuthorizeErrors.InvalidScope, "Invalid scope for response type");
                }
    
                //设置通过校验的资源集合
                request.ValidatedResources = validatedResources;
    
                return Valid(request);
            }

     这里篇幅过长,分析一下几个核心的关键点

    i、scope是可以在服务端进行拦截的,源码如下:

          public ParsedScopesResult ParseScopeValues(IEnumerable<string> scopeValues)
            {
                if (scopeValues == null) throw new ArgumentNullException(nameof(scopeValues));
    
                var result = new ParsedScopesResult();
    
                foreach (var scopeValue in scopeValues)
                {
                    var ctx = new ParseScopeContext(scopeValue);
    
                    //这里可以通过重写DefaultScopeParser的ParseScopeValue方法,来实现改变客户端传入scope的值
                    ParseScopeValue(ctx);
                    
                    if (ctx.Succeeded)
                    {
                        var parsedScope = ctx.ParsedName != null ?
                            new ParsedScopeValue(ctx.RawValue, ctx.ParsedName, ctx.ParsedParameter) :
                            new ParsedScopeValue(ctx.RawValue);
    
                        result.ParsedScopes.Add(parsedScope);
                    }
                    else if (!ctx.Ignore)
                    {
                        result.Errors.Add(new ParsedScopeValidationError(scopeValue, ctx.Error));
                    }
                    else
                    {
                        _logger.LogDebug("Scope parsing ignoring scope {scope}", scopeValue);
                    }
                }
    
                return result;
            }

    ii、client和其相关资源仓储可以在id4服务端重写,其加载逻辑就是通过客户端传入的scope去对应的仓储中查找,代码如下:

            public static async Task<Resources> FindResourcesByScopeAsync(this IResourceStore store, IEnumerable<string> scopeNames)
            {
                //根据客户端传入的scope从store中查询配置的identityrescoce资源
                var identity = await store.FindIdentityResourcesByScopeNameAsync(scopeNames);
                //根据客户端传入的scope从store中查询配置的apiResources资源
                var apiResources = await store.FindApiResourcesByScopeNameAsync(scopeNames);
                //根据客户端传入的scope从store中查询配置的apiScope资源
                var scopes = await store.FindApiScopesByNameAsync(scopeNames);
    
                //校验identityrescoce资源、apiResources资源、apiScope资源
                Validate(identity, apiResources, scopes);
    
                
                var resources = new Resources(identity, apiResources, scopes)
                {
                    //这个scope('offline_access')用于生成refreshtoken
                    OfflineAccess = scopeNames.Contains(IdentityServerConstants.StandardScopes.OfflineAccess)
                };
    
                return resources;
            }

    资源的核心校验逻辑如下:

    private static void Validate(IEnumerable<IdentityResource> identity, IEnumerable<ApiResource> apiResources, IEnumerable<ApiScope> apiScopes)
            {
                // attempt to detect invalid configuration. this is about the only place
                // we can do this, since it's hard to get the values in the store.
    
                //判断identity scope是否存在重复
                var identityScopeNames = identity.Select(x => x.Name).ToArray();
                var dups = GetDuplicates(identityScopeNames);
                if (dups.Any())
                {
                    var names = dups.Aggregate((x, y) => x + ", " + y);
                    throw new Exception(
                        $"Duplicate identity scopes found. This is an invalid configuration. Use different names for identity scopes. Scopes found: {names}");
                }
    
                //判断api rescouce是否存在重复
                var apiNames = apiResources.Select(x => x.Name);
                dups = GetDuplicates(apiNames);
                if (dups.Any())
                {
                    var names = dups.Aggregate((x, y) => x + ", " + y);
                    throw new Exception(
                        $"Duplicate api resources found. This is an invalid configuration. Use different names for API resources. Names found: {names}");
                }
                
                //判断api scope是否存在重复的
                var scopesNames = apiScopes.Select(x => x.Name);
                dups = GetDuplicates(scopesNames);
                if (dups.Any())
                {
                    var names = dups.Aggregate((x, y) => x + ", " + y);
                    throw new Exception(
                        $"Duplicate scopes found. This is an invalid configuration. Use different names for scopes. Names found: {names}");
                }
    
                //判断identity相关的scope和api相关的scope是否有交集
                var overlap = identityScopeNames.Intersect(scopesNames).ToArray();
                if (overlap.Any())
                {
                    var names = overlap.Aggregate((x, y) => x + ", " + y);
                    throw new Exception(
                        $"Found identity scopes and API scopes that use the same names. This is an invalid configuration. Use different names for identity scopes and API scopes. Scopes found: {names}");
                }
            }

    iii、在从仓储中读取到相关资源后,会将相关值写入到ResourceValidationResult实例,后续的操作将不会在查询仓储,而是总内存判断

    v、根据客户端传入的scope判断id4服务端配置客户端是否支持,如果支持写入ResourceValidationResult实例,不支持写入ResourceValidationResult实例的InvalidScopes,源码如下:

            protected virtual async Task ValidateScopeAsync(
                Client client, 
                Resources resourcesFromStore, 
                ParsedScopeValue requestedScope, 
                ResourceValidationResult result)
            {
                //refreshtoken scope特殊处理
                if (requestedScope.ParsedName == IdentityServerConstants.StandardScopes.OfflineAccess)
                {
                    if (await IsClientAllowedOfflineAccessAsync(client))
                    {
                        result.Resources.OfflineAccess = true;
                        result.ParsedScopes.Add(new ParsedScopeValue(IdentityServerConstants.StandardScopes.OfflineAccess));
                    }
                    else
                    {
                        result.InvalidScopes.Add(IdentityServerConstants.StandardScopes.OfflineAccess);
                    }
                }
                else
                {
                    //根据客户端传入的scope从resources实例中的identityrescoce资源
                    var identity = resourcesFromStore.FindIdentityResourcesByScope(requestedScope.ParsedName);
                    if (identity != null)
                    {
                        //判断id4服务端配置的客户端是否配置了identityrescoce资源
                        if (await IsClientAllowedIdentityResourceAsync(client, identity))
                        {
                            result.ParsedScopes.Add(requestedScope);
                            result.Resources.IdentityResources.Add(identity);
                        }
                        else
                        {
                            result.InvalidScopes.Add(requestedScope.RawValue);
                        }
                    }
                    else
                    {
                        //根据客户端传入的scope从resources实例中的identityrescoce资源
                        var apiScope = resourcesFromStore.FindApiScope(requestedScope.ParsedName);
                        if (apiScope != null)
                        {
                            //判断id4服务端配置的客户端是否配置了ApiScope资源
                            if (await IsClientAllowedApiScopeAsync(client, apiScope))
                            {
                                result.ParsedScopes.Add(requestedScope);
                                result.Resources.ApiScopes.Add(apiScope);
    
                                //根据客户端传入的scope从resources实例中的是否存在关联的scope资源,如果apiresource配置了向result写入ApiResources
                                var apis = resourcesFromStore.FindApiResourcesByScope(apiScope.Name);
                                foreach (var api in apis)
                                {
                                    result.Resources.ApiResources.Add(api);
                                }
                            }
                            else
                            {
                                result.InvalidScopes.Add(requestedScope.RawValue);
                            }
                        }
                        else
                        {
                            _logger.LogError("Scope {scope} not found in store.", requestedScope.ParsedName);
                            result.InvalidScopes.Add(requestedScope.RawValue);
                        }
                    }
                }
            }

    (5)、校验可选参数

    这里暂时不做分析

    (6)、构造了CustomAuthorizeRequestValidationContext实例传入通过ValidatedAuthorizeRequest实例,给外部自定义修改,代码如下:

                var context = new CustomAuthorizeRequestValidationContext
                {
                    Result = new AuthorizeRequestValidationResult(request)
                };
                await _customValidator.ValidateAsync(context);

    这里就可以实现ICustomAuthorizeRequestValidator来重写ValidateAsync方法,来实现定制化.

    (7)、返回最终的ValidatedAuthorizeRequest实例

    ok,在经过上述一系列操作之后,获取到了一个最终的ValidatedAuthorizeRequest实例,接着执行如下代码:

                var interactionResult = await _interactionGenerator.ProcessInteractionAsync(request, consent);
                if (interactionResult.IsError)
                {
                    return await CreateErrorResultAsync("Interaction generator error", request, interactionResult.Error, interactionResult.ErrorDescription, false);
                }
                if (interactionResult.IsLogin)
                {
                    return new LoginPageResult(request);
                }
                if (interactionResult.IsConsent)
                {
                    return new ConsentPageResult(request);
                }
                if (interactionResult.IsRedirect)
                {
                    return new CustomRedirectResult(request, interactionResult.RedirectUrl);
                }
    
                var response = await _authorizeResponseGenerator.CreateResponseAsync(request);
    
                await RaiseResponseEventAsync(response);
    
                LogResponse(response);
    
                return new AuthorizeResult(response);

    很明显,到这里开始处理客户端 的交互了,看一下IAuthorizeInteractionResponseGenerator的默认实现的ProcessInteractionAsync方法,代码如下:

            public virtual async Task<InteractionResponse> ProcessInteractionAsync(ValidatedAuthorizeRequest request, ConsentResponse consent = null)
            {
                Logger.LogTrace("ProcessInteractionAsync");
    
                //处理需要用户点击授权相关 demo第一次登录调用不会触发
                if (consent != null && consent.Granted == false && consent.Error.HasValue && request.Subject.IsAuthenticated() == false)
                {
                    // special case when anonymous user has issued an error prior to authenticating
                    Logger.LogInformation("Error: User consent result: {error}", consent.Error);
    
                    var error = consent.Error switch
                    {
                        AuthorizationError.AccountSelectionRequired => OidcConstants.AuthorizeErrors.AccountSelectionRequired,
                        AuthorizationError.ConsentRequired => OidcConstants.AuthorizeErrors.ConsentRequired,
                        AuthorizationError.InteractionRequired => OidcConstants.AuthorizeErrors.InteractionRequired,
                        AuthorizationError.LoginRequired => OidcConstants.AuthorizeErrors.LoginRequired,
                        _ => OidcConstants.AuthorizeErrors.AccessDenied
                    };
                    
                    return new InteractionResponse
                    {
                        Error = error,
                        ErrorDescription = consent.ErrorDescription
                    };
                }
    
                //处理登录
                var result = await ProcessLoginAsync(request);
                
                if (!result.IsLogin && !result.IsError && !result.IsRedirect)
                {
                    result = await ProcessConsentAsync(request, consent);
                }
    
                //如果判断到结果是需要登录或者需要用户授权或者需要跳转 且请求的prompt设置为none的话(代表不需要展示客户端ui)  报错
                if ((result.IsLogin || result.IsConsent || result.IsRedirect) && request.PromptModes.Contains(OidcConstants.PromptModes.None))
                {
                    // prompt=none means do not show the UI
                    Logger.LogInformation("Changing response to LoginRequired: prompt=none was requested");
                    result = new InteractionResponse
                    {
                        Error = result.IsLogin ? OidcConstants.AuthorizeErrors.LoginRequired :
                                    result.IsConsent ? OidcConstants.AuthorizeErrors.ConsentRequired : 
                                        OidcConstants.AuthorizeErrors.InteractionRequired
                    };
                }
    
                return result;
            }

    下面是决定交互行为的逻辑,代码如下:

            protected internal virtual async Task<InteractionResponse> ProcessLoginAsync(ValidatedAuthorizeRequest request)
            {
                if (request.PromptModes.Contains(OidcConstants.PromptModes.Login) ||
                    request.PromptModes.Contains(OidcConstants.PromptModes.SelectAccount))
                {
                    Logger.LogInformation("Showing login: request contains prompt={0}", request.PromptModes.ToSpaceSeparatedString());
    
                    // remove prompt so when we redirect back in from login page
                    // we won't think we need to force a prompt again
                    request.RemovePrompt();
                    
                    return new InteractionResponse { IsLogin = true };
                }
    
                // unauthenticated user
                //判断是否处于认证成功之后的状态
                var isAuthenticated = request.Subject.IsAuthenticated();
                
                // user de-activated
                bool isActive = false;
    
                //如果当前用户已经认证成功
                if (isAuthenticated)
                {
                    //判断当前用户是否处于活跃状态
                    var isActiveCtx = new IsActiveContext(request.Subject, request.Client, IdentityServerConstants.ProfileIsActiveCallers.AuthorizeEndpoint);
                    await Profile.IsActiveAsync(isActiveCtx);
                    
                    isActive = isActiveCtx.IsActive;
                }
    
                //如果不是认证成功状态或者不处于活跃状态
                if (!isAuthenticated || !isActive)
                {
                    if (!isAuthenticated)
                    {
                        Logger.LogInformation("Showing login: User is not authenticated");
                    }
                    else if (!isActive)
                    {
                        Logger.LogInformation("Showing login: User is not active");
                    }
    
                    //返回交互返回值,需要登陆了
                    return new InteractionResponse { IsLogin = true };
                }
    
                // check current idp
                var currentIdp = request.Subject.GetIdentityProvider();
    
                // check if idp login hint matches current provider
                var idp = request.GetIdP();
                if (idp.IsPresent())
                {
                    if (idp != currentIdp)
                    {
                        Logger.LogInformation("Showing login: Current IdP ({currentIdp}) is not the requested IdP ({idp})", currentIdp, idp);
                        return new InteractionResponse { IsLogin = true };
                    }
                }
    
                // check authentication freshness
                if (request.MaxAge.HasValue)
                {
                    var authTime = request.Subject.GetAuthenticationTime();
                    if (Clock.UtcNow > authTime.AddSeconds(request.MaxAge.Value))
                    {
                        Logger.LogInformation("Showing login: Requested MaxAge exceeded.");
    
                        return new InteractionResponse { IsLogin = true };
                    }
                }
    
                // check local idp restrictions
                if (currentIdp == IdentityServerConstants.LocalIdentityProvider)
                {
                    if (!request.Client.EnableLocalLogin)
                    {
                        Logger.LogInformation("Showing login: User logged in locally, but client does not allow local logins");
                        return new InteractionResponse { IsLogin = true };
                    }
                }
                // check external idp restrictions if user not using local idp
                else if (request.Client.IdentityProviderRestrictions != null && 
                    request.Client.IdentityProviderRestrictions.Any() &&
                    !request.Client.IdentityProviderRestrictions.Contains(currentIdp))
                {
                    Logger.LogInformation("Showing login: User is logged in with idp: {idp}, but idp not in client restriction list.", currentIdp);
                    return new InteractionResponse { IsLogin = true };
                }
    
                // check client's user SSO timeout
                if (request.Client.UserSsoLifetime.HasValue)
                {
                    var authTimeEpoch = request.Subject.GetAuthenticationTimeEpoch();
                    var nowEpoch = Clock.UtcNow.ToUnixTimeSeconds();
    
                    var diff = nowEpoch - authTimeEpoch;
                    if (diff > request.Client.UserSsoLifetime.Value)
                    {
                        Logger.LogInformation("Showing login: User's auth session duration: {sessionDuration} exceeds client's user SSO lifetime: {userSsoLifetime}.", diff, request.Client.UserSsoLifetime);
                        return new InteractionResponse { IsLogin = true };
                    }
                }
    
                return new InteractionResponse();
            }

    第一次调用,必然走这个返回值,代码如下:

               //如果不是认证成功状态或者不处于活跃状态
                if (!isAuthenticated || !isActive)
                {
                    if (!isAuthenticated)
                    {
                        Logger.LogInformation("Showing login: User is not authenticated");
                    }
                    else if (!isActive)
                    {
                        Logger.LogInformation("Showing login: User is not active");
                    }
    
                    //返回交互返回值,需要登陆了
                    return new InteractionResponse { IsLogin = true };
                }

    最后的结果返回值如下:

     接着执行如下代码:

                if (interactionResult.IsLogin)
                {
                    return new LoginPageResult(request);
                }
                if (interactionResult.IsConsent)
                {
                    return new ConsentPageResult(request);
                }
                if (interactionResult.IsRedirect)
                {
                    return new CustomRedirectResult(request, interactionResult.RedirectUrl);
                }
    
                var response = await _authorizeResponseGenerator.CreateResponseAsync(request);
    
                await RaiseResponseEventAsync(response);
    
                LogResponse(response);
    
                return new AuthorizeResult(response);

    返回LoginPageResult,id4的设计是url通过特定的终结点处理,然后,执行终结点返回值的ExecuteAsync方法,下面解析下源码,如下:

            public async Task ExecuteAsync(HttpContext context)
            {
                Init(context);
    
                //从上下文的Items属性中获取key为idsvr:IdentityServerBasePath的value 作为id4服务的根路径  加上 connect/authorize/callback 作为returnurl的值(不包含querystring)
                var returnUrl = context.GetIdentityServerBasePath().EnsureTrailingSlash() + Constants.ProtocolRoutePaths.AuthorizeCallback;
                if (_authorizationParametersMessageStore != null)
                {
                    var msg = new Message<IDictionary<string, string[]>>(_request.Raw.ToFullDictionary());
                    var id = await _authorizationParametersMessageStore.WriteAsync(msg);
                    returnUrl = returnUrl.AddQueryString(Constants.AuthorizationParamsStore.MessageStoreIdParameterName, id);
                }
                else
                {
                    //并且returanurl的querystring 值就是客户端oidc组件根据配置参数和id4服务拉取的配置参数生成OpenIdConnectMessage实例值
                    returnUrl = returnUrl.AddQueryString(_request.Raw.ToQueryString());
                }
    
                //获取id4服务配置的登录url
                var loginUrl = _options.UserInteraction.LoginUrl;
                //存在不是本地url的情况,访问其他认证server的情况
                if (!loginUrl.IsLocalUrl())
                {
                    // this converts the relative redirect path to an absolute one if we're 
                    // redirecting to a different server
                    returnUrl = context.GetIdentityServerHost().EnsureTrailingSlash() + returnUrl.RemoveLeadingSlash();
                }
    
                //Account/Login?ReturnUrl=id4服务的根路径/connect/authorize/callback?客户端oidc组件根据配置参数和id4服务拉取的配置参数生成OpenIdConnectMessage实例值组成的querystring
                var url = loginUrl.AddQueryString(_options.UserInteraction.LoginReturnUrlParameter, returnUrl);
                
                //跳转到id4服务的登录页
                context.Response.RedirectToAbsoluteUrl(url);
            }

     ok,到这里就带着生成ReturnUrl跳转去了id4服务的登录功能.

    接着解析下下id4服务的登录控制器试图如下步骤:

    i、第一步校验ReturnUrl,源码如下:

     public bool IsValidReturnUrl(string returnUrl)
            {
                if (returnUrl.IsLocalUrl())
                {
                    var index = returnUrl.IndexOf('?');
                    if (index >= 0)
                    {
                        returnUrl = returnUrl.Substring(0, index);
                    }
    
                    if (returnUrl.EndsWith(Constants.ProtocolRoutePaths.Authorize, StringComparison.Ordinal) ||
                        returnUrl.EndsWith(Constants.ProtocolRoutePaths.AuthorizeCallback, StringComparison.Ordinal))
                    {
                        _logger.LogTrace("returnUrl is valid");
                        return true;
                    }
                }
    
                _logger.LogTrace("returnUrl is not valid");
                return false;
            }

    这里首先校验下是不是本地url,接着校验url的主体部分是不是connect/authorize或者connect/authorize/callback oidc的returnurl只能是二者

    ii、接着还是从url中解析出客户端传递的OpenIdConnectMessage实例值

                    var parameters = returnUrl.ReadQueryStringAsNameValueCollection();
                    if (_authorizationParametersMessageStore != null)
                    {
                        var messageStoreId = parameters[Constants.AuthorizationParamsStore.MessageStoreIdParameterName];
                        var entry = await _authorizationParametersMessageStore.ReadAsync(messageStoreId);
                        parameters = entry?.Data.FromFullDictionary() ?? new NameValueCollection();
                    }

    这里注意_authorizationParametersMessageStore,校验,这段代码说明客户端OpenIdConnectMessage实例值是可以写入向redis这种的缓存的,而客户端只需要传递缓存之后,向OpenIdConnectMessage实例值的authzId从而服务端根据这个authzId值从redis中获取对应的oidc的参数值.

    iii、还是校验下用户有没有登录和检验参数的过程和上面一样,代码如下:

    //根据id4默认配置的认证的cookie认证方案中获取用户信息
                    var user = await _userSession.GetUserAsync();
                    var result = await _validator.ValidateAsync(parameters, user);
                    if (!result.IsError)
                    {
                        _logger.LogTrace("AuthorizationRequest being returned");
                        return new AuthorizationRequest(result.ValidatedRequest);
                    }

    应为到这里没有触发过登录方法,所以这里依然为空.

    demo中其他的一些比如google登录这里暂时不说了

    ok,登录页面渲染完成了.

    接着看登录逻辑.

            [HttpPost]
            [ValidateAntiForgeryToken]
            public async Task<IActionResult> Login(LoginInputModel model, string button)
            {
                //校验渲染登录视图时设置到页面隐藏域中的ReturnUrl值和渲染登录试图的检验逻辑一样
                var context = await _interaction.GetAuthorizationContextAsync(model.ReturnUrl);if (ModelState.IsValid)
                {
                    //校验用户名密码
                    if (_users.ValidateCredentials(model.Username, model.Password))
                    {
                        var user = _users.FindByUsername(model.Username);
                        await _events.RaiseAsync(new UserLoginSuccessEvent(user.Username, user.SubjectId, user.Username, clientId: context?.Client.ClientId));
    
                        AuthenticationProperties props = null;
    
                        //认证成功,设置了记住登录
                        if (AccountOptions.AllowRememberLogin && model.RememberLogin)
                        {
                            //持久化登录信息
                            props = new AuthenticationProperties
                            {
                                IsPersistent = true,
                                ExpiresUtc = DateTimeOffset.UtcNow.Add(AccountOptions.RememberMeLoginDuration)
                            };
                        };
    
                        // 生成登录用户
                        var isuser = new IdentityServerUser(user.SubjectId)
                        {
                            DisplayName = user.Username
                        };
    
                        //登录
                        await HttpContext.SignInAsync(isuser, props);
    
                        if (context != null)
                        {
                            //如果是本地客户端(id4和客户端集成到一起的情况)  检验条件是判断RedirectUri前缀是否已http或者https开头
                            if (context.IsNativeClient())
                            {
                                // The client is native, so this change in how to
                                // return the response is for better UX for the end user.
                                return this.LoadingPage("Redirect", model.ReturnUrl);
                            }
    
                            //重定向到/connect/authorize/callback的url
                            return Redirect(model.ReturnUrl);
                        }
                    }
    
                    await _events.RaiseAsync(new UserLoginFailureEvent(model.Username, "invalid credentials", clientId:context?.Client.ClientId));
                    ModelState.AddModelError(string.Empty, AccountOptions.InvalidCredentialsErrorMessage);
                }
    
                // something went wrong, show form with error
                var vm = await BuildLoginViewModelAsync(model);
                return View(vm);
            }

    第一步校验ReturnUrl值,本质还是校验客户端OpenIdConnectMessage实例值

    第二步校验登录名密码,验证通过,写入cookie,通过如下代码:

    await HttpContext.SignInAsync(isuser, props);

    生成Principal的源码如下:

            public ClaimsPrincipal CreatePrincipal()
            {
                //向claims写入用户id
                if (SubjectId.IsMissing()) throw new ArgumentException("SubjectId is mandatory", nameof(SubjectId));
                var claims = new List<Claim> { new Claim(JwtClaimTypes.Subject, SubjectId) };
    
                //向claims写入用户名
                if (DisplayName.IsPresent())
                {
                    claims.Add(new Claim(JwtClaimTypes.Name, DisplayName));
                }
    
                //向claims写入身份提供商,如果id4服务和identity部署在一台服务器上,那么默认时local
                if (IdentityProvider.IsPresent())
                {
                    claims.Add(new Claim(JwtClaimTypes.IdentityProvider, IdentityProvider));
                }
    
                //向claims写入认证通过的时间
                if (AuthenticationTime.HasValue)
                {
                    claims.Add(new Claim(JwtClaimTypes.AuthenticationTime, new DateTimeOffset(AuthenticationTime.Value).ToUnixTimeSeconds().ToString()));
                }
    
                //向claims写入认证方式
                if (AuthenticationMethods.Any())
                {
                    foreach (var amr in AuthenticationMethods)
                    {
                        claims.Add(new Claim(JwtClaimTypes.AuthenticationMethod, amr));
                    }
                }
    
                claims.AddRange(AdditionalClaims);
    
                var id = new ClaimsIdentity(claims.Distinct(new ClaimComparer()), Constants.IdentityServerAuthenticationType, JwtClaimTypes.Name, JwtClaimTypes.Role);
                return new ClaimsPrincipal(id);
            }

    demo中只写入了用户id和用户名,这里还要注意的时,在引入id4服务的默认实现后,前文说过,id4服务端通过巧妙的di版本的装饰者对cookie认证做了重写.源码如下:

            internal static void AddDecorator<TService>(this IServiceCollection services)
            {
                //找到service中默认的传入TService的实现
                var registration = services.LastOrDefault(x => x.ServiceType == typeof(TService));
                if (registration == null)
                {
                    throw new InvalidOperationException("Service type: " + typeof(TService).Name + " not registered.");
                }
                if (services.Any(x => x.ServiceType == typeof(Decorator<TService>)))
                {
                    throw new InvalidOperationException("Decorator already registered for type: " + typeof(TService).Name + ".");
                }
    
                //移除原有的
                services.Remove(registration);
    
                if (registration.ImplementationInstance != null)
                {
                    var type = registration.ImplementationInstance.GetType();
                    var innerType = typeof(Decorator<,>).MakeGenericType(typeof(TService), type);
                    services.Add(new ServiceDescriptor(typeof(Decorator<TService>), innerType, ServiceLifetime.Transient));
                    services.Add(new ServiceDescriptor(type, registration.ImplementationInstance));
                }
                else if (registration.ImplementationFactory != null)
                {
                    services.Add(new ServiceDescriptor(typeof(Decorator<TService>), provider =>
                    {
                        return new DisposableDecorator<TService>((TService)registration.ImplementationFactory(provider));
                    }, registration.Lifetime));
                }
                else
                {
                    //通过Decorator<TService,TService的原有实现类型> 注册为Decorator<TService> 写入DI中
                    var type = registration.ImplementationType;
                    var innerType = typeof(Decorator<,>).MakeGenericType(typeof(TService), registration.ImplementationType);
                    services.Add(new ServiceDescriptor(typeof(Decorator<TService>), innerType, ServiceLifetime.Transient));
    
                    //写入默认实现,要不然Decorator<,>构造器拿不到实现
                    services.Add(new ServiceDescriptor(type, type, registration.Lifetime));
                }
            }

    添加了一些默认Claim,如下:

            private void AugmentMissingClaims(ClaimsPrincipal principal, DateTime authTime)
            {
                var identity = principal.Identities.First();
    
                // ASP.NET Identity issues this claim type and uses the authentication middleware name
                // such as "Google" for the value. this code is trying to correct/convert that for
                // our scenario. IOW, we take their old AuthenticationMethod value of "Google"
                // and issue it as the idp claim. we then also issue a amr with "external"
                var amr = identity.FindFirst(ClaimTypes.AuthenticationMethod);
                if (amr != null &&
                    identity.FindFirst(JwtClaimTypes.IdentityProvider) == null &&
                    identity.FindFirst(JwtClaimTypes.AuthenticationMethod) == null)
                {
                    _logger.LogDebug("Removing amr claim with value: {value}", amr.Value);
                    identity.RemoveClaim(amr);
    
                    _logger.LogDebug("Adding idp claim with value: {value}", amr.Value);
                    identity.AddClaim(new Claim(JwtClaimTypes.IdentityProvider, amr.Value));
    
                    _logger.LogDebug("Adding amr claim with value: {value}", Constants.ExternalAuthenticationMethod);
                    identity.AddClaim(new Claim(JwtClaimTypes.AuthenticationMethod, Constants.ExternalAuthenticationMethod));
                }
    
                //如果在认证端没有写入IdentityProvider(idp)身份提供端,id4服务默认向Cliam添加idp为local的值 代表本地提供身份
                if (identity.FindFirst(JwtClaimTypes.IdentityProvider) == null)
                {
                    _logger.LogDebug("Adding idp claim with value: {value}", IdentityServerConstants.LocalIdentityProvider);
                    identity.AddClaim(new Claim(JwtClaimTypes.IdentityProvider, IdentityServerConstants.LocalIdentityProvider));
                }
    
                //如果在认证端没有写入AuthenticationMethod(amr)身份认证方式,id4服务默认向Cliam添加amr为local的值 代表本地提供身份认证
                if (identity.FindFirst(JwtClaimTypes.AuthenticationMethod) == null)
                {
                    if (identity.FindFirst(JwtClaimTypes.IdentityProvider).Value == IdentityServerConstants.LocalIdentityProvider)
                    {
                        _logger.LogDebug("Adding amr claim with value: {value}", OidcConstants.AuthenticationMethods.Password);
                        identity.AddClaim(new Claim(JwtClaimTypes.AuthenticationMethod, OidcConstants.AuthenticationMethods.Password));
                    }
                    else
                    {
                        _logger.LogDebug("Adding amr claim with value: {value}", Constants.ExternalAuthenticationMethod);
                        identity.AddClaim(new Claim(JwtClaimTypes.AuthenticationMethod, Constants.ExternalAuthenticationMethod));
                    }
                }
    
                //如果在认证端没有写入AuthenticationTime(auth_time)身份认证通过时间,id4服务默认向Cliam添加auth_time为当前时间戳的值
                if (identity.FindFirst(JwtClaimTypes.AuthenticationTime) == null)
                {
                    var time = new DateTimeOffset(authTime).ToUnixTimeSeconds().ToString();
    
                    _logger.LogDebug("Adding auth_time claim with value: {value}", time);
                    identity.AddClaim(new Claim(JwtClaimTypes.AuthenticationTime, time, ClaimValueTypes.Integer64));
                }
            }

    claim详情如下:

    写入cookie源码,如不明白请参考.Net Core 3.0认证组件之Cookie认证组件解析源码如下:

    protected async override Task HandleSignInAsync(ClaimsPrincipal user, AuthenticationProperties? properties)
            {
                if (user == null)
                {
                    throw new ArgumentNullException(nameof(user));
                }
    
                properties = properties ?? new AuthenticationProperties();
    
                _signInCalled = true;
    
                // Process the request cookie to initialize members like _sessionKey.
                await EnsureCookieTicket();
                var cookieOptions = BuildCookieOptions();
    
                var signInContext = new CookieSigningInContext(
                    Context,
                    Scheme,
                    Options,
                    user,
                    properties,
                    cookieOptions);
    
                DateTimeOffset issuedUtc;
                if (signInContext.Properties.IssuedUtc.HasValue)
                {
                    issuedUtc = signInContext.Properties.IssuedUtc.Value;
                }
                else
                {
                    issuedUtc = Clock.UtcNow;
                    signInContext.Properties.IssuedUtc = issuedUtc;
                }
    
                if (!signInContext.Properties.ExpiresUtc.HasValue)
                {
                    signInContext.Properties.ExpiresUtc = issuedUtc.Add(Options.ExpireTimeSpan);
                }
    
                await Events.SigningIn(signInContext);
    
                if (signInContext.Properties.IsPersistent)
                {
                    var expiresUtc = signInContext.Properties.ExpiresUtc ?? issuedUtc.Add(Options.ExpireTimeSpan);
                    signInContext.CookieOptions.Expires = expiresUtc.ToUniversalTime();
                }
    
                var ticket = new AuthenticationTicket(signInContext.Principal!, signInContext.Properties, signInContext.Scheme.Name);
    
                if (Options.SessionStore != null)
                {
                    if (_sessionKey != null)
                    {
                        // Renew the ticket in cases of multiple requests see: https://github.com/dotnet/aspnetcore/issues/22135
                        await Options.SessionStore.RenewAsync(_sessionKey, ticket);
                    }
                    else
                    {
                        _sessionKey = await Options.SessionStore.StoreAsync(ticket);
                    }
    
                    var principal = new ClaimsPrincipal(
                        new ClaimsIdentity(
                            new[] { new Claim(SessionIdClaim, _sessionKey, ClaimValueTypes.String, Options.ClaimsIssuer) },
                            Options.ClaimsIssuer));
                    ticket = new AuthenticationTicket(principal, null, Scheme.Name);
                }
    
                var cookieValue = Options.TicketDataFormat.Protect(ticket, GetTlsTokenBinding());
    
                Options.CookieManager.AppendResponseCookie(
                    Context,
                    Options.Cookie.Name!,
                    cookieValue,
                    signInContext.CookieOptions);
    
                var signedInContext = new CookieSignedInContext(
                    Context,
                    Scheme,
                    signInContext.Principal!,
                    signInContext.Properties,
                    Options);
    
                await Events.SignedIn(signedInContext);
    
                // Only redirect on the login path
                var shouldRedirect = Options.LoginPath.HasValue && OriginalPath == Options.LoginPath;
                await ApplyHeaders(shouldRedirect, signedInContext.Properties);
    
                Logger.AuthenticationSchemeSignedIn(Scheme.Name);
            }

    这里RemeberMe的逻辑如下:

                if (signInContext.Properties.IsPersistent)
                {
                    var expiresUtc = signInContext.Properties.ExpiresUtc ?? issuedUtc.Add(Options.ExpireTimeSpan);
                    signInContext.CookieOptions.Expires = expiresUtc.ToUniversalTime();
                }

    第三步 在id4端写入cookie之后,重定向到id4 /connect/authorize/callback终结点,并带着客户端OpenIdConnectMessage实例值转换成的querystring.

    ok,接着分析/connect/authorize/callback终结点源码如下:

            public override async Task<IEndpointResult> ProcessAsync(HttpContext context)
            {
                if (!HttpMethods.IsGet(context.Request.Method))
                {
                    Logger.LogWarning("Invalid HTTP method for authorize endpoint.");
                    return new StatusCodeResult(HttpStatusCode.MethodNotAllowed);
                }
    
                Logger.LogDebug("Start authorize callback request");
    
                //从url中解析出parameters(客户端OpenIdConnectMessage实例值)
                var parameters = context.Request.Query.AsNameValueCollection();
                //客户端OpenIdConnectMessage实例值是可以被写入到如redis等缓存中的(客户端只需要传一个authId,缓存的key)
                if (_authorizationParametersMessageStore != null)
                {
                    //从缓存中读取OpenIdConnectMessage实例值
                    var messageStoreId = parameters[Constants.AuthorizationParamsStore.MessageStoreIdParameterName];
                    var entry = await _authorizationParametersMessageStore.ReadAsync(messageStoreId);
                    parameters = entry?.Data.FromFullDictionary() ?? new NameValueCollection();
    
                    await _authorizationParametersMessageStore.DeleteAsync(messageStoreId);
                }
    
                //从cookie中读取用户信息
                var user = await UserSession.GetUserAsync();
    
                //授权相关demo中不涉及后续介绍 这里consent为null
                var consentRequest = new ConsentRequest(parameters, user?.GetSubjectId());
                var consent = await _consentResponseStore.ReadAsync(consentRequest.Id);
    
                if (consent != null && consent.Data == null)
                {
                    return await CreateErrorResultAsync("consent message is missing data");
                }
    
                try
                {
                    //处理授权请求
                    var result = await ProcessAuthorizeRequestAsync(parameters, user, consent?.Data);
    
                    Logger.LogTrace("End Authorize Request. Result type: {0}", result?.GetType().ToString() ?? "-none-");
    
                    return result;
                }
                finally
                {
                    if (consent != null)
                    {
                        await _consentResponseStore.DeleteAsync(consentRequest.Id);
                    }
                }
            }

    ok,源码中做了相关注释,这里开始解析授权请求相关源码

    第一步:校验参数 应为有大量源码前面解析过,所以说下大致流程

    1、检验客户端信息是否合法

    2、检验客户端传递的redirect_uri是否合法(常规校验和判断id4的客户端设置的redirect_uri和客户端本身的是否一直)

    3、设置State值、检验responseType长度、判断responseType是否支持、判断ResponseType是否支持、设置GrantType、设置ResponseMode、判断下GrantType是否支持、设置Pkce模式相关参数、判断responseMode是否支持、判断传入id4服务端配置的GrantType集合是否支持客户端传入的GrantType

    4、校验scope,并设置

    5、校验可选参数和之前不一样的是,应为当前流程用户已认证成功,会设置SessionId,源码如下:

                if (_options.Endpoints.EnableCheckSessionEndpoint)
                {
                    if (request.Subject.IsAuthenticated())
                    {
                        var sessionId = await _userSession.GetSessionIdAsync();
                        if (sessionId.IsPresent())
                        {
                            request.SessionId = sessionId;
                        }
                        else
                        {
                            LogError("Check session endpoint enabled, but SessionId is missing", request);
                        }
                    }
                    else
                    {
                        //当前用户如果没有认证成功,清空sessionid
                        request.SessionId = ""; // empty string for anonymous users
                    }
                }

    第二步:处理接下去该走什么流程了,这里和上面的区别在于,当前用户已经认证成功,所以会触发判断用户是否处于活跃状态的流程,源码如下:

                //判断是否处于认证成功之后的状态
                var isAuthenticated = request.Subject.IsAuthenticated();
                
                // user de-activated
                bool isActive = false;
    
                //如果当前用户已经认证成功
                if (isAuthenticated)
                {
                    //判断当前用户是否处于活跃状态
                    var isActiveCtx = new IsActiveContext(request.Subject, request.Client, IdentityServerConstants.ProfileIsActiveCallers.AuthorizeEndpoint);
                    await Profile.IsActiveAsync(isActiveCtx);
                    
                    isActive = isActiveCtx.IsActive;
                }

    默认实现是返回true,如果不自定义,那么只要登录成功且没有过期的用户,一直处于活跃状态.客户端也可以配置sso的生命周期源码如下:

          if (request.Client.UserSsoLifetime.HasValue)
                {
                    var authTimeEpoch = request.Subject.GetAuthenticationTimeEpoch();
                    var nowEpoch = Clock.UtcNow.ToUnixTimeSeconds();
    
                    var diff = nowEpoch - authTimeEpoch;
                    if (diff > request.Client.UserSsoLifetime.Value)
                    {
                        Logger.LogInformation("Showing login: User's auth session duration: {sessionDuration} exceeds client's user SSO lifetime: {userSsoLifetime}.", diff, request.Client.UserSsoLifetime);
                        return new InteractionResponse { IsLogin = true };
                    }
                }

    第三步:生成code,源码如下:

            public virtual async Task<AuthorizeResponse> CreateResponseAsync(ValidatedAuthorizeRequest request)
            {
                if (request.GrantType == GrantType.AuthorizationCode)
                {
                    return await CreateCodeFlowResponseAsync(request);
                }
                if (request.GrantType == GrantType.Implicit)
                {
                    return await CreateImplicitFlowResponseAsync(request);
                }
                if (request.GrantType == GrantType.Hybrid)
                {
                    return await CreateHybridFlowResponseAsync(request);
                }
    
                Logger.LogError("Unsupported grant type: " + request.GrantType);
                throw new InvalidOperationException("invalid grant type: " + request.GrantType);
            }

     demo中采用的是AuthorizationCode模式,分析下源码

    1、创建code,源码如下:

    protected virtual async Task<AuthorizationCode> CreateCodeAsync(ValidatedAuthorizeRequest request)
            {
                string stateHash = null;
                //加密state,通过id4服务端默认加密服务(公私钥加密)
                if (request.State.IsPresent())
                {
                    var credential = await KeyMaterialService.GetSigningCredentialsAsync(request.Client.AllowedIdentityTokenSigningAlgorithms);
                    if (credential == null)
                    {
                        throw new InvalidOperationException("No signing credential is configured.");
                    }
    
                    var algorithm = credential.Algorithm;
                    stateHash = CryptoHelper.CreateHashClaimValue(request.State, algorithm);
                }
    
                var code = new AuthorizationCode
                {
                    //code创建时间
                    CreationTime = Clock.UtcNow.UtcDateTime,
                    //客户端id
                    ClientId = request.Client.ClientId,
                    //生命周期
                    Lifetime = request.Client.AuthorizationCodeLifetime,
                    //当前认证通过用户的ClaimsPrincipal
                    Subject = request.Subject,
                    //当前认证通过用户的会话id
                    SessionId = request.SessionId,
                    Description = request.Description,
                    //再次加密已经在客户端加密过的CodeChallenge,安全考虑,到时候客户端会校验值
                    CodeChallenge = request.CodeChallenge.Sha256(),
                    //加密方式
                    CodeChallengeMethod = request.CodeChallengeMethod,
                    //是否openid请求
                    IsOpenId = request.IsOpenIdRequest,
                    //请求的scope集合
                    RequestedScopes = request.ValidatedResources.RawScopeValues,
                    //跳转到客户端的url
                    RedirectUri = request.RedirectUri,
                    //nonce值
                    Nonce = request.Nonce,
                    //StateHash值 安全相关
                    StateHash = stateHash,
                    //是否设置了用户同意相关
                    WasConsentShown = request.WasConsentShown
                };
    
                return code;
            }

    code内容如下:

    2、存储code值,通过IPersistedGrantStore,默认实现是存储到ConcurrentDictionary中

    3、将code(这里的code并不是1中的core,而是PersistedGrant实例的Key属性)写入response实例

         Logger.LogDebug("Creating Authorization Code Flow response.");
    
                var code = await CreateCodeAsync(request);
                var id = await AuthorizationCodeStore.StoreAuthorizationCodeAsync(code);
    
                var response = new AuthorizeResponse
                {
                    Request = request,
                    Code = id,
                    SessionState = request.GenerateSessionStateValue()
                };
    
                return response;

    4、将response实例写入AuthorizeResult实例

    return new AuthorizeResult(response);

    第三步:执行授权AuthorizeResult实例的ExecuteAsync方法

    1、添加客户端id到cookie中,源码如下:

            public virtual async Task AddClientIdAsync(string clientId)
            {
                if (clientId == null) throw new ArgumentNullException(nameof(clientId));
    
                //再次进行cookie认证填充认证Properties
                await AuthenticateAsync();
                if (Properties != null)
                {
                    var clientIds = Properties.GetClientList();
                    if (!clientIds.Contains(clientId))
                    {
                        //向认证Properties写入客户端id
                        Properties.AddClientId(clientId);
                        //更新cookie
                        await UpdateSessionCookie();
                    }
                }
            }

    2、根据相关参数生成一段html

    <html><head><meta http-equiv='X-UA-Compatible' content='IE=edge' /><base target='_self'/></head><body><form method='post' action='http://localhost:5002/signin-oidc'><input type='hidden' name='code' value='A2E7A4E75DB0663DECA93937D6376FB735F249F5D3C939EF85681E5FC07D2424' />
    <input type='hidden' name='scope' value='openid profile api1' />
    <input type='hidden' name='state' value='CfDJ8HpC1EPIyftOtkkyJFkl1v-gnmUzTayM1rqhzWpMkDLveWVyWrcB2_PjZ5Koy-flZE2XXi34wnhhJYHq0T8WAx6XcNDgupbLE-94ej9vb5oxoOcb53hiMhEDxX8BBy94S5qkK6vSy2LiAmZc0NnsXirmq0QebaDdON43Nex7L1mZHUCeVEj26cNXRd-nrePfrj1JRVXxrBHjGfK0E--wQas_lsXT-3X0xhFJ5zvmJAx9T-Hf70PxEdwKMnhQzCjUhFYd0_rXVUFXoCNGbQblp8XXdmQX-2oXMVZxEwDWyv0sDp4GTYiF5JzDh3a9QtAKnWtFtLlGA1gu8w7Nx6vew_icH6nAZlUbd9sxQpC3ZZPbQ2ZkwK0OXca88IMVXzyi2A' />
    <input type='hidden' name='session_state' value='9f6KAPCgCNnb8aDm4tV4IbVhBAIXEFCrf5Z7mGa4rkY.359378DC06BECE3035CC1313C7B7F83D' />
    <noscript><button>Click to continue</button></noscript></form><script>window.addEventListener('load', function(){document.forms[0].submit();});</script></body></html>

    Response输出这段html时,会触发表单的自动提交,提交的地址是http://localhost:5002/signin-oidc

    表单参数:code、scope、state、session_state

    到这里又回去了客户端.

  • 相关阅读:
    动态网页技术--JSP(5)
    动态网页技术--JSP(4)
    动态网页技术--JSP(3)
    动态网页技术--JSP(2)
    动态网页技术--JSP(1)
    动态网页技术--Servlet
    TomCat服务器搭建
    06_多线程
    05_进程间通信 IPC
    04_进程池
  • 原文地址:https://www.cnblogs.com/GreenLeaves/p/16402191.html
Copyright © 2020-2023  润新知