zoukankan      html  css  js  c++  java
  • IdentityServer 4 学习笔记

    一、简介 


         IdentityServer 4 是一个基于ASP.NET Core免费开源的 OpenID Connect 和 OAuth 2.0 框架。IdentityServer 4 整合了在应用程序中集成基于令牌的身份验证、单点登录和 API 访问控制所需的所有协议实现和扩展点。IdentityServer 4由 OpenID基金会正式认证,因此符合规范且可互操作。它是.NET基金会的一部分,根据他们的行为准则运作。它根据Apache 2(OSI批准的许可证)获得许可。

         说白了,IdentityServer 4 其实就是ASP.NET Core的一个中间件,可以无缝的接入 ASP.NET Core 处理管道中。

         那什么是ASP.NET Core 中间件?ASP.NET Core的处理流程是一个管道,而中间件是装配到管道中的用于处理请求和响应的组件。中间件按照装配的先后顺序执行,并决定是否进入下一个组件。中间件管道的处理流程如下图(图片来源于官网)

         IdentityServer 4 源码地址 : https://github.com/IdentityServer/IdentityServer4   ,我下载的版本是 4.0.4

    二、处理过程


      

    整个处理过程分为启动初始化和监听响应请求两个部分:

    1、如图左侧为Asp.net core 创建WebHost通过Startup类注册 IdentityServer服务、使用 IdentityServer中间件。

    中间件配置类

    路径 :  ConfigurationIdentityServerApplicationBuilderExtensions.cs

    public static class IdentityServerApplicationBuilderExtensions
    {
        public static IApplicationBuilder UseIdentityServer(this IApplicationBuilder app, IdentityServerMiddlewareOptions options = null)
        {
               app.Validate();
    
                app.UseMiddleware<BaseUrlMiddleware>();
    
                app.ConfigureCors();
    
                // it seems ok if we have UseAuthentication more than once in the pipeline --
                // this will just re-run the various callback handlers and the default authN 
                // handler, which just re-assigns the user on the context. claims transformation
                // will run twice, since that's not cached (whereas the authN handler result is)
                // related: https://github.com/aspnet/Security/issues/1399
                if (options == null) options = new IdentityServerMiddlewareOptions();
                options.AuthenticationMiddleware(app);
    
                app.UseMiddleware<MutualTlsEndpointMiddleware>();
                app.UseMiddleware<IdentityServerMiddleware>();
    
                return app;
        }
    }
    View Code

    2、右侧为WebHost进行请求监听

    下面以Client采用 用户名密码模式(GrantTypes.ResourceOwnerPassword)请求Token (地址 http://localhost:5000/connect/token)为例进行Post请求

    (1)将请求传入 IdentityServerMiddleware ,中间件会注入节点IEndpointRouter(默认EndpointRouter类)通过执行 Invoke 的 router.Find(context)  根据 context.Request.Path 查找对应的IEndpointHandler终结点处理类。

    中间件实现类:

    路径:HostingIdentityServerMiddleware.cs
    public class IdentityServerMiddleware
    {
        private readonly RequestDelegate _next;
        private readonly ILogger _logger;
    
        /// <summary>
        /// Initializes a new instance of the <see cref="IdentityServerMiddleware"/> class.
        /// </summary>
        /// <param name="next">The next.</param>
        /// <param name="logger">The logger.</param>
        public IdentityServerMiddleware(RequestDelegate next, ILogger<IdentityServerMiddleware> logger)
        {
            _next = next;
            _logger = logger;
        }
    
        /// <summary>
        /// Invokes the middleware. 调用中间件。
        /// </summary>
        /// <param name="context">The context.</param>
        /// <param name="router">The router.</param>
        /// <param name="session">The user session.</param>
        /// <param name="events">The event service.</param>
        /// <param name="backChannelLogoutService"></param>
        /// <returns></returns>
        public async Task Invoke(HttpContext context, IEndpointRouter router, IUserSession session, IEventService events, IBackChannelLogoutService backChannelLogoutService)
        {
            // this will check the authentication session and from it emit the check session
            // cookie needed from JS-based signout clients.
            // 这将检查身份验证会话,并从中发出基于js的签出客户端所需的检查会话cookie。
            await session.EnsureSessionIdCookieAsync();
    
            context.Response.OnStarting(async () =>
            {
                if (context.GetSignOutCalled())
                {
                    _logger.LogDebug("SignOutCalled set; processing post-signout session cleanup.");
    
                    // this clears our session id cookie so JS clients can detect the user has signed out
                    // 这将清除我们的会话id cookie,以便JS客户端可以检测用户已经退出
                    await session.RemoveSessionIdCookieAsync();
    
                    // back channel logout  回发通道注销
                    var logoutContext = await session.GetLogoutNotificationContext();
                    if (logoutContext != null)
                    {
                        await backChannelLogoutService.SendLogoutNotificationsAsync(logoutContext);
                    }
                }
            });
    
            try
            {
                var endpoint = router.Find(context);
                if (endpoint != null)
                {
                    _logger.LogInformation("Invoking IdentityServer endpoint: {endpointType} for {url}", endpoint.GetType().FullName, context.Request.Path.ToString());
    
                    var result = await endpoint.ProcessAsync(context);
    
                    if (result != null)
                    {
                        _logger.LogTrace("Invoking result: {type}", result.GetType().FullName);
                        await result.ExecuteAsync(context);
                    }
    
                    return;
                }
            }
            catch (Exception ex)
            {
                await events.RaiseAsync(new UnhandledExceptionEvent(ex));
                _logger.LogCritical(ex, "Unhandled exception: {exception}", ex.Message);
                throw;
            }
    
            await _next(context);
        }
    }
    View Code

    节点路由类:

    路径:HostingEndpointRouter.cs
    internal class EndpointRouter : IEndpointRouter
    {
        private readonly IEnumerable<Endpoint> _endpoints;
        private readonly IdentityServerOptions _options;
        private readonly ILogger _logger;
    
        public EndpointRouter(IEnumerable<Endpoint> endpoints, IdentityServerOptions options, ILogger<EndpointRouter> logger)
        {
            _endpoints = endpoints;
            _options = options;
            _logger = logger;
        }
    
        public IEndpointHandler Find(HttpContext context)
        {
            if (context == null) throw new ArgumentNullException(nameof(context));
    
            foreach(var endpoint in _endpoints)
            {
                var path = endpoint.Path;
                if (context.Request.Path.Equals(path, StringComparison.OrdinalIgnoreCase))
                {
                    var endpointName = endpoint.Name;
                    _logger.LogDebug("Request path {path} matched to endpoint type {endpoint}", context.Request.Path, endpointName);
    
                    return GetEndpointHandler(endpoint, context);
                }
            }
    
            _logger.LogTrace("No endpoint entry found for request path: {path}", context.Request.Path);
    
            return null;
        }
    
        private IEndpointHandler GetEndpointHandler(Endpoint endpoint, HttpContext context)
        {
            if (_options.Endpoints.IsEndpointEnabled(endpoint))
            {
                if (context.RequestServices.GetService(endpoint.Handler) is IEndpointHandler handler)
                {
                    _logger.LogDebug("Endpoint enabled: {endpoint}, successfully created handler: {endpointHandler}", endpoint.Name, endpoint.Handler.FullName);
                    return handler;
                }
    
                _logger.LogDebug("Endpoint enabled: {endpoint}, failed to create handler: {endpointHandler}", endpoint.Name, endpoint.Handler.FullName);
            }
            else
            {
                _logger.LogWarning("Endpoint disabled: {endpoint}", endpoint.Name);
            }
    
            return null;
        }
    }
    View Code
    (2)根据 地址  http://localhost:5000/connect/token 请求会找到,TokenEndpoint 这个类,这个类会注入ITokenRequestValidator(默认TokenRequestValidator)、ITokenResponseGenerator(默认TokenResponseGenerator )、IEndpointResult(默认TokenResult )等处理类,这个终结点实例会返回给中间件,由中间件调用
    var result = await endpoint.ProcessAsync(context);
    

      endpoint.ProcessAsync 会依次执行validate client、validate request、create response、return result,最终调用:

    await result.ExecuteAsync(context);
    

      返回相应信息。

    终结点处理程序:

    路径:EndpointsTokenEndpoint.cs
     
    internal class TokenEndpoint : IEndpointHandler
    {
        private readonly IClientSecretValidator _clientValidator;
        private readonly ITokenRequestValidator _requestValidator;
        private readonly ITokenResponseGenerator _responseGenerator;
        private readonly IEventService _events;
        private readonly ILogger _logger;
    
        /// <summary>
        /// Initializes a new instance of the <see cref="TokenEndpoint" /> class.
        /// </summary>
        /// <param name="clientValidator">The client validator.</param>
        /// <param name="requestValidator">The request validator.</param>
        /// <param name="responseGenerator">The response generator.</param>
        /// <param name="events">The events.</param>
        /// <param name="logger">The logger.</param>
        public TokenEndpoint(
            IClientSecretValidator clientValidator, 
            ITokenRequestValidator requestValidator, 
            ITokenResponseGenerator responseGenerator, 
            IEventService events, 
            ILogger<TokenEndpoint> logger)
        {
            _clientValidator = clientValidator;
            _requestValidator = requestValidator;
            _responseGenerator = responseGenerator;
            _events = events;
            _logger = logger;
        }
    
        /// <summary>
        /// Processes the request.
        /// </summary>
        /// <param name="context">The HTTP context.</param>
        /// <returns></returns>
        public async Task<IEndpointResult> ProcessAsync(HttpContext context)
        {
            _logger.LogTrace("Processing token request.");
    
            // validate HTTP
            if (!HttpMethods.IsPost(context.Request.Method) || !context.Request.HasApplicationFormContentType())
            {
                _logger.LogWarning("Invalid HTTP request for token endpoint");
                return Error(OidcConstants.TokenErrors.InvalidRequest);
            }
    
            return await ProcessTokenRequestAsync(context);
        }
    
        private async Task<IEndpointResult> ProcessTokenRequestAsync(HttpContext context)
        {
            _logger.LogDebug("Start token request.");
    
            // validate client
            var clientResult = await _clientValidator.ValidateAsync(context);
    
            if (clientResult.Client == null)
            {
                return Error(OidcConstants.TokenErrors.InvalidClient);
            }
    
            // validate request
            var form = (await context.Request.ReadFormAsync()).AsNameValueCollection();
            _logger.LogTrace("Calling into token request validator: {type}", _requestValidator.GetType().FullName);
            var requestResult = await _requestValidator.ValidateRequestAsync(form, clientResult);  //默认调用TokenRequestValidator类的ValidateRequestAsync
    
            if (requestResult.IsError)
            {
                await _events.RaiseAsync(new TokenIssuedFailureEvent(requestResult));
                return Error(requestResult.Error, requestResult.ErrorDescription, requestResult.CustomResponse);
            }
    
            // create response
            _logger.LogTrace("Calling into token request response generator: {type}", _responseGenerator.GetType().FullName);
            var response = await _responseGenerator.ProcessAsync(requestResult);
    
            await _events.RaiseAsync(new TokenIssuedSuccessEvent(response, requestResult));
            LogTokens(response, requestResult);
    
            // return result
            _logger.LogDebug("Token request success.");
            return new TokenResult(response);
        }
    
        private TokenErrorResult Error(string error, string errorDescription = null, Dictionary<string, object> custom = null)
        {
            var response = new TokenErrorResponse
            {
                Error = error,
                ErrorDescription = errorDescription,
                Custom = custom
            };
    
            return new TokenErrorResult(response);
        }
    
        private void LogTokens(TokenResponse response, TokenRequestValidationResult requestResult)
        {
            var clientId = $"{requestResult.ValidatedRequest.Client.ClientId} ({requestResult.ValidatedRequest.Client?.ClientName ?? "no name set"})";
            var subjectId = requestResult.ValidatedRequest.Subject?.GetSubjectId() ?? "no subject";
    
            if (response.IdentityToken != null)
            {
                _logger.LogTrace("Identity token issued for {clientId} / {subjectId}: {token}", clientId, subjectId, response.IdentityToken);
            }
            if (response.RefreshToken != null)
            {
                _logger.LogTrace("Refresh token issued for {clientId} / {subjectId}: {token}", clientId, subjectId, response.RefreshToken);
            }
            if (response.AccessToken != null)
            {
                _logger.LogTrace("Access token issued for {clientId} / {subjectId}: {token}", clientId, subjectId, response.AccessToken);
            }
        }
    }
    View Code

    验证处理类:

    路径:ValidationDefaultTokenRequestValidator.cs
     
    internal class TokenRequestValidator : ITokenRequestValidator
    {
        ...
        private readonly IResourceOwnerPasswordValidator _resourceOwnerValidator;
        ...
    
        public TokenRequestValidator(IdentityServerOptions options, 
            IAuthorizationCodeStore authorizationCodeStore, 
            IResourceOwnerPasswordValidator resourceOwnerValidator, 
            IProfileService profile, 
            IDeviceCodeValidator deviceCodeValidator, 
            ExtensionGrantValidator extensionGrantValidator, 
            ICustomTokenRequestValidator customRequestValidator,
            IResourceValidator resourceValidator,
            IResourceStore resourceStore,
            ITokenValidator tokenValidator, 
            IRefreshTokenService refreshTokenService,
            IEventService events, 
            ISystemClock clock, 
            ILogger<TokenRequestValidator> logger)
        {
                ...
            _resourceOwnerValidator = resourceOwnerValidator;
                ...
        }
    
        private async Task<TokenRequestValidationResult> ValidateResourceOwnerCredentialRequestAsync(NameValueCollection parameters)
        {
            _logger.LogDebug("Start resource owner password token request validation");
    
            /////////////////////////////////////////////
            // check if client is authorized for grant type
            /////////////////////////////////////////////
            if (!_validatedRequest.Client.AllowedGrantTypes.Contains(GrantType.ResourceOwnerPassword))
            {
                LogError("Client not authorized for resource owner flow, check the AllowedGrantTypes setting", new { client_id = _validatedRequest.Client.ClientId });
                return Invalid(OidcConstants.TokenErrors.UnauthorizedClient);
            }
    
            /////////////////////////////////////////////
            // check if client is allowed to request scopes
            /////////////////////////////////////////////
            if (!(await ValidateRequestedScopesAsync(parameters)))
            {
                return Invalid(OidcConstants.TokenErrors.InvalidScope);
            }
    
            /////////////////////////////////////////////
            // check resource owner credentials
            /////////////////////////////////////////////
            var userName = parameters.Get(OidcConstants.TokenRequest.UserName);
            var password = parameters.Get(OidcConstants.TokenRequest.Password);
    
            if (userName.IsMissing())
            {
                LogError("Username is missing");
                return Invalid(OidcConstants.TokenErrors.InvalidGrant);
            }
    
            if (password.IsMissing())
            {
                password = "";
            }
    
            if (userName.Length > _options.InputLengthRestrictions.UserName ||
                password.Length > _options.InputLengthRestrictions.Password)
            {
                LogError("Username or password too long");
                return Invalid(OidcConstants.TokenErrors.InvalidGrant);
            }
    
            _validatedRequest.UserName = userName;
    
    
            /////////////////////////////////////////////
            // authenticate user
            /////////////////////////////////////////////
            var resourceOwnerContext = new ResourceOwnerPasswordValidationContext
            {
                UserName = userName,
                Password = password,
                Request = _validatedRequest
            };
            await _resourceOwnerValidator.ValidateAsync(resourceOwnerContext);
    
            if (resourceOwnerContext.Result.IsError)
            {
                // protect against bad validator implementations
                resourceOwnerContext.Result.Error ??= OidcConstants.TokenErrors.InvalidGrant;
    
                if (resourceOwnerContext.Result.Error == OidcConstants.TokenErrors.UnsupportedGrantType)
                {
                    LogError("Resource owner password credential grant type not supported");
                    await RaiseFailedResourceOwnerAuthenticationEventAsync(userName, "password grant type not supported", resourceOwnerContext.Request.Client.ClientId);
    
                    return Invalid(OidcConstants.TokenErrors.UnsupportedGrantType, customResponse: resourceOwnerContext.Result.CustomResponse);
                }
    
                var errorDescription = "invalid_username_or_password";
    
                if (resourceOwnerContext.Result.ErrorDescription.IsPresent())
                {
                    errorDescription = resourceOwnerContext.Result.ErrorDescription;
                }
    
                LogInformation("User authentication failed: ", errorDescription ?? resourceOwnerContext.Result.Error);
                await RaiseFailedResourceOwnerAuthenticationEventAsync(userName, errorDescription, resourceOwnerContext.Request.Client.ClientId);
    
                return Invalid(resourceOwnerContext.Result.Error, errorDescription, resourceOwnerContext.Result.CustomResponse);
            }
    
            if (resourceOwnerContext.Result.Subject == null)
            {
                var error = "User authentication failed: no principal returned";
                LogError(error);
                await RaiseFailedResourceOwnerAuthenticationEventAsync(userName, error, resourceOwnerContext.Request.Client.ClientId);
    
                return Invalid(OidcConstants.TokenErrors.InvalidGrant);
            }
    
            /////////////////////////////////////////////
            // make sure user is enabled
            /////////////////////////////////////////////
            var isActiveCtx = new IsActiveContext(resourceOwnerContext.Result.Subject, _validatedRequest.Client, IdentityServerConstants.ProfileIsActiveCallers.ResourceOwnerValidation);
            await _profile.IsActiveAsync(isActiveCtx);
    
            if (isActiveCtx.IsActive == false)
            {
                LogError("User has been disabled", new { subjectId = resourceOwnerContext.Result.Subject.GetSubjectId() });
                await RaiseFailedResourceOwnerAuthenticationEventAsync(userName, "user is inactive", resourceOwnerContext.Request.Client.ClientId);
    
                return Invalid(OidcConstants.TokenErrors.InvalidGrant);
            }
    
            _validatedRequest.UserName = userName;
            _validatedRequest.Subject = resourceOwnerContext.Result.Subject;
    
            await RaiseSuccessfulResourceOwnerAuthenticationEventAsync(userName, resourceOwnerContext.Result.Subject.GetSubjectId(), resourceOwnerContext.Request.Client.ClientId);
            _logger.LogDebug("Resource owner password token request validation success.");
            return Valid(resourceOwnerContext.Result.CustomResponse);
        }
    }
    View Code

     响应处理类:

    路径:ResponseHandlingDefaultTokenResponseGenerator.cs
     
    public class TokenResponseGenerator : ITokenResponseGenerator
    {
        /// <summary>
        /// The logger
        /// </summary>
        protected readonly ILogger Logger;
    
        /// <summary>
        /// The token service
        /// </summary>
        protected readonly ITokenService TokenService;
    
        /// <summary>
        /// The refresh token service
        /// </summary>
        protected readonly IRefreshTokenService RefreshTokenService;
    
        /// <summary>
        /// The scope parser
        /// </summary>
        public IScopeParser ScopeParser { get; }
    
        /// <summary>
        /// The resource store
        /// </summary>
        protected readonly IResourceStore Resources;
    
        /// <summary>
        /// The clients store
        /// </summary>
        protected readonly IClientStore Clients;
    
        /// <summary>
        ///  The clock
        /// </summary>
        protected readonly ISystemClock Clock;
    
        /// <summary>
        /// Initializes a new instance of the <see cref="TokenResponseGenerator" /> class.
        /// </summary>
        /// <param name="clock">The clock.</param>
        /// <param name="tokenService">The token service.</param>
        /// <param name="refreshTokenService">The refresh token service.</param>
        /// <param name="scopeParser">The scope parser.</param>
        /// <param name="resources">The resources.</param>
        /// <param name="clients">The clients.</param>
        /// <param name="logger">The logger.</param>
        public TokenResponseGenerator(ISystemClock clock, ITokenService tokenService, IRefreshTokenService refreshTokenService, IScopeParser scopeParser, IResourceStore resources, IClientStore clients, ILogger<TokenResponseGenerator> logger)
        {
            Clock = clock;
            TokenService = tokenService;
            RefreshTokenService = refreshTokenService;
            ScopeParser = scopeParser;
            Resources = resources;
            Clients = clients;
            Logger = logger;
        }
    
        /// <summary>
        /// Processes the response.
        /// </summary>
        /// <param name="request">The request.</param>
        /// <returns></returns>
        public virtual async Task<TokenResponse> ProcessAsync(TokenRequestValidationResult request)
        {
            switch (request.ValidatedRequest.GrantType)
            {
                case OidcConstants.GrantTypes.ClientCredentials:
                    return await ProcessClientCredentialsRequestAsync(request);
                case OidcConstants.GrantTypes.Password:
                    return await ProcessPasswordRequestAsync(request);
                case OidcConstants.GrantTypes.AuthorizationCode:
                    return await ProcessAuthorizationCodeRequestAsync(request);
                case OidcConstants.GrantTypes.RefreshToken:
                    return await ProcessRefreshTokenRequestAsync(request);
                case OidcConstants.GrantTypes.DeviceCode:
                    return await ProcessDeviceCodeRequestAsync(request);
                default:
                    return await ProcessExtensionGrantRequestAsync(request);
            }
        }
    
        /// <summary>
        /// Creates the response for an client credentials request.
        /// 为客户端凭证请求创建响应。
        /// </summary>
        /// <param name="request">The request.</param>
        /// <returns></returns>
        protected virtual Task<TokenResponse> ProcessClientCredentialsRequestAsync(TokenRequestValidationResult request)
        {
            Logger.LogTrace("Creating response for client credentials request");
    
            return ProcessTokenRequestAsync(request);
        }
    
        /// <summary>
        /// Creates the response for a password request.
        /// 为密码请求创建响应。
        /// </summary>
        /// <param name="request">The request.</param>
        /// <returns></returns>
        protected virtual Task<TokenResponse> ProcessPasswordRequestAsync(TokenRequestValidationResult request)
        {
            Logger.LogTrace("Creating response for password request");
    
            return ProcessTokenRequestAsync(request);
        }
    
        /// <summary>
        /// Creates the response for an authorization code request.
        /// 为授权代码请求创建响应。
        /// </summary>
        /// <param name="request">The request.</param>
        /// <returns></returns>
        /// <exception cref="System.InvalidOperationException">Client does not exist anymore.</exception>
        protected virtual async Task<TokenResponse> ProcessAuthorizationCodeRequestAsync(TokenRequestValidationResult request)
        {
            Logger.LogTrace("Creating response for authorization code request");
    
            //////////////////////////
            // access token
            /////////////////////////
            var (accessToken, refreshToken) = await CreateAccessTokenAsync(request.ValidatedRequest);
            var response = new TokenResponse
            {
                AccessToken = accessToken,
                AccessTokenLifetime = request.ValidatedRequest.AccessTokenLifetime,
                Custom = request.CustomResponse,
                Scope = request.ValidatedRequest.AuthorizationCode.RequestedScopes.ToSpaceSeparatedString()
            };
    
            //////////////////////////
            // refresh token
            /////////////////////////
            if (refreshToken.IsPresent())
            {
                response.RefreshToken = refreshToken;
            }
    
            //////////////////////////
            // id token
            /////////////////////////
            if (request.ValidatedRequest.AuthorizationCode.IsOpenId)
            {
                // load the client that belongs to the authorization code
                Client client = null;
                if (request.ValidatedRequest.AuthorizationCode.ClientId != null)
                {
                    client = await Clients.FindEnabledClientByIdAsync(request.ValidatedRequest.AuthorizationCode.ClientId);
                }
                if (client == null)
                {
                    throw new InvalidOperationException("Client does not exist anymore.");
                }
    
                var parsedScopesResult = ScopeParser.ParseScopeValues(request.ValidatedRequest.AuthorizationCode.RequestedScopes);
                var validatedResources = await Resources.CreateResourceValidationResult(parsedScopesResult);
    
                var tokenRequest = new TokenCreationRequest
                {
                    Subject = request.ValidatedRequest.AuthorizationCode.Subject,
                    ValidatedResources = validatedResources,
                    Nonce = request.ValidatedRequest.AuthorizationCode.Nonce,
                    AccessTokenToHash = response.AccessToken,
                    StateHash = request.ValidatedRequest.AuthorizationCode.StateHash,
                    ValidatedRequest = request.ValidatedRequest
                };
    
                var idToken = await TokenService.CreateIdentityTokenAsync(tokenRequest);
                var jwt = await TokenService.CreateSecurityTokenAsync(idToken);
                response.IdentityToken = jwt;
            }
    
            return response;
        }
    
        /// <summary>
        /// Creates the response for a refresh token request.
        /// 为刷新令牌请求创建响应。
        /// </summary>
        /// <param name="request">The request.</param>
        /// <returns></returns>
        protected virtual async Task<TokenResponse> ProcessRefreshTokenRequestAsync(TokenRequestValidationResult request)
        {
            Logger.LogTrace("Creating response for refresh token request");
    
            var oldAccessToken = request.ValidatedRequest.RefreshToken.AccessToken;
            string accessTokenString;
    
            if (request.ValidatedRequest.Client.UpdateAccessTokenClaimsOnRefresh)
            {
                var subject = request.ValidatedRequest.RefreshToken.Subject;
    
                // todo: do we want to just parse here and build up validated result
                // or do we want to fully re-run validation here.
                var parsedScopesResult = ScopeParser.ParseScopeValues(oldAccessToken.Scopes);
                var validatedResources = await Resources.CreateResourceValidationResult(parsedScopesResult);
    
                var creationRequest = new TokenCreationRequest
                {
                    Subject = subject,
                    Description = request.ValidatedRequest.RefreshToken.Description,
                    ValidatedRequest = request.ValidatedRequest,
                    ValidatedResources = validatedResources
                };
    
                var newAccessToken = await TokenService.CreateAccessTokenAsync(creationRequest);
                accessTokenString = await TokenService.CreateSecurityTokenAsync(newAccessToken);
            }
            else
            {
                oldAccessToken.CreationTime = Clock.UtcNow.UtcDateTime;
                oldAccessToken.Lifetime = request.ValidatedRequest.AccessTokenLifetime;
    
                accessTokenString = await TokenService.CreateSecurityTokenAsync(oldAccessToken);
            }
    
            var handle = await RefreshTokenService.UpdateRefreshTokenAsync(request.ValidatedRequest.RefreshTokenHandle, request.ValidatedRequest.RefreshToken, request.ValidatedRequest.Client);
    
            return new TokenResponse
            {
                IdentityToken = await CreateIdTokenFromRefreshTokenRequestAsync(request.ValidatedRequest, accessTokenString),
                AccessToken = accessTokenString,
                AccessTokenLifetime = request.ValidatedRequest.AccessTokenLifetime,
                RefreshToken = handle,
                Custom = request.CustomResponse,
                Scope = request.ValidatedRequest.RefreshToken.Scopes.ToSpaceSeparatedString()
            };
        }
    
        /// <summary>
        /// Processes the response for device code grant request.
        /// 处理设备代码授予请求的响应。
        /// </summary>
        /// <param name="request">The request.</param>
        /// <returns></returns>
        protected virtual async Task<TokenResponse> ProcessDeviceCodeRequestAsync(TokenRequestValidationResult request)
        {
            Logger.LogTrace("Creating response for device code request");
    
            //////////////////////////
            // access token
            /////////////////////////
            var (accessToken, refreshToken) = await CreateAccessTokenAsync(request.ValidatedRequest);
            var response = new TokenResponse
            {
                AccessToken = accessToken,
                AccessTokenLifetime = request.ValidatedRequest.AccessTokenLifetime,
                Custom = request.CustomResponse,
                Scope = request.ValidatedRequest.DeviceCode.AuthorizedScopes.ToSpaceSeparatedString()
            };
    
            //////////////////////////
            // refresh token
            /////////////////////////
            if (refreshToken.IsPresent())
            {
                response.RefreshToken = refreshToken;
            }
    
            //////////////////////////
            // id token
            /////////////////////////
            if (request.ValidatedRequest.DeviceCode.IsOpenId)
            {
                // load the client that belongs to the device code
                Client client = null;
                if (request.ValidatedRequest.DeviceCode.ClientId != null)
                {
                    client = await Clients.FindEnabledClientByIdAsync(request.ValidatedRequest.DeviceCode.ClientId);
                }
                if (client == null)
                {
                    throw new InvalidOperationException("Client does not exist anymore.");
                }
    
                var parsedScopesResult = ScopeParser.ParseScopeValues(request.ValidatedRequest.DeviceCode.AuthorizedScopes);
                var validatedResources = await Resources.CreateResourceValidationResult(parsedScopesResult);
                
                var tokenRequest = new TokenCreationRequest
                {
                    Subject = request.ValidatedRequest.DeviceCode.Subject,
                    ValidatedResources = validatedResources,
                    AccessTokenToHash = response.AccessToken,
                    ValidatedRequest = request.ValidatedRequest
                };
    
                var idToken = await TokenService.CreateIdentityTokenAsync(tokenRequest);
                var jwt = await TokenService.CreateSecurityTokenAsync(idToken);
                response.IdentityToken = jwt;
            }
    
            return response;
        }
    
        /// <summary>
        /// Creates the response for an extension grant request.
        /// 为扩展授权请求创建响应。
        /// </summary>
        /// <param name="request">The request.</param>
        /// <returns></returns>
        protected virtual Task<TokenResponse> ProcessExtensionGrantRequestAsync(TokenRequestValidationResult request)
        {
            Logger.LogTrace("Creating response for extension grant request");
    
            return ProcessTokenRequestAsync(request);
        }
    
        /// <summary>
        /// Creates the response for a token request.
        /// 为令牌请求创建响应。
        /// </summary>
        /// <param name="validationResult">The validation result.</param>
        /// <returns></returns>
        protected virtual async Task<TokenResponse> ProcessTokenRequestAsync(TokenRequestValidationResult validationResult)
        {
            (var accessToken, var refreshToken) = await CreateAccessTokenAsync(validationResult.ValidatedRequest);
            var response = new TokenResponse
            {
                AccessToken = accessToken,
                AccessTokenLifetime = validationResult.ValidatedRequest.AccessTokenLifetime,
                Custom = validationResult.CustomResponse,
                Scope = validationResult.ValidatedRequest.ValidatedResources.RawScopeValues.ToSpaceSeparatedString()
            };
    
            if (refreshToken.IsPresent())
            {
                response.RefreshToken = refreshToken;
            }
    
            return response;
        }
    
        /// <summary>
        /// Creates the access/refresh token.
        /// 创建访问/刷新令牌。
        /// </summary>
        /// <param name="request">The request.</param>
        /// <returns></returns>
        /// <exception cref="System.InvalidOperationException">Client does not exist anymore.</exception>
        protected virtual async Task<(string accessToken, string refreshToken)> CreateAccessTokenAsync(ValidatedTokenRequest request)
        {
            TokenCreationRequest tokenRequest;
            bool createRefreshToken;
    
            if (request.AuthorizationCode != null)
            {
                createRefreshToken = request.AuthorizationCode.RequestedScopes.Contains(IdentityServerConstants.StandardScopes.OfflineAccess);
    
                // load the client that belongs to the authorization code
                // 加载属于授权代码的客户端
                Client client = null;
                if (request.AuthorizationCode.ClientId != null)
                {
                    client = await Clients.FindEnabledClientByIdAsync(request.AuthorizationCode.ClientId);
                }
                if (client == null)
                {
                    throw new InvalidOperationException("Client does not exist anymore.");
                }
    
                var parsedScopesResult = ScopeParser.ParseScopeValues(request.AuthorizationCode.RequestedScopes);
                var validatedResources = await Resources.CreateResourceValidationResult(parsedScopesResult);
    
                tokenRequest = new TokenCreationRequest
                {
                    Subject = request.AuthorizationCode.Subject,
                    Description = request.AuthorizationCode.Description,
                    ValidatedResources = validatedResources,
                    ValidatedRequest = request
                };
            }
            else if (request.DeviceCode != null)
            {
                createRefreshToken = request.DeviceCode.AuthorizedScopes.Contains(IdentityServerConstants.StandardScopes.OfflineAccess);
    
                Client client = null;
                if (request.DeviceCode.ClientId != null)
                {
                    client = await Clients.FindEnabledClientByIdAsync(request.DeviceCode.ClientId);
                }
                if (client == null)
                {
                    throw new InvalidOperationException("Client does not exist anymore.");
                }
    
                var parsedScopesResult = ScopeParser.ParseScopeValues(request.DeviceCode.AuthorizedScopes);
                var validatedResources = await Resources.CreateResourceValidationResult(parsedScopesResult);
    
                tokenRequest = new TokenCreationRequest
                {
                    Subject = request.DeviceCode.Subject,
                    Description = request.DeviceCode.Description,
                    ValidatedResources = validatedResources,
                    ValidatedRequest = request
                };
            }
            else
            {
                createRefreshToken = request.ValidatedResources.Resources.OfflineAccess;
    
                tokenRequest = new TokenCreationRequest
                {
                    Subject = request.Subject,
                    ValidatedResources = request.ValidatedResources,
                    ValidatedRequest = request
                };
            }
    
            var at = await TokenService.CreateAccessTokenAsync(tokenRequest);
            var accessToken = await TokenService.CreateSecurityTokenAsync(at);
    
            if (createRefreshToken)
            {
                var refreshToken = await RefreshTokenService.CreateRefreshTokenAsync(tokenRequest.Subject, at, request.Client);
                return (accessToken, refreshToken);
            }
    
            return (accessToken, null);
        }
    
        /// <summary>
        /// Creates an id_token for a refresh token request if identity resources have been requested.
        /// 如果已请求标识资源,则为刷新令牌请求创建id_token。
        /// </summary>
        /// <param name="request">The request.</param>
        /// <param name="newAccessToken">The new access token.</param>
        /// <returns></returns>
        protected virtual async Task<string> CreateIdTokenFromRefreshTokenRequestAsync(ValidatedTokenRequest request, string newAccessToken)
        {
            // todo: can we just check for "openid" scope? 我们能检查一下“openid”范围吗?
            //var identityResources = await Resources.FindEnabledIdentityResourcesByScopeAsync(request.RefreshToken.Scopes);
            //if (identityResources.Any())
    
            if (request.RefreshToken.Scopes.Contains(OidcConstants.StandardScopes.OpenId))
            {
                var oldAccessToken = request.RefreshToken.AccessToken;
    
                var parsedScopesResult = ScopeParser.ParseScopeValues(oldAccessToken.Scopes);
                var validatedResources = await Resources.CreateResourceValidationResult(parsedScopesResult);
    
                var tokenRequest = new TokenCreationRequest
                {
                    Subject = request.RefreshToken.Subject,
                    ValidatedResources = validatedResources,
                    ValidatedRequest = request,
                    AccessTokenToHash = newAccessToken
                };
    
                var idToken = await TokenService.CreateIdentityTokenAsync(tokenRequest);
                return await TokenService.CreateSecurityTokenAsync(idToken);
            }
    
            return null;
        }
    }
    View Code

     返回结果类:

    路径:EndpointsResultsTokenResult.cs
    internal class TokenResult : IEndpointResult
    {
        public TokenResponse Response { get; set; }
    
        public TokenResult(TokenResponse response)
        {
            Response = response ?? throw new ArgumentNullException(nameof(response));
        }
    
        public async Task ExecuteAsync(HttpContext context)
        {
            context.Response.SetNoCache();
    
            var dto = new ResultDto
            {
                id_token = Response.IdentityToken,
                access_token = Response.AccessToken,
                refresh_token = Response.RefreshToken,
                expires_in = Response.AccessTokenLifetime,
                token_type = OidcConstants.TokenResponse.BearerTokenType,
                scope = Response.Scope,
                
                Custom = Response.Custom
            };
    
            await context.Response.WriteJsonAsync(dto);
        }
    
        internal class ResultDto
        {
            public string id_token { get; set; }
            public string access_token { get; set; }
            public int expires_in { get; set; }
            public string token_type { get; set; }
            public string refresh_token { get; set; }
            public string scope { get; set; }
    
            [JsonExtensionData]
            public Dictionary<string, object> Custom { get; set; }
        }
    }
    View Code

     (3)用户名密码验证

    在 TokenRequestValidator 类中 会调用 IResourceOwnerPasswordValidator 接口的实现类 ,验证用户密码返回验证结果信息GrantValidationResult,组件默认注入的是 NotSupportedResourceOwnerPasswordValidator,

    //IdentityServerBuilderExtensionsCore类
    public
    static IIdentityServerBuilder AddValidators(this IIdentityServerBuilder builder) { ... builder.Services.TryAddTransient<ITokenRequestValidator, TokenRequestValidator>(); ... builder.Services.TryAddTransient<IResourceOwnerPasswordValidator, NotSupportedResourceOwnerPasswordValidator>(); ... }
    //NotSupportedResourceOwnerPasswordValidator位置在ValidationDefaultNotSupportedResouceOwnerCredentialValidator.cs

    public class NotSupportedResourceOwnerPasswordValidator : IResourceOwnerPasswordValidator
        {
            private readonly ILogger _logger;
    
            /// <summary>
            /// Initializes a new instance of the <see cref="NotSupportedResourceOwnerPasswordValidator"/> class.
            /// </summary>
            /// <param name="logger">The logger.</param>
            public NotSupportedResourceOwnerPasswordValidator(ILogger<NotSupportedResourceOwnerPasswordValidator> logger)
            {
                _logger = logger;
            }
    
            /// <summary>
            /// Validates the resource owner password credential
            /// </summary>
            /// <param name="context">The context.</param>
            /// <returns></returns>
            public Task ValidateAsync(ResourceOwnerPasswordValidationContext context)
            {
                context.Result = new GrantValidationResult(TokenRequestErrors.UnsupportedGrantType);
    
                _logger.LogInformation("Resource owner password credential type not supported. Configure an IResourceOwnerPasswordValidator.");
                return Task.CompletedTask;
            }
        }
    View Code

    在开发中我们通常采用  .AddTestUsers(TestUsers.Users) 添加测试用户,这方法会注入 TestUserResourceOwnerPasswordValidatorc类,从添加的用户列表中验证用户密码,返回验证结果信息GrantValidationResult,

        public static class IdentityServerBuilderExtensions
        {
            /// <summary>
            /// Adds test users.
            /// </summary>
            /// <param name="builder">The builder.</param>
            /// <param name="users">The users.</param>
            /// <returns></returns>
            public static IIdentityServerBuilder AddTestUsers(this IIdentityServerBuilder builder, List<TestUser> users)
            {
                builder.Services.AddSingleton(new TestUserStore(users));
                builder.AddProfileService<TestUserProfileService>();
                builder.AddResourceOwnerValidator<TestUserResourceOwnerPasswordValidator>();
    
                return builder;
            }
        }
    View Code
    public class TestUserResourceOwnerPasswordValidator : IResourceOwnerPasswordValidator
        {
            private readonly TestUserStore _users;
            private readonly ISystemClock _clock;
    
            /// <summary>
            /// Initializes a new instance of the <see cref="TestUserResourceOwnerPasswordValidator"/> class.
            /// </summary>
            /// <param name="users">The users.</param>
            /// <param name="clock">The clock.</param>
            public TestUserResourceOwnerPasswordValidator(TestUserStore users, ISystemClock clock)
            {
                _users = users;
                _clock = clock;
            }
    
            /// <summary>
            /// Validates the resource owner password credential
            /// 验证资源所有者密码凭据
            /// </summary>
            /// <param name="context">The context.</param>
            /// <returns></returns>
            public Task ValidateAsync(ResourceOwnerPasswordValidationContext context)
            {
                if (_users.ValidateCredentials(context.UserName, context.Password))
                {
                    var user = _users.FindByUsername(context.UserName);
                    context.Result = new GrantValidationResult(
                        user.SubjectId ?? throw new ArgumentException("Subject ID not set", nameof(user.SubjectId)), 
                        OidcConstants.AuthenticationMethods.Password, _clock.UtcNow.UtcDateTime, 
                        user.Claims);
                }
    
                return Task.CompletedTask;
            }
        }
    View Code

    因此我们可以继承IResourceOwnerPasswordValidator接口实现自己的用户密码校验逻辑,如自己定义的数据库表结构中通过传入的ResourceOwnerPasswordValidationContext类的 context.UserName, context.Password参数完成自己的用户校验逻辑,通过配置 .AddResourceOwnerValidator<T>()替换默认的验证行为。

    \IdentityServerBuilderExtensionsAdditional类
    \路径ConfigurationDependencyInjectionBuilderExtensionsAdditional.cs
    public static IIdentityServerBuilder AddResourceOwnerValidator<T>(this IIdentityServerBuilder builder)
        where T : class, IResourceOwnerPasswordValidator
    {
        builder.Services.AddTransient<IResourceOwnerPasswordValidator, T>();
    
        return builder;
    }
  • 相关阅读:
    博客园
    未释放的已删除文件
    ssh连接缓慢
    剑指 Offer 38. 字符串的排列
    剑指 Offer 37. 序列化二叉树
    剑指 Offer 50. 第一个只出现一次的字符
    剑指 Offer 36. 二叉搜索树与双向链表
    剑指 Offer 35. 复杂链表的复制
    剑指 Offer 34. 二叉树中和为某一值的路径
    剑指 Offer 33. 二叉搜索树的后序遍历序列
  • 原文地址:https://www.cnblogs.com/liuxtj/p/13652169.html
Copyright © 2011-2022 走看看