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;
    }
  • 相关阅读:
    当开发者产生一个伟大的想法之后应该做的10件事
    PUT 还是 POST ?
    Failed to issue method call: Unit mysqld.service failed to load: No such file or directory.
    使用 Protocol Buffers 代替 JSON 的五个原因
    Java 打印堆栈的几种方法
    Eclipse调试Java的10个技巧
    如何使用命令查看系统名称?
    Feed系统架构资料收集
    dcm4chee 修改默认(0002,0013) ImplementationVersionName
    【原创】分布式之数据库和缓存双写一致性方案解析
  • 原文地址:https://www.cnblogs.com/liuxtj/p/13652169.html
Copyright © 2011-2022 走看看