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

 

原文地址:https://www.cnblogs.com/WebAssembly/p/IdentityServer4_study_note.html