本人学习笔记,理解的可能不对,没时间详细整理,请前辈们指教。如果您看了觉得有收获,我非常贴心地在右侧提供了打赏入口 =====>(转文末)。
有兴趣的朋友可以 加群: 169366609 ,一起探讨。
看看下面这些很熟悉的 url片段:
public const string Authorize = "connect/authorize";
public const string AuthorizeCallback = Authorize + "/callback";
public const string DiscoveryConfiguration = ".well-known/openid-configuration";
public const string DiscoveryWebKeys = DiscoveryConfiguration + "/jwks";
public const string Token = "connect/token";
public const string Revocation = "connect/revocation";
public const string UserInfo = "connect/userinfo";
public const string Introspection = "connect/introspect";
public const string EndSession = "connect/endsession";
public const string EndSessionCallback = EndSession + "/callback";
public const string CheckSession = "connect/checksession";
internal class TokenEndpoint : IEndpointHandler { private readonly IClientSecretValidator _clientValidator; private readonly ITokenRequestValidator _requestValidator; private readonly ITokenResponseGenerator _responseGenerator; public async Task<IEndpointResult> ProcessAsync(HttpContext context) { return await ProcessTokenRequestAsync(context); } private async Task<IEndpointResult> ProcessTokenRequestAsync(HttpContext context) { var clientResult = await _clientValidator.ValidateAsync(context); var requestResult = await _requestValidator.ValidateRequestAsync(form, clientResult); var response = await _responseGenerator.ProcessAsync(requestResult); return new TokenResult(response); } } internal class AuthorizeEndpoint : AuthorizeEndpointBase { public override async Task<IEndpointResult> ProcessAsync(HttpContext context) { var user = await UserSession.GetUserAsync(); var result = await ProcessAuthorizeRequestAsync(values, user, null); return result; } } internal abstract class AuthorizeEndpointBase : IEndpointHandler { private readonly IAuthorizeRequestValidator _validator; private readonly IAuthorizeInteractionResponseGenerator _interactionGenerator; private readonly IAuthorizeResponseGenerator _authorizeResponseGenerator; internal async Task<IEndpointResult> ProcessAuthorizeRequestAsync(NameValueCollection parameters,
ClaimsPrincipal user, ConsentResponse consent) { var result = await _validator.ValidateAsync(parameters, user); var request = result.ValidatedRequest; var interactionResult = await _interactionGenerator.ProcessInteractionAsync(request, consent); if (interactionResult.IsLogin) { return new LoginPageResult(request); } if (interactionResult.IsConsent) { return new ConsentPageResult(request);} if (interactionResult.IsRedirect){ return new CustomRedirectResult(request, interactionResult.RedirectUrl); } var response = await _authorizeResponseGenerator.CreateResponseAsync(request); return new AuthorizeResult(response); } } public class TokenResponseGenerator : ITokenResponseGenerator { 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: return await ProcessExtensionGrantRequestAsync(request);// 这是扩展点 } } } public class AuthorizeResponseGenerator : IAuthorizeResponseGenerator { public virtual async Task<AuthorizeResponse> CreateResponseAsync(ValidatedAuthorizeRequest request) { if (request.GrantType == GrantType.AuthorizationCode) { return await CreateCodeFlowResponseAsync(request); } if (request.GrantType == GrantType.Implicit) { return await CreateImplicitFlowResponseAsync(request); } if (request.GrantType == GrantType.Hybrid) { return await CreateHybridFlowResponseAsync(request); }
// 这里就没扩展点了,如果真要扩展AuthorizeResult,可以在这里加代码
Logger.LogError("Unsupported grant type: " + request.GrantType); throw new InvalidOperationException("invalid grant type: " + request.GrantType); } }
4、请求授权流程的AuthorizeEndpoint与 AuthorizeResult
我们这里只看两个比较关键的 EndpointHandler:AuthorizeEndpoint和TokenEndpoint。从上面的截图可以看到 AuthorizeEndpoint.ProcessAsync方法最终返回的是AuthorizeResult,而TokenEndpoint.ProcessAsync方法最终返回的是TokenResult。这里将请求流程分成了两种:授权请求流程和Token请求流程。
这里先要搞清楚AuthorizeResult.ExecuteAsync与TokenResult.ExecuteAsync不同处理逻辑。
AuthorizeResult.ExecuteAsync如下:
图(二) AuthorizeResult.ExecuteAsync执行逻辑
显然,AuthorizeResult.ExecuteAsync是以 重定向或者POST的方式将token返回到客户端。
TokenResult.ExecuteAsync如下:
图(三) TokenResult.ExecuteAsync执行逻辑
TokenResult.ExecuteAsync的执行逻辑不用解释 就是输出 json格式的数据,json中携带token。
正是AuthorizeResult.ExecuteAsync与TokenResult.ExecuteAsync不同处理逻辑,决定了客户端如何接收token。因此客户端的callback里一定要按照返回的不同处理逻辑来写代码,以正确接收token。
上面比较了AuthorizeResult 与 TokenResult的不同处理方式,下面才开始介绍 AuthorizeEndpoint 与 AuthorizeResult。
让我们再看看图一中的AuthorizeEndpoint.ProcessAuthorizeRequestAsync的执行逻辑:
(1)、_validator.ValidateAsync:返回一个经过验证的ValidatedAuthorizeRequest对象,
此对象中注意描述了授权请求的各种信息,如Client、ClientClaims、ParsedSecret、AccessTokenType、Subject、
ResponseType、ResponseMode、GrantType、RedirectUri、RequestedScopes、State等等,等等,不一一列举了。
总之就是授权请求的各种信息,直观的理解就是 客户端或者浏览器向OP发起授权请求时,所携带的各种信息。
如果还不理解,可以看看下面的图四。要注意做左侧第一列
图(四)OAuth2.0的四种授权方式
现在我们回过头来看看AuthorizeEndpoint里的 IAuthorizeRequestValidator 与 IAuthorizeInteractionResponseGenerator。
先看IAuthorizeRequestValidator的实现类AuthorizeRequestValidator:
图(五)AuthorizeRequestValidator 读取客户端传过来的值,并验证
(2)、_interactionGenerator.ProcessInteractionAsync(request, consent)
此方法返回一个InteractionResponse对象。确定当前授权流程要执行哪步操作:是登录、确认授权还是重定向。
InteractionResponse就3个属性:IsLogin、IsConsent 和 RedirectUrl,确定了这三个属性的值后,就知道下一步要干什么了,
可以看看图一中的代码,再贴一次:
if (interactionResult.IsLogin) { return new LoginPageResult(request); }
if (interactionResult.IsConsent) { return new ConsentPageResult(request);}
if (interactionResult.IsRedirect){ return new CustomRedirectResult(request, interactionResult.RedirectUrl); }
这里的这三个IEndpointResult是授权请求流程的小插曲,此处是根据ValidatedAuthorizeRequest对象的数据确定当前应该重定向到登录、确认授权还是自定义重定向。从源码可以看出,此处并无扩展点,将来作者也许会在此处加个类似ICustomAuthorizeResponseGenerator接口,在此处插入一行代码,就可以改变InteractionResponse对象的属性值,从而改变交互流程。
(3)、_authorizeResponseGenerator.CreateResponseAsync :此处返回AuthorizeResponse对象,已经很接近AuthorizeResult了。
这里是根据 三种不同的授权方式:授权码流、隐式流和混合流,返回一个AuthorizeResponse对象,直接看代码:
图六: 授权码流、隐式流和混合流三种不同的授权方式下获取身份AuthorizeResponse
先不管生成AuthorizeResponse对象的代码逻辑。 AuthorizeResponse对象被包装成一个AuthorizeResult对象,
根据不同的Response Mode ,以 Query、Fragment和FormPost方式将 返回客户端。AuthorizeResult.ExecuteAsync没精力写了,就是 拿到code,id token 或者 token。
5、Token请求流程的TokenEndpoint 与 TokenResult
如果发起的是token请求流程,即 客户端请求 :http://localhost:5000/connect/token ,则 响应此请求的 EndpointHandler:为TokenEndpoint。代码如下
图七: TokenEndpoint的执行流程
先看TokenRequestValidator,不解释了,直接看代码:
图八: TokenRequestValidator的执行流程
一定要注意这里 是 Token Request了,不再是 Authorizate Request了,这里的GrantTypes.AuthorizationCode是指
以code换token,而不是要得到code。还有 客户端证书与密码授权模式,是不是看到这里就有豁然开朗的感觉了。
还有一点要注意的是,这里的RunValidationAsync方法也为我们引入了一个拦截 ValidatedTokenRequest 对象的数据的接口, ICustomTokenRequestValidator,这里也是一个定制的点,我们可以实现此接口来 修改客户端传过来的 各种参数。刚好昨天我们一个项目里使用了与RunValidationAsync类似的思路: 如果多个方法有公共的地方,例如这里要引入ICustomTokenRequestValidator来定制多个 类似方法的代码,只要这几个方法返回值和输入的参数相同,可以使用这里的方法,看来我的思路跟作者是一致的。
再来看TokenResponseGenerator,代码如下:
图九: TokenResponseGenerator 与 AuthorizeResponseGenerator的执行流程
代码比较直观,TokenResponseGenerator 是生成 TokenResponse ,而AuthorizeResponseGenerator是生成AuthorizeResponse。
TokenResponseGenerator : ITokenResponseGenerator
在Task<(string accessToken, string refreshToken)> CreateAccessTokenAsync(ValidatedTokenRequest request)方法中创建token和refresh token:
var token = await TokenService.CreateAccessTokenAsync(tokenRequest);
var jwt = await TokenService.CreateSecurityTokenAsync(token);
和
var refreshToken = await RefreshTokenService.CreateRefreshTokenAsync(tokenRequest.Subject, token, request.Client);
具体创建token的工作由DefaultTokenService : ITokenService完成。
标准的Claims由CreateIdentityTokenAsync(TokenCreationRequest request) 和 CreateAccessTokenAsync(TokenCreationRequest request)添加,而其他 Claims由DefaultClaimsService : IClaimsService捉刀.
再看看DefaultClaimsService : IClaimsService的两个关键方法:
Task<IEnumerable<Claim>> GetIdentityTokenClaimsAsync(ClaimsPrincipal subject, Resources resources, bool includeAllIdentityClaims, ValidatedRequest request)
Task<IEnumerable<Claim>> GetAccessTokenClaimsAsync(ClaimsPrincipal subject, Resources resources, ValidatedRequest request)。
生成 token,以及加入 claims这部分 写的比较乱, 有空再详细查看,完善。
在添加 client、scope、标准 claims之后,会调用await Profile.GetProfileDataAsync(context)以添加自定义的Claims.这就是如果要添加自定义claims必须注入自定义IProfileService的原因。
在网上资料中我们还会常常见到IResourceOwnerPasswordValidator。
Task<TokenRequestValidationResult> ValidateResourceOwnerCredentialRequestAsync(NameValueCollection parameters)
方法中调用了await _resourceOwnerValidator.ValidateAsync(resourceOwnerContext);以判断resourceOwnerContext.Result是否正确,且仅仅在 GrantTypes.Password情况下才会用到,具体要在TokenRequestValidator找。