【.NET Core项目实战-统一认证平台】第十三章 授权篇-如何强制有效令牌过期

【.NET Core项目实战-统一认证平台】开篇及目录索引

上一篇我介绍了JWT的生成验证及流程内容,相信大家也对JWT非常熟悉了,今天将从一个小众的需求出发,介绍如何强制令牌过期的思路和实现过程。

.netcore项目实战交流群(637326624),有兴趣的朋友可以在群里交流讨论。

一、前言

众所周知,IdentityServer4 默认支持两种类型的 Token,一种是 Reference Token,一种是 JWT Token 。前者的特点是 Token 的有效与否是由 Token 颁发服务集中化控制的,颁发的时候会持久化 Token,然后每次验证都需要将 Token 传递到颁发服务进行验证,是一种中心化的验证方式。JWT Token的特点与前者相反,每个资源服务不需要每次都要都去颁发服务进行验证 Token 的有效性验证,上一篇也介绍了,该 Token 由三部分组成,其中最后一部分包含了一个签名,是在颁发的时候采用非对称加密算法进行数据的签名,保证了 Token 的不可篡改性,校验时与颁发服务的交互,仅仅是获取公钥用于验证签名,且该公钥获取以后可以自己缓存,持续使用,不用再去交互获得,除非数字证书发生变化。

二、Reference Token的用法

上一篇已经介绍了JWT Token的整个生成过程,为了演示强制过期策略,这里需要了解下Reference Token是如何生成和存储的,这样可以帮助掌握IdentityServer4所有的工作方式。

1、新增测试客户端

由于我们已有数据库,为了方便演示,我直接使用SQL脚本新增。

--新建客户端(AccessTokenType 0 JWT 1 Reference Token) INSERT INTO Clients(AccessTokenType,AccessTokenLifetime,ClientId,ClientName,Enabled) VALUES(1,3600,'clientref','测试Ref客户端',1); -- SELECT * FROM Clients WHERE ClientId='clientref' --2、添加客户端密钥,密码为(secreta) sha256 INSERT INTO ClientSecrets VALUES(23,'',null,'SharedSecret','2tytAAysa0zaDuNthsfLdjeEtZSyWw8WzbzM8pfTGNI='); --3、增加客户端授权权限 INSERT INTO ClientGrantTypes VALUES(23,'client_credentials'); --4、增加客户端能够访问scope INSERT INTO ClientScopes VALUES(23,'mpc_gateway');

这里添加了认证类型为Reference Token客户端为clientref,并分配了客户端授权和能访问的scope,然后我们使用PostMan测试下客户端。

【.NET Core项目实战-统一认证平台】第十三章 授权篇-如何强制有效令牌过期

如上图所示,可以正确的返回access_token,且有标记的过期时间。

2、如何校验token的有效性?

IdentityServer4给已经提供了Token的校验地址,可以通过访问此地址来校验Token的有效性,使用前需要了解传输的参数和校验方式。

在授权篇开始时我介绍了IdentityServer4的源码剖析,相信都掌握了看源码的方式,这里就不详细介绍了。

核心代码为IntrospectionEndpoint,标注出校验的核心代码,用到的几个校验方式已经注释出来了。

private async Task<IEndpointResult> ProcessIntrospectionRequestAsync(HttpContext context) { _logger.LogDebug("Starting introspection request."); // 校验ApiResources信息,支持 basic 和 form两种方式,和授权时一样 var apiResult = await _apiSecretValidator.ValidateAsync(context); if (apiResult.Resource == null) { _logger.LogError("API unauthorized to call introspection endpoint. aborting."); return new StatusCodeResult(HttpStatusCode.Unauthorized); } var body = await context.Request.ReadFormAsync(); if (body == null) { _logger.LogError("Malformed request body. aborting."); await _events.RaiseAsync(new TokenIntrospectionFailureEvent(apiResult.Resource.Name, "Malformed request body")); return new StatusCodeResult(HttpStatusCode.BadRequest); } // 验证access_token的有效性,根据 _logger.LogTrace("Calling into introspection request validator: {type}", _requestValidator.GetType().FullName); var validationResult = await _requestValidator.ValidateAsync(body.AsNameValueCollection(), apiResult.Resource); if (validationResult.IsError) { LogFailure(validationResult.Error, apiResult.Resource.Name); await _events.RaiseAsync(new TokenIntrospectionFailureEvent(apiResult.Resource.Name, validationResult.Error)); return new BadRequestResult(validationResult.Error); } // response generation _logger.LogTrace("Calling into introspection response generator: {type}", _responseGenerator.GetType().FullName); var response = await _responseGenerator.ProcessAsync(validationResult); // render result LogSuccess(validationResult.IsActive, validationResult.Api.Name); return new IntrospectionResult(response); } //校验Token有效性核心代码 public async Task<TokenValidationResult> ValidateAccessTokenAsync(string token, string expectedScope = null) { _logger.LogTrace("Start access token validation"); _log.ExpectedScope = expectedScope; _log.ValidateLifetime = true; TokenValidationResult result; if (token.Contains(".")) {//jwt if (token.Length > _options.InputLengthRestrictions.Jwt) { _logger.LogError("JWT too long"); return new TokenValidationResult { IsError = true, Error = OidcConstants.ProtectedResourceErrors.InvalidToken, ErrorDescription = "Token too long" }; } _log.AccessTokenType = AccessTokenType.Jwt.ToString(); result = await ValidateJwtAsync( token, string.Format(Constants.AccessTokenAudience, _context.HttpContext.GetIdentityServerIssuerUri().EnsureTrailingSlash()), await _keys.GetValidationKeysAsync()); } else {//Reference token if (token.Length > _options.InputLengthRestrictions.TokenHandle) { _logger.LogError("token handle too long"); return new TokenValidationResult { IsError = true, Error = OidcConstants.ProtectedResourceErrors.InvalidToken, ErrorDescription = "Token too long" }; } _log.AccessTokenType = AccessTokenType.Reference.ToString(); result = await ValidateReferenceAccessTokenAsync(token); } _log.Claims = result.Claims.ToClaimsDictionary(); if (result.IsError) { return result; } // make sure client is still active (if client_id claim is present) var clientClaim = result.Claims.FirstOrDefault(c => c.Type == JwtClaimTypes.ClientId); if (clientClaim != null) { var client = await _clients.FindEnabledClientByIdAsync(clientClaim.Value); if (client == null) { _logger.LogError("Client deleted or disabled: {clientId}", clientClaim.Value); result.IsError = true; result.Error = OidcConstants.ProtectedResourceErrors.InvalidToken; result.Claims = null; return result; } } // make sure user is still active (if sub claim is present) var subClaim = result.Claims.FirstOrDefault(c => c.Type == JwtClaimTypes.Subject); if (subClaim != null) { var principal = Principal.Create("tokenvalidator", result.Claims.ToArray()); if (result.ReferenceTokenId.IsPresent()) { principal.Identities.First().AddClaim(new Claim(JwtClaimTypes.ReferenceTokenId, result.ReferenceTokenId)); } var isActiveCtx = new IsActiveContext(principal, result.Client, IdentityServerConstants.ProfileIsActiveCallers.AccessTokenValidation); await _profile.IsActiveAsync(isActiveCtx); if (isActiveCtx.IsActive == false) { _logger.LogError("User marked as not active: {subject}", subClaim.Value); result.IsError = true; result.Error = OidcConstants.ProtectedResourceErrors.InvalidToken; result.Claims = null; return result; } } // check expected scope(s) if (expectedScope.IsPresent()) { var scope = result.Claims.FirstOrDefault(c => c.Type == JwtClaimTypes.Scope && c.Value == expectedScope); if (scope == null) { LogError(string.Format("Checking for expected scope {0} failed", expectedScope)); return Invalid(OidcConstants.ProtectedResourceErrors.InsufficientScope); } } _logger.LogDebug("Calling into custom token validator: {type}", _customValidator.GetType().FullName); var customResult = await _customValidator.ValidateAccessTokenAsync(result); if (customResult.IsError) { LogError("Custom validator failed: " + (customResult.Error ?? "unknown")); return customResult; } // add claims again after custom validation _log.Claims = customResult.Claims.ToClaimsDictionary(); LogSuccess(); return customResult; }

有了上面的校验代码,就可以很容易掌握使用的参数和校验的方式,现在我们就分别演示JWT Token和Reference token两个校验方式及返回的值。

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:https://www.heiqu.com/wpppjw.html