zoukankan      html  css  js  c++  java
  • 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找。

     

  • 相关阅读:
    ubuntu 制做samba
    《Programming WPF》翻译 第4章 前言
    《Programming WPF》翻译 第4章 3.绑定到数据列表
    《Programming WPF》翻译 第4章 4.数据源
    《Programming WPF》翻译 第5章 6.触发器
    《Programming WPF》翻译 第4章 2.数据绑定
    《Programming WPF》翻译 第4章 1.不使用数据绑定
    《Programming WPF》翻译 第5章 7.控件模板
    《Programming WPF》翻译 第5章 8.我们进行到哪里了?
    《Programming WPF》翻译 第5章 5.数据模板和样式
  • 原文地址:https://www.cnblogs.com/WebAssembly/p/IdentityServer4_study_note.html
Copyright © 2011-2022 走看看