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

     

  • 相关阅读:
    android 混淆代码 -- 报错:can't find referenced class
    adb shell 删除删除指定文件夹和文件
    php GD库
    javascript的继承实现
    Mysql
    如何使用canvas画星星
    基于形态编程设计类
    算法
    腾讯web前端一面
    如何去克服害怕
  • 原文地址:https://www.cnblogs.com/WebAssembly/p/IdentityServer4_study_note.html
Copyright © 2020-2023  润新知