【.NET Core项目实战-统一认证平台】第十一章 授权篇-密码授权模式

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

上篇文章介绍了基于Ids4客户端授权的原理及如何实现自定义的客户端授权,并配合网关实现了统一的授权异常返回值和权限配置等相关功能,本篇将介绍密码授权模式,从使用场景、源码剖析到具体实现详细讲解密码授权模式的相关应用。

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

一、使用场景?

由于密码授权模式需要用户在业务系统输入账号密码,为了安全起见,对于使用密码模式的业务系统,我们认为是绝对可靠的,不存在泄漏用户名和密码的风险,所以使用场景定位为公司内部系统或集团内部系统或公司内部app等内部应用,非内部应用,尽量不要开启密码授权模式,防止用户账户泄漏。

这种模式适用于用户对应用程序高度信任的情况。比如是用户系统的一部分。

二、Ids4密码模式的默认实现剖析

在我们使用密码授权模式之前,我们需要理解密码模式是如何实现的,在上一篇中,我介绍了客户端授权的实现及源码剖析,相信我们已经对Ids4客户端授权已经熟悉,今天继续分析密码模式是如何获取到令牌的。

Ids4的所有授权都在TokenEndpoint方法中,密码模式授权也是先校验客户端授权,如果客户端校验失败,直接返回删除信息,如果客户端校验成功,继续校验用户名和密码,详细实现代码如下。

1、校验是否存在grantType,然后根据不同的类型启用不同的校验方式。

// TokenRequestValidator.cs 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: //1、密码授权模式调用方法 return await RunValidationAsync(ValidateResourceOwnerCredentialRequestAsync, parameters); case OidcConstants.GrantTypes.RefreshToken: return await RunValidationAsync(ValidateRefreshTokenRequestAsync, parameters); default: return await RunValidationAsync(ValidateExtensionGrantRequestAsync, parameters); } }

2、启用密码授权模式校验规则,首先校验传输的参数和scope是否存在,然后校验用户名密码是否准确,最后校验用户是否可用。

private async Task<TokenRequestValidationResult> ValidateResourceOwnerCredentialRequestAsync(NameValueCollection parameters) { _logger.LogDebug("Start resource owner password token request validation"); ///////////////////////////////////////////// // 校验授权模式 ///////////////////////////////////////////// if (!_validatedRequest.Client.AllowedGrantTypes.Contains(GrantType.ResourceOwnerPassword)) { LogError("{clientId} not authorized for resource owner flow, check the AllowedGrantTypes of client", _validatedRequest.Client.ClientId); return Invalid(OidcConstants.TokenErrors.UnauthorizedClient); } ///////////////////////////////////////////// // 校验客户端是否允许这些scope ///////////////////////////////////////////// if (!(await ValidateRequestedScopesAsync(parameters))) { return Invalid(OidcConstants.TokenErrors.InvalidScope); } ///////////////////////////////////////////// // 校验参数是否为定义的用户名或密码参数 ///////////////////////////////////////////// var userName = parameters.Get(OidcConstants.TokenRequest.UserName); var password = parameters.Get(OidcConstants.TokenRequest.Password); if (userName.IsMissing() || password.IsMissing()) { LogError("Username or password missing"); return Invalid(OidcConstants.TokenErrors.InvalidGrant); } 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; ///////////////////////////////////////////// // 校验用户名和密码是否准确 ///////////////////////////////////////////// var resourceOwnerContext = new ResourceOwnerPasswordValidationContext { UserName = userName, Password = password, Request = _validatedRequest }; //默认使用的是 TestUserResourceOwnerPasswordValidator await _resourceOwnerValidator.ValidateAsync(resourceOwnerContext); if (resourceOwnerContext.Result.IsError) { if (resourceOwnerContext.Result.Error == OidcConstants.TokenErrors.UnsupportedGrantType) { LogError("Resource owner password credential grant type not supported"); await RaiseFailedResourceOwnerAuthenticationEventAsync(userName, "password grant type not supported"); return Invalid(OidcConstants.TokenErrors.UnsupportedGrantType, customResponse: resourceOwnerContext.Result.CustomResponse); } var errorDescription = "invalid_username_or_password"; if (resourceOwnerContext.Result.ErrorDescription.IsPresent()) { errorDescription = resourceOwnerContext.Result.ErrorDescription; } LogInfo("User authentication failed: {error}", errorDescription ?? resourceOwnerContext.Result.Error); await RaiseFailedResourceOwnerAuthenticationEventAsync(userName, errorDescription); 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); return Invalid(OidcConstants.TokenErrors.InvalidGrant); } ///////////////////////////////////////////// // 设置用户可用,比如用户授权后被锁定,可以通过此方法实现 默认实现 TestUserProfileService ///////////////////////////////////////////// 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: {subjectId}", resourceOwnerContext.Result.Subject.GetSubjectId()); await RaiseFailedResourceOwnerAuthenticationEventAsync(userName, "user is inactive"); return Invalid(OidcConstants.TokenErrors.InvalidGrant); } _validatedRequest.UserName = userName; _validatedRequest.Subject = resourceOwnerContext.Result.Subject; await RaiseSuccessfulResourceOwnerAuthenticationEventAsync(userName, resourceOwnerContext.Result.Subject.GetSubjectId()); _logger.LogDebug("Resource owner password token request validation success."); return Valid(resourceOwnerContext.Result.CustomResponse); }

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

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