IdentityServer4學習筆記


本人學習筆記,理解的可能不對,沒時間詳細整理,請前輩們指教。如果您看了覺得有收獲,我非常貼心地在右側提供了打賞入口 =====>(轉文末)。

有興趣的朋友可以 加群: 169366609  ,一起探討。

1、 Endpoint 和 EndpointHandler 
Endpoint代表一個 url地址,EndpointHandler是這個 Endpoint對應的 處理器。當 請求這個url時,相應的EndpointHandler生成響應流。
具體實現思路是,EndpointHandler(或者說IEndpointHandler接口)的 ProcessAsync(感覺方法名應該叫ProcessRequestAsync)方法返回一個IEndpointResult對象,
IEndpointResult對象的ExecuteAsync方法執行時,向 響應上下文中寫入響應流,即context.Response.WriteXXXX
 
context.Response.WriteHtmlAsync(html);
context.Response.WriteHtmlAsync(GetFormPostHtml());
context.Response.WriteJsonAsync(ObjectSerializer.ToJObject(this.Entries));
context.Response.WriteJsonAsync(jobject);
context.Response.Redirect(BuildRedirectUri());
context.Response.RedirectToAbsoluteUrl(url);
 
IEndpointHandler接口:
Task<IEndpointResult> ProcessAsync(HttpContext context); // 請求相應url時的處理結果是得到一個 IEndpointResult對象
 
IEndpointResult接口:
Task ExecuteAsync(HttpContext context);
 
2、在 Startup 中 ConfigureServices 注冊所有的 Endpoint 和 EndpointHandler 
 
這些 EndpointHandler有
AuthorizeEndpoint、 
AuthorizeCallbackEndpoint、 
TokenEndpoint、 
DiscoveryEndpoint、 
CheckSessionEndpoint、
EndSessionEndpoint、
UserInfoEndpoint
 
EndpointRouter 實現 IEndpointRouter接口,該接口的 Find(HttpContext context)方法根據 url地址返回一個 IEndpointHandler對象。
是通過 AddDefaultEndpoints 方法注冊的這些 IEndpointHandler。
 
3、在 Startup 中 Configure方法中加入一個 IdentityServerMiddleware 中間件,用於處理 相關url請求。
 
IdentityServerMiddleware的Invoke方法中 根據請求上下文,利用EndpointRouter得到一個IEndpointHandler,調用其ProcessAsync得到一個IEndpointResult對象,
再調用IEndpointResult對象的ExecuteAsync方法生成響應流。
 
IEndpointResult對象有:
AuthorizeResult
CheckSessionResult
EndSessionResult
TokenResult
UserInfoResult
ConsentPageResult 
LoginPageResult
DiscoveryDocumentResult
StatusCodeResult
CustomRedirectResult

 看看下面這些很熟悉的 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); } }
放個AuthorizeEndpoint和TokenEndpoint的代碼截圖,直觀點。

 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 讀取客戶端傳過來的值,並驗證

 直接看上面的代碼,含義很清楚,不多解釋了。很不幸AuthorizeRequestValidator是internal類,這意味着你不能繼承他了,只能自己實現個IAuthorizeRequestValidator接口代替它,但AuthorizeRequestValidator里 內置了一個自定義驗證的擴展點,那就是 ICustomAuthorizeRequestValidator,我們可以自己實現個ICustomAuthorizeRequestValidator調試一下看看。從源碼可以看出,此擴展點的功能是 拿到客戶端傳過來的ValidatedAuthorizeRequest對象后,可以定制修改。
 

(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找。

 

 

 

 

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM