本人學習筆記,理解的可能不對,沒時間詳細整理,請前輩們指教。如果您看了覺得有收獲,我非常貼心地在右側提供了打賞入口 =====>(轉文末)。
有興趣的朋友可以 加群: 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找。


