上篇文章我介绍了如何强制令牌过时的实现,相信你们对
IdentityServer4
的验证流程有了更深的了解,本篇我将介绍如何使用自定义的受权方式集成老的业务系统验证,而后根据不一样的客户端使用不一样的认证方式来集成到统一认证平台。html.netcore项目实战交流群(637326624),有兴趣的朋友能够在群里交流讨论。sql
当咱们须要使用开源项目的某些功能时,最好了解实现的原理,才能正确和熟练使用功能,避免出现各类未知bug问题和出现问题没法解决的被动场面。c#
在使用此功能前,咱们须要了解完整的实现流程,下面我将从源码开始讲解IdentityServer4
是如何实现自定义的受权方式。app
从我以前的文章中咱们知道受权方式是经过Grant_Type
的值来判断的,因此咱们自定义的受权方式,也是经过此值来区分,因此须要了解自定义的值处理流程。TokenRequestValidator
是请求验证的方法,除了常规验证外,还增长了自定义的验证方式。async
public async Task<TokenRequestValidationResult> ValidateRequestAsync(NameValueCollection parameters, ClientSecretValidationResult clientValidationResult) { _logger.LogDebug("Start token request validation"); _validatedRequest = new ValidatedTokenRequest { Raw = parameters ?? throw new ArgumentNullException(nameof(parameters)), Options = _options }; if (clientValidationResult == null) throw new ArgumentNullException(nameof(clientValidationResult)); _validatedRequest.SetClient(clientValidationResult.Client, clientValidationResult.Secret, clientValidationResult.Confirmation); ///////////////////////////////////////////// // check client protocol type ///////////////////////////////////////////// if (_validatedRequest.Client.ProtocolType != IdentityServerConstants.ProtocolTypes.OpenIdConnect) { LogError("Client {clientId} has invalid protocol type for token endpoint: expected {expectedProtocolType} but found {protocolType}", _validatedRequest.Client.ClientId, IdentityServerConstants.ProtocolTypes.OpenIdConnect, _validatedRequest.Client.ProtocolType); return Invalid(OidcConstants.TokenErrors.InvalidClient); } ///////////////////////////////////////////// // check grant type ///////////////////////////////////////////// var grantType = parameters.Get(OidcConstants.TokenRequest.GrantType); if (grantType.IsMissing()) { LogError("Grant type is missing"); return Invalid(OidcConstants.TokenErrors.UnsupportedGrantType); } if (grantType.Length > _options.InputLengthRestrictions.GrantType) { LogError("Grant type is too long"); return Invalid(OidcConstants.TokenErrors.UnsupportedGrantType); } _validatedRequest.GrantType = grantType; switch (grantType) { case OidcConstants.GrantTypes.AuthorizationCode: return await RunValidationAsync(ValidateAuthorizationCodeRequestAsync, parameters); case OidcConstants.GrantTypes.ClientCredentials: return await RunValidationAsync(ValidateClientCredentialsRequestAsync, parameters); case OidcConstants.GrantTypes.Password: return await RunValidationAsync(ValidateResourceOwnerCredentialRequestAsync, parameters); case OidcConstants.GrantTypes.RefreshToken: return await RunValidationAsync(ValidateRefreshTokenRequestAsync, parameters); default://统一的自定义的验证方式 return await RunValidationAsync(ValidateExtensionGrantRequestAsync, parameters); } }
从上面代码能够看出,除了内置的受权方式,其余的都是用ValidateExtensionGrantRequestAsync
来进行验证,详细的验证规则继续分析实现过程。ide
private async Task<TokenRequestValidationResult> ValidateExtensionGrantRequestAsync(NameValueCollection parameters) { _logger.LogDebug("Start validation of custom grant token request"); ///////////////////////////////////////////// // 校验客户端是否开启了此受权方式 ///////////////////////////////////////////// if (!_validatedRequest.Client.AllowedGrantTypes.Contains(_validatedRequest.GrantType)) { LogError("{clientId} does not have the custom grant type in the allowed list, therefore requested grant is not allowed", _validatedRequest.Client.ClientId); return Invalid(OidcConstants.TokenErrors.UnsupportedGrantType); } ///////////////////////////////////////////// // 判断是否注入了此自定义的受权实现 ///////////////////////////////////////////// if (!_extensionGrantValidator.GetAvailableGrantTypes().Contains(_validatedRequest.GrantType, StringComparer.Ordinal)) { LogError("No validator is registered for the grant type: {grantType}", _validatedRequest.GrantType); return Invalid(OidcConstants.TokenErrors.UnsupportedGrantType); } ///////////////////////////////////////////// // 校验是否支持scope ///////////////////////////////////////////// if (!await ValidateRequestedScopesAsync(parameters)) { return Invalid(OidcConstants.TokenErrors.InvalidScope); } ///////////////////////////////////////////// // 调用自定义的验证明现方法 ///////////////////////////////////////////// var result = await _extensionGrantValidator.ValidateAsync(_validatedRequest); if (result == null) { LogError("Invalid extension grant"); return Invalid(OidcConstants.TokenErrors.InvalidGrant); } if (result.IsError) { if (result.Error.IsPresent()) { LogError("Invalid extension grant: {error}", result.Error); return Invalid(result.Error, result.ErrorDescription, result.CustomResponse); } else { LogError("Invalid extension grant"); return Invalid(OidcConstants.TokenErrors.InvalidGrant, customResponse: result.CustomResponse); } } if (result.Subject != null) { ///////////////////////////////////////////// // 判断当前的用户是否可用 ///////////////////////////////////////////// var isActiveCtx = new IsActiveContext( result.Subject, _validatedRequest.Client, IdentityServerConstants.ProfileIsActiveCallers.ExtensionGrantValidation); await _profile.IsActiveAsync(isActiveCtx); if (isActiveCtx.IsActive == false) { // todo: raise event? LogError("User has been disabled: {subjectId}", result.Subject.GetSubjectId()); return Invalid(OidcConstants.TokenErrors.InvalidGrant); } _validatedRequest.Subject = result.Subject; } _logger.LogDebug("Validation of extension grant token request success"); return Valid(result.CustomResponse); }
从代码中能够看出,实现流程以下:测试
从源码中,能够发现流程已经很是清晰了,核心类ExtensionGrantValidator
实现了自定义受权的校验过程,进一步分析下此类的代码实现。ui
using IdentityServer4.Models; using Microsoft.Extensions.Logging; using System; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; namespace IdentityServer4.Validation { /// <summary> /// Validates an extension grant request using the registered validators /// </summary> public class ExtensionGrantValidator { private readonly ILogger _logger; private readonly IEnumerable<IExtensionGrantValidator> _validators; /// <summary> /// Initializes a new instance of the <see cref="ExtensionGrantValidator"/> class. /// </summary> /// <param name="validators">The validators.</param> /// <param name="logger">The logger.</param> public ExtensionGrantValidator(IEnumerable<IExtensionGrantValidator> validators, ILogger<ExtensionGrantValidator> logger) { if (validators == null) { _validators = Enumerable.Empty<IExtensionGrantValidator>(); } else { _validators = validators; } _logger = logger; } /// <summary> /// Gets the available grant types. /// </summary> /// <returns></returns> public IEnumerable<string> GetAvailableGrantTypes() { return _validators.Select(v => v.GrantType); } /// <summary> /// Validates the request. /// </summary> /// <param name="request">The request.</param> /// <returns></returns> public async Task<GrantValidationResult> ValidateAsync(ValidatedTokenRequest request) { var validator = _validators.FirstOrDefault(v => v.GrantType.Equals(request.GrantType, StringComparison.Ordinal)); if (validator == null) { _logger.LogError("No validator found for grant type"); return new GrantValidationResult(TokenRequestErrors.UnsupportedGrantType); } try { _logger.LogTrace("Calling into custom grant validator: {type}", validator.GetType().FullName); var context = new ExtensionGrantValidationContext { Request = request }; await validator.ValidateAsync(context); return context.Result; } catch (Exception e) { _logger.LogError(1, e, "Grant validation error: {message}", e.Message); return new GrantValidationResult(TokenRequestErrors.InvalidGrant); } } } }
从上面代码能够发现,自定义受权方式,只须要实现IExtensionGrantValidator
接口便可,而后支持多个自定义受权方式的共同使用。this
到此整个验证过程解析完毕了,而后再查看下生成Token流程,实现方法为TokenResponseGenerator
,这个方法并不陌生,前几篇介绍不一样的受权方式都介绍了,因此直接看实现代码。加密
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); default://自定义受权生成Token的方式 return await ProcessExtensionGrantRequestAsync(request); } } protected virtual Task<TokenResponse> ProcessExtensionGrantRequestAsync(TokenRequestValidationResult request) { Logger.LogTrace("Creating response for extension grant request"); return ProcessTokenRequestAsync(request); }
实现的代码方式和客户端模式及密码模式同样,这里就很少介绍了。
最后咱们查看下是如何注入IExtensionGrantValidator
,是否对外提供接入方式,发现IdentityServer4
提供了AddExtensionGrantValidator
扩展方法,咱们本身实现自定义受权后添加便可,详细实现代码以下。
public static IIdentityServerBuilder AddExtensionGrantValidator<T>(this IIdentityServerBuilder builder) where T : class, IExtensionGrantValidator { builder.Services.AddTransient<IExtensionGrantValidator, T>(); return builder; }
如今开始开发第一个自定义受权方式,GrantType
定义为CzarCustomUser
,而后实现IExtensionGrantValidator
接口,为了演示方便,我新建一个测试用户表,用来模拟老系统的登陆方式。
Create Table CzarCustomUser ( iid int identity, username varchar(50), usertruename varchar(50), userpwd varchar(100) ) --插入测试用户密码信息,测试数据密码不加密 insert into CzarCustomUser values('jinyancao','金焰的世界','777777')
而后把实现验证的方法,因为代码太简单,我就直接贴代码以下。
namespace Czar.AuthPlatform.Web.Application.IRepository { public interface ICzarCustomUserRepository { /// <summary> /// 根据帐号密码获取用户实体 /// </summary> /// <param name="uaccount">帐号</param> /// <param name="upassword">密码</param> /// <returns></returns> CzarCustomUser FindUserByuAccount(string uaccount, string upassword); } } namespace Czar.AuthPlatform.Web.Application.Repository { public class CzarCustomUserRepository : ICzarCustomUserRepository { private readonly string DbConn = ""; public CzarCustomUserRepository(IOptions<CzarConfig> czarConfig) { DbConn = czarConfig.Value.DbConnectionStrings; } /// <summary> /// 根据帐号密码获取用户实体 /// </summary> /// <param name="uaccount">帐号</param> /// <param name="upassword">密码</param> /// <returns></returns> public CzarCustomUser FindUserByuAccount(string uaccount, string upassword) { using (var connection = new SqlConnection(DbConn)) { string sql = @"SELECT * from CzarCustomUser where username=@uaccount and userpwd=upassword "; var result = connection.QueryFirstOrDefault<CzarCustomUser>(sql, new { uaccount, upassword }); return result; } } } } namespace Czar.AuthPlatform.Web.Application.IServices { public interface ICzarCustomUserServices { /// <summary> /// 根据帐号密码获取用户实体 /// </summary> /// <param name="uaccount">帐号</param> /// <param name="upassword">密码</param> /// <returns></returns> CzarCustomUser FindUserByuAccount(string uaccount, string upassword); } } namespace Czar.AuthPlatform.Web.Application.Services { public class CzarCustomUserServices: ICzarCustomUserServices { private readonly ICzarCustomUserRepository czarCustomUserRepository; public CzarCustomUserServices(ICzarCustomUserRepository czarCustomUserRepository) { this.czarCustomUserRepository = czarCustomUserRepository; } /// <summary> /// 根据帐号密码获取用户实体 /// </summary> /// <param name="uaccount">帐号</param> /// <param name="upassword">密码</param> /// <returns></returns> public CzarCustomUser FindUserByuAccount(string uaccount, string upassword) { return czarCustomUserRepository.FindUserByuAccount(uaccount, upassword); } } }
如今能够定义自定义的受权类型了,我起名为CzarCustomUserGrantValidator
,实现代码以下。
using Czar.AuthPlatform.Web.Application.IServices; using IdentityServer4.Models; using IdentityServer4.Validation; using System.Threading.Tasks; namespace Czar.AuthPlatform.Web.Application.Ids4 { /// <summary> /// 金焰的世界 /// 2019-01-28 /// 自定义用户受权 /// </summary> public class CzarCustomUserGrantValidator : IExtensionGrantValidator { public string GrantType => "CzarCustomUser"; private readonly ICzarCustomUserServices czarCustomUserServices; public CzarCustomUserGrantValidator(ICzarCustomUserServices czarCustomUserServices) { this.czarCustomUserServices = czarCustomUserServices; } public Task ValidateAsync(ExtensionGrantValidationContext context) { var userName = context.Request.Raw.Get("czar_name"); var userPassword = context.Request.Raw.Get("czar_password"); if (string.IsNullOrEmpty(userName) || string.IsNullOrEmpty(userPassword)) { context.Result = new GrantValidationResult(TokenRequestErrors.InvalidGrant); } //校验登陆 var result = czarCustomUserServices.FindUserByuAccount(userName, userPassword); if (result==null) { context.Result = new GrantValidationResult(TokenRequestErrors.InvalidGrant); } //添加指定的claims context.Result = new GrantValidationResult( subject: result.iid.ToString(), authenticationMethod: GrantType, claims: result.Claims); return Task.CompletedTask; } } }
这就实现了自定义受权的功能,是否是很简单呢?而后添加此扩展方法。
services.AddIdentityServer(option => { option.PublicOrigin = Configuration["CzarConfig:PublicOrigin"]; }) .AddDeveloperSigningCredential() .AddDapperStore(option => { option.DbConnectionStrings = Configuration["CzarConfig:DbConnectionStrings"]; }) .AddResourceOwnerValidator<CzarResourceOwnerPasswordValidator>() .AddProfileService<CzarProfileService>() .AddSecretValidator<JwtSecretValidator>() //添加自定义受权 .AddExtensionGrantValidator<CzarCustomUserGrantValidator>();
如今是否是就可使用自定义受权的方式了呢?打开PostMan
测试,按照源码解析和设计参数,测试信息以下,发现报错,原来是还未配置好客户端访问权限,开启权限测试以下。
在使用IdentityServer4
时咱们必定要理解整个验证流程。根据此次配置,我再梳理下流程以下:
经过此流程会发现咱们缺乏受权方式配置,因此请求时提示上面的提示,既然知道缘由了,那就很简单的来实现,添加客户端自定义受权模式。此信息是在ClientGrantTypes
表中,字段为客户端ID和受权方式。我测试的客户端ID为21,受权方式为CzarCustomUser
,那直接使用SQL语句插入关系,而后再测试。
INSERT INTO ClientGrantTypes VALUES(21,'CzarCustomUser');
发现能够获取到预期结果,而后查看access_token是什么内容,显示以下。
显示的信息和咱们定义的信息相同,并且能够经过amr
来区分受权类型,不一样的业务系统使用不一样的认证方式,而后统一集成到认证平台便可。
本篇我介绍了自定义受权方式,从源码解析到最后的实现详细讲解了实现原理,并使用测试的用户来实现自定义的认证流程,本篇涉及的知识点很少,可是很是重要,由于咱们在使用统一身份认证时常常会遇到多种认证方式的结合,和多套不一样应用用户的使用,在掌握了受权原理后,就能在不一样的受权方式中切换的游刃有余。
思考下,有了这些知识后,关于短信验证码登陆和扫码登陆是否是有心理有底了呢?若是本身实现这类登陆应该都知道从哪里下手了吧。
下篇我将介绍经常使用登陆的短信验证码受权方式,尽情期待吧。