变形精怪

树有年轮,人有皱纹

导航

IdentityServer4中的核心类

Posted on 2020-08-23 16:19  变形精怪  阅读(1049)  评论(0编辑  收藏  举报

启动配置器IIdentityServerBuilder

可以把它理解为一个IServiceCollection的容器,它商品有几个扩展方法,方便我们用来注册ids使用到的相关服务,为啥不直接扩展IServiceCollection而是包一层呢?因为这样封装性更好,与ids相关服务注册方法都在IIdentityServerBuilder,不至于让IServiceCollection点出方法的时候太乱

ids相关服务注册体现在核心的扩展方法AddIdentityServer()上,主要任务如下:

AddRequiredPlatformServices:注册基础服务,不必深究

AddCookieAuthentication:单点登陆时用用到cookie身份验证方案

AddCoreServices:注册ids需要的核心的服务

AddDefaultEndpoints:ids重要的终结点都是在这里注册的

AddPluggableServices:注册插件服务

AddValidators:注册各种验证器

AddResponseGenerators:注册各种token构建器

AddDefaultSecretParsers:

AddDefaultSecretValidators

AddInMemoryPersistedGrants:

ids配置选项IdentityServerOptions

使用的.net core的选项模式,它包含基本的配置项,也包含其它服务关联的选项对象,因此我们可以在启动配置中通过这些选项来定制ids的某些功能

验证过的请求ValidatedRequest

它表示客户端向ids发起的一个请求,且这个请求是验证通过了的。,但它有时候只是一个初步的验证,比如在登陆获取code时它就没有验证客户端密钥。关于何时?如何?验证的在后面会说,先看看它的成员来感受下它

  • Raw:请求的原始参数,NameValueCollection
  • ClientId:客户端id
  • Client:验证时根据当前请求的client_id参数在配置中获取的客户端实体
  • Secret:它表示客户端的密钥,在ids4网页登陆在获取code的步骤中是不需要密钥的,ids只要能识别是哪各客户端就行,只有在后续获取AccessToken时才需要密钥
  • AccessTokenLifetime:在ids配置客户端时可以设置AccessToken的有效时长,在发放accessToken前也可以修改这个值
  • ClientClaims:同上,若是用户使用此客户端登陆时在需要在accessToken中包含哪些cliaim,也是在ids中配置客户端设置的,当然在发放accessToken前也可以改。资源服务器可以根据解析得到的Claim进行更灵活的权限检查
  • AccessTokenType:accessToken分为引用token和jwttoken,这个也是在ids配置客户端时配置的,可以在发放accessToken前修改
  • Subject:代表当前用户
  • SessionId:一次授权会产生一个唯一id,idtoken和accessToken的claim中都会保存这个sessionid
  • Options:这个是ids4在启动时配置的选项对象的引用
  • ValidatedResources:本次请求且验证同的资源的列表
  • Confirmation:客户端密钥验证时产生的一个值
  • SetClient(Client client, ParsedSecret secret = null, string confirmation = ""):来设置客户端,从查找引用可以看出AuthorizeRequestValidator.ValidateAsync时没有设置验证后的密钥

验证过的授权请求ValidatedAuthorizeRequest

它表示客户端向ids发起的一个验证过的授权请求,它继承ValidatedRequest。所以它也表示一个ids中验证过的请求,只不过是一个发起授权的请求

  • ResponseType:本次授权请求希望返回的数据类型,可选值定义在OidcConstants.ResponseTypes常量中,有:Code、Token、IdToken、IdTokenToken、CodeIdToken、CodeToken、CodeIdTokenToken。使用验证码模式发起授权请求时响应类型就是Code
  • ResponseMode:集成ids4网页登陆时,当向客户端返回code时以什么样的方式,默认是动态构建一个Form表单Post到客户端的回调地址。还可以设置为QueryString方式。后续客户端回用code再请求ids换取accessToken和idToken
  • GrantType:对于ids的4中授权模式,在GrantType常量中定义,AuthorizationCode、Implicit、Hybrid,客户端凭据和资源所有者密码模式中授权流程比较简单,不需要ids服务器与客户端多次交互,所以不需要这里授权码模式
  • RedirectUri:当授权流程完成后需要跳转回客户端指定的地址
  • RequestedScopes:客户端请求时指定的希望请求的scope列表
  • WasConsentShown:在用户登陆时是否需要用户对客户端请求的scope进行确认
  • State:OAuth2中规定的state,客户端发起请求时携带此参数,返回code时原样携带回客户端的回调地址,起到一个参数传递作用
  • IsOpenIdRequest:只有请求的scope中包含openid就为true,某些授权流程验证时需要使用到此参数
  • IsApiResourceRequest:只有请求的scope中包含api资源的scope则为true
  • Nonce:OAuth2中规定的随机数,会参与到token的签名和验证
  • AuthenticationContextReferenceClasses:客户端请求时可以携带一个acr_values参数,值的格式为:idp:qq bb:sdf。acr是AuthenticationContextReferenceClasses的缩写,idp是identityProvider的缩写,表示本次请求希望使用什么身份验证方式,可以是local,表示用ids的本地用户登陆,也可以让ids使用第三方登陆。acr值可以用空格分隔多个。
  • PromptModes:不太懂,提示模式,提示用户登陆、提示确认授权范围
  • LoginHint:客户端跳转到ids登录页时可以指定一个默认的登陆用户名
  • CodeChallenge:参考:https://www.dazhuanlan.com/2019/12/24/5e01e8e3c136f/
  • CodeChallengeMethod:同上
  • RequestObjectValues:客户端发起授权请求时通常以queryString形式传递参数,但oidc协议还规定了可以通过jwt格式传递参数,参考:https://openid.net/specs/openid-connect-core-1_0.html#JWTRequests。RequestObjectValues就表示解析到的参数的值

授权请求验证器AuthorizeRequestValidator

它表示对客户端的授权请求的验证器,主要在以下场景被使用到

  1. 在客户端集成ids的网页登陆时,当跳转到ids的登陆页面get请求以及录入账号密码发起post请求做登陆时
  2. 在集成网页登陆ids做完身份验证后还会跳转到自己的callback地址(终结点),也就是请求AuthorizeCallbackEndpoint
  3. 在不是集成ids网页登陆,而是直接像请求AuthorizeEndpoint获取临时code时

其中1、2是集成网页登陆时会经历的两个步骤,核心的验证方法的签名是这样的

Task<AuthorizeRequestValidationResult> ValidateAsync(NameValueCollection parameters, ClaimsPrincipal subject = null)

验证的返回类型是AuthorizeRequestValidationResult,它只是对ValidatedAuthorizeRequest的一个包装。

所以对它的理解就两点:返回类型是验证过的授权请求ValidatedAuthorizeRequest,验证过程是怎么样的

它的主要验证内容如下:

  1. 根据请求参数中的client_id去ids的配置中找到Client实体并赋值给ValidatedAuthorizeRequest,赋值时与client实体相关的配置信息也会设置到返回值上。值得注意的是这里并没有去验证客户端密钥
  2. 加载和验证request object,参考:RequestObjectValues,这个可以忽略
  3. 验证RedirectUri
  4. 验证state, response_type, response_mode,主要就是看是否存在,是否是ids配置支持的,以及根据授权模式验证某些参数是否符合逻辑
  5. 验证scope,这里结合ids的配置对scope、客户端、资源进行验证
  6. 验证辅助参数:nonce, prompt, acr_values, login_hint etc
  7. 最后是执行我们自定义的验证器
  8. 最后验证好的结果会设置到ValidatedAuthorizeRequest,然后返回

这里没有详细分析每一步验证,但是我们可以有个大概印象,将来需要扩展或哪里不懂时只是设计到授权验证时能想到去看这的源码

授权参数存取器IAuthorizationParametersMessageStore

在ids执行授权过程中涉及到很多参数,比如cliaentid、securet、scope等,这些参数在需要在授权的多个步骤中传递,ids为此提供了一个存取器。

具体来说当mvc客户端因为没有登陆跳转到ids4的登陆页面时会携带一堆参数,当用户输入完账号密码登陆成功后会使用这个接口存储跟授权相关的参数,然后回调ids4的.../auth../callback去做后续生成code的工作,在这个回调中会从querystring中拿到之前存储授权参数生成的唯一key,通过这个key调用IAuthorizationParametersMessageStore拿到之前的完整的授权相关参数

默认实现有两个:QueryStringAuthorizationParametersMessageStore(直接存储在地址栏参数QueryString里)、DistributedCacheAuthorizationParametersMessageStore(使用分布式缓存接口IDistributedCache进行存取)。

ids中默认没有在ioc容器中注册任何授权参数的存取器

ReturnUrl转换器IReturnUrlParser

当ids授权检查时并不是直接从queryString中获取客户端提交的参数,而时从QueryString中的ReturnUrl参数中获取客户端id、scope等参数的。在集成ids网页登陆过程中,当发起"/authorize"或“/callback”请求时,从ReturnUrl参数中获取授权请求的参数,然后将其转换为表示授权请求对象的AuthorizationRequest。先看看方法签名

Task<AuthorizationRequest> ParseAsync(string returnUrl)

参数returnUrl就是queryString中的同名参数,这个参数里面包含授权请求的核心参数(如:clientid、scope、redirect_uri等),是客户端通过ids的客户端库自动构建的。AuthorizationRequest可以等效于ValidatedAuthorizeRequest

内部逻辑如下:

  1. 判断是否是请求的“/authorize”或“/callback”,若是则继续,否则返回null
  2. 将参数转换为NameValueCollection的形式
  3. 若是callback请求,尝试通过参数存取器AuthorizationParametersMessageStore获取前一步步骤存储下来的授权参数
  4. 尝试获取当前用户,若是“/authorize”就是匿名用户,若是“/callback”就是前一步骤(“/authorize”)身份验证正规的用户,默认是基于cookie的
  5. 最后调用授权请求验证器AuthorizeRequestValidator进行验证,得到ValidatedAuthorizeRequest,并将其转换为AuthorizationRequest进行返回

ReturnUrl转换器容器ReturnUrlParser

它是IReturnUrlParser的容器,它的设计思路类似mvc中路由和路由容器的。当需要将returnUrl参数转换为表示当前授权的请求AuthorizationRequest时,它遍历内部的ReturnUrlParser,然后调用它进行转换,只有有一个转换成功,则跳出循环并返回结果

授权交互响应构建器IAuthorizeInteractionResponseGenerator

它用来确保请求用户必须是经过身份验证的,或授权确认哪些scope了的。定义如下:

Task<InteractionResponse> ProcessInteractionAsync(ValidatedAuthorizeRequest request, ConsentResponse consent = null)

request:验证过的授权请求参数,里面包含当前用户

consent:用户的授权确认消息

此接口在AuthorizeEndpointBase.ProcessAuthorizeRequestAsync被用到,所以ids中所有端点(终结点)都会来做这步操作。默认实现是AuthorizeInteractionResponseGenerator。核心逻辑如下:

检查用户是否需要登陆:

  1. 若请求参数中存在PromptModes包含“Login”或“SelectAccount”,则直接返回说用户需要登陆。此时会删除这个参数,这样下次执行到这里时就不会再提示了。
  2. 在用户是已登陆时,通过IProfileService来设置用户的激活状态,所以这里给我留了个扩展点。默认是直接设置未已激活
  3. 若是匿名用户或用户未激活,则直接返回说用户需要登陆。
  4. 授权请求参数中可能存在一个客户端指定的idp(IdentityProvider),而用户信息的IdentityProvider表示当前用户当初是使用哪个身份验证方案做的验证,这里会做个对比,若客户端发起授权请求的参数希望使用的身份验证方案 与  当前用户当初做身份验证的那个方案不匹配,则直接返回说用户需要登陆。
  5. 请求中可能存在一个MaxAge参数,它表示用户的过期时长,单位秒。若授权请求的参数中有这个值,则会与当前用户的GetAuthenticationTime()对比,若过期了,则直接返回说用户需要登陆。
  6. 若用户当初身份验证用的那个方案就是使用的ids的本地身份验证方案,但此时发现当前客户端没有开启本地身份验证方案,则直接返回说用户需要登陆。
  7. 若客户端配置了IdentityProviderRestrictions,则表示此客户端只允许这几种身份验证方案,所以看看用户当初使用的身份验证方案在其中没有,若没有,则直接返回说用户需要登陆。
  8. 若客户端配置了UserSsoLifetime,它表示客户端配置的用户最大有效时长,单位秒,当前用户登陆是否过期了,若过期了,则直接返回说用户需要登陆。这跟步骤5不同,那里是当前请求参数的MaxAge,而这里是客户端配置中的用户有效时长

若上面检查用户需要登陆,就直接返回了,否则继续根据用户授权确认消息继续判断用户是否需要重新登陆,逻辑如下:

检查用户授权确认信息:

首先确认是否必须要求用户来确认授权范围,具体怎么确认的后面再补充,只要知道在特定条件下是必须要用户来确认授权范围的

然后是看授权请求的参数中是否包含PromptModes提示模式,且里面包含Consent,此时也表示必须要用户来确认授权范围

当以上任意条件满足时才检查用户确认授权的信息,否则不检查,直接认为符合要求,进行返回,下面看看若满足任意条件时如何检查的

  1. 若用户授权确认消息为空,则直接返回说需要用户授权确认
  2. 若用户在之前说拒绝授权,返回错误信息
  3. 若用户确认过授权范围,这拿到用户允许的scope的列表
  4. 特定情况下,有些scope是必须允许的,此时看看用户是否都允许了这些scope,若没有的话,返回错误信息
  5. 此时说明一切ok,结合ids配置里的Resource、scope、客户端请求的scope列表、用户允许的scope列表 最终形成一个本次授权允许的资源及scope,这个结果会设置到当前授权请求的ValidatedResources属性上
  6. 若客户端配置了AllowRememberConsent,说明此客户端允许保存此用户的授权范围保存下来。还要检查用户点击确认授权是否勾选了记住授权,若都满足则通过IConsentService将用户对此客户端的授权确认信息存储下来,下次授权可以直接用了。

交互服务IIdentityServerInteractionService

集成ids提供的网页登陆、第三方登陆、授权确认页面等涉及到用户与ids进行交互,这个交互过程用到的一些功能就封装在IIdentityServerInteractionService接口中,主要用在ids处理网页登陆的AccountCtroller中。默认实现:DefaultIdentityServerInteractionService,下面逐一描述每个方法

Task<AuthorizationRequest> GetAuthorizationContextAsync(string returnUrl)

在授权请求时参数中有个returnUrl,它里面携带了本次请求的重要参数(client_id、scope、等),此方法就是将returnUrl参数转换为表示当前授权请求的AuthorizationRequest。在进入ids网页登陆时、在网页登陆中的第三方登陆回调时、在用户登陆授权确认时都会使用到此方法

它啥都不干,将任务直接委托给ReturnUrlParser

DenyAuthorizationAsync

用户拒绝授权,核心逻辑就是使用授权消息存储器IConsentMessageStore将一条用户拒绝授权的消息纪录下来,默认时以加密cookie的形式存储的。

集成网页登陆后,处理了DenyAuthorizationAsync会继续执行ids的 callback终结点 “idsServer/connect/authenzation/callback”

 

目前只研究到登陆部分,注销、授权确认等操作会涉及到此接口的其它方法,将在后续补充说明

IdentityServer的终结点Endpoint

比如发起AuthenrozationCode流程时要向https://ids-server-domain/authenrozation发起请求,以获取临时code,后续的步骤还会访问https://ids-server-domain/token通过临时code换取token,在ids各种授权模式中还有好几个这样的地址,它们被定义为终结点(其实wcf、asp.net core现在的路由里 向这种一个被请求的地址都叫终结点,所以提到终结点你应该想到就是一个被请求的地址)

在ids中Endpoint = url + Handler(处理器类型),将来请求抵达时url用来匹配判断当前请求是否与这个终结点匹配;Handler则被用来处理本次请求

终结点在ids服务器启动阶段配置(毕竟ids就那几个固定的终结点嘛)。在请求抵达时匹配找到当前请求对应的终结点,根据终结点的Handler类型从容器中取出对应的IEndpointHandler,请求就由它处理,这些EndpointHandler当然是在AddIdentityServer中注册的

IdentityServer的终结点EndpointRouter

请求抵达时,根据当前请求地址找到Endpoint的任务就是EndpointRouter来完成的,不过它多做了一步,找到当前Endpoint后直接根据Handler类型从容器中取出对应的IEndpointHandler

EndpointRouter也是在AddIdentityServer注册的

拦截终结点请求的中间件IdentityServerMiddleware

比如此时有个客户端使用授权码模式向ids服务器的https://ids-server-domain/authenrozation发起请求,在asp.net core中肯定是有个中间件来拦截这个请求,这个中间件调用上面的EndpointRouter,如果找到一个匹配的IEndpointHandler,就让它直接处理本次请求,否则啥都不干执行下一个中间件。IdentityServerMiddleware就是这个拦截ids相关请求的中间件

这个中间件是在UseIdentityServer中注册的

它指向完EndpointHandler后返回一个Result,这个Result在中间件中被执行,以TokenResult为例,就是将token输出json响应

授权响应生成器IAuthorizeResponseGenerator

无论是多种授权模式中的请求的响应,还是单种授权模式中的多个步骤的响应,几乎都是通过此接口来完成的,比如授权码模式响应code时、隐式模式Implicit中返回accessToken/idToeken等。所以它的作用是根据  当前已验证过的授权请求对象ValidatedAuthorizeRequest,创建一个表示响应的AuthorizeResponse对象,此对象中可能包含code、accessToken、等,ids中统一用这个类型表示所有的授权响应,而不是为每种授权情形单独定义。

先看看AuthorizeResponse对象的定义

 1 public class AuthorizeResponse
 2 {
 3     public ValidatedAuthorizeRequest Request { get; set; }
 4     public string RedirectUri => Request?.RedirectUri;
 5     public string State => Request?.State;
 6     public string Scope => Request?.ValidatedResources?.RawScopeValues.ToSpaceSeparatedString();
 7 
 8     public string IdentityToken { get; set; }
 9     public string AccessToken { get; set; }
10     public int AccessTokenLifetime { get; set; }
11     public string Code { get; set; }
12     public string SessionState { get; set; }13 }

创建授权码响应核心逻辑如下:

  1. 创建一个表示授权码的实体对象AuthorizationCode
    CreationTime    //创建时间
    ClientId         //客户端id
    Lifetime         //有效期,单位秒
    Subject          //当前登陆的用户
    SessionId        //一次授权应该是产生一个唯一的sessionid
    Description      //描述
    CodeChallenge    //是个啥签名
    CodeChallengeMethod   //上面签名的签名算法,目前还不晓得在哪用,不重要,应该就是验证此Code是否被串改过
    IsOpenId              //若授权的scope里包含 openId这个scope 则为true
    RequestedScopes       //本次授权请求最终能访问的scope集合
    RedirectUri          //所有授权流程结束后用户将跳转到这个地址
    Nonce                  //随机字符串,客户端发起授权请求提供的那个随机字符串餐宿
    StateHash              //客户端发起授权请求时提供的状态参数的hash值,因为这个参数是原样返回给客户端的,所以价格hash签名,确保此状态数据没有变动过
    WasConsentShown        //是否要求用户确认授权    

    这些数据都是直接获取间接从表示当前授权请求的对象中来的,而这个对象是经过验证过的,最终可以作为响应的数据源的

  2. 通过授权码存取器IAuthorizationCodeStore将AuthorizationCode存储到服务端,并参数一个字符串的唯一id
  3. 组织授权响应,返回
    var response = new AuthorizeResponse
    {
        Request = request,
        Code = id,
        SessionState = request.GenerateSessionStateValue()
    };

暂时就不说其它流程了,以后补充吧。不过值得注意的是里面并没有单独针对accessToken创建的流程。而隐式模式中包含accessToken和idToken的创建流程,因此猜测请求token时可能会映射到隐式Implicit模式的流程上去处理。

授权结果AuthorizeResult

这个设计类似mvc里IActionResult和各种Result的设计,ids中有个IEndpointResult接口,它表示ids中的终结点处理后的结果,ids中各种处理在响应前都会生成一个实现此接口的XXResult对象,在ids中间件中会来执行此结果,最终完成对请求的响应。

AuthorizeResult实现了IEndpointResult接口,它表示执行授权操作的结果,典型的就是ids的callbackEndpoint的处理结果,此终结点主要用来生成code。此终结点的最后会生成一个表示授权响应的AuthorizeResponse,它最终被转换为AuthorizeResult。默认情况下主要的的逻辑是携带code,跳转到客户端的回调地址“xxxmvc/oidc-callback”,核心步骤如下:

  1. 通过ids提供的IUserSession将当前客户端id存储下来,便于后续做注销处理。本质上是将客户端id存储到加密cookie的用户票证里的。
  2. 由于默认回调客户端是使用post方式,所以这里会动态创建一个form表单进行响应,内部有js,响应时直接提交,实现post跳转到客户端的回调地址。

终结点抽象类AuthorizeEndpointBase

所有终结点都集成至它。方法签名如下:

Task<IEndpointResult> ProcessAuthorizeRequestAsync(NameValueCollection parameters, ClaimsPrincipal user, ConsentResponse consent)

它主要做了3件事

  1. 使用授权请求验证器IAuthorizeRequestValidator对参数进行验证,得到一个验证通过的授权请求对象AuthorizeRequestValidationResult
  2. 验证用户对授权的确认信息(如果有的画)
  3. 根据授权请求、授权模式创建一个存储需要响应的数据对象并返回(参考

授权回调端点AuthorizeCallbackEndpoint

在集成ids网页登陆时,用来发放code

  1. 准备请求参数
  2. 从前一步骤的身份验证中获取当前登陆用户
  3. 调用父类的ProcessAuthorizeRequestAsync执行code的发放,会携带code跳转到客户端指定的回调页面(/oidc-callback)

基于Cookie的消息存取器MessageCookie<TModel>

在ids4运行过程中可能需要在用户cookie里存取一些数据,此对象就是来做这个工作的。比如使用ids4的登陆时,如启用授权确认,则相关消息将间接使用这个对象来存取消息。

泛型TModel表示这个消息类型,写入时:以 消息类型Id.消息Id的格式作为cookie名,将TModel序列号为json,然后进行加密作为cookie值,读取时:反过来。

IdentityServerOptions.UserInteraction.CookieMessageThreshold控制消息的数量,若写入cookie消息的数量大于这个值 则会删除早期的cookie消息。

登陆授权确认时的消息的存取器ConsentMessageStore

在使用ids4提供的登陆功能时可以开启登陆时由用户确认授权,此时主要可以让用户选择授权哪些scope,用户做的这个选择需要纪录下来,以便ids的后续步骤使用,针对用户授权确认的消息的存取、删除功能就由ConsentMessageStore来完成。它内部使用上面说的MessageCookie<TModel>来实现的。被存取的消息里最重要的就是用户允许的scope的列表

Token

一个实体类,定义在IdentityServer4.Model中的

  • AllowedSigningAlgorithms:允许的签名算法列表
  • Audiences此token:一个字符串列表,指名这个token可以给谁用。对应到与在webApi配置jwtToken身份验证时配置的那个字符串(并开启aud验证),这样webApi服务器在验证token时看自己并不在这个列表中,则认为验证token也会失败(当然token验证还有其它几个步骤)
  • Issuer:此token是谁发放的,一般就是ids4
  • CreationTime:创建时间
  • Type:一个字符串,一般是IdentityToken、AccessToken,可能还有其它类型的吧
  • ClientId:客户端id
  • AccessTokenType:如果是AccessToken,则可能是jwt或Reference
  • Claims:token内部包含的cliaim,webApi验证token后可以从中取得这些cliam,可以在配置ids4的scope时指定生成token时里面方哪些claim,比如用户的“所属部门”,这样webApi服务器这边可以拿到当前登陆用户的claim,结合asp.net core的基于策略的授权可以做灵活的权限控制
  • SubjectId:用户唯一Id,从Claims去的SubectId
  • SessionId:ids4做身份验证后会生成一个sessionId,唯一id,多次身份验证生成sessionId不一样,可以理解为它表示一次身份验证
  • Scopes:客户端允许的scope、客户端请求的、用户授权确认时允许的 三者的交集,最终表示此token能访问的scope

解析后密钥ParsedSecret

简单点理解它就是 Id + 密码 + 机密类型 + 附加数据。比如:客户端client_id + 客户端密钥client_securet + ParsedSecretTypes.SharedSecret

此对象目前我晓得的在客户端哪code时 需要携带客户端密钥,此时会用到这个对象,ids会从客户端请求过来的客户端id,客户端密钥来创建此对象。具体怎么用的后续文章会分析。

先看看它的定义

1 public class ParsedSecret
2 {
3     public string Id { get; set; }          //此机密对象的唯一id
4     public object Credential { get; set; }  //凭据对象,简单的情况下可能是个字符串形式的密码,复杂的可能是个证书
5     public string Type { get; set; }        //机密类型,任意字符串,但模式是常量中定义的
6     public Dictionary<string, string> Properties { get; set; } = new Dictionary<string, string>(); //可以用它存点附加数据
7 }

这里的机密类型是对机密的一种分类,不好描述,它以常量的形式定义,感受下吧

1 public static class ParsedSecretTypes
2 {
3         public const string NoSecret = "NoSecret";
4         public const string SharedSecret = "SharedSecret";
5         public const string X509Certificate = "X509Certificate";
6         public const string JwtBearer = "urn:ietf:params:oauth:client-assertion-type:jwt-bearer";
7 }

密钥解析器ISecretParser

它的任务是从当前Http请求解析得到解析后的机密ParsedSecret

比如在客户端携带code 客户端密钥 之类的 找ids请求token时,默认是通过post一个表达向ids发起请求的,就有个PostBodySecretParser的实现类,它从请求的表单中拿到客户端id和密钥,然后创建一个解析后的机密对象ParsedSecret,为后续验证客户端做准备。

密钥解析器容器ISecretsListParser

又是这种经典的设计,它是ISecretParser的容器,它遍历内部的机密解析器ISecretParser,只要有一个找到了,则跳出遍历。

密钥验证器ISecretValidator

它根据ids中配置的密钥列表和当前请求提供的密钥做验证,接口定义如下:

Task<SecretValidationResult> ValidateAsync(IEnumerable<Secret> secrets, ParsedSecret parsedSecret);

secrets:从ids配置(可能在数据库)中拿到密钥配置。如:某个客户端配置的密钥列表

parsedSecret:当前请求中得到的密钥信息

SecretValidationResult:返回值,里面包含一个Confirmation的字符串,不晓得是啥,反正是验证后得到的一个字符串,会存储到名为cnf的claim中

此接口有好几个实现类,这里是理解为主,因此我们分析下其中的HashedSharedSecretValidator,核心逻辑如下:

  1. 过滤secrets参数,只获取类型为IdentityServerConstants.SecretTypes.SharedSecret的密钥
  2. 对parsedSecuret做hash256和512计算
  3. 遍历过滤后的过滤secrets,与parsedSecuret的hash值做比对,一旦匹配到成功则跳出循环,直接返回结果

此实现类没有设置返回值的Confirmation属性

猜想密钥材料主要是用来签名,而这里的密钥验证是对client密钥进行验证,apiResource好像也可以单独设置密钥

密钥验证器容器ISecretsListValidator

又是这种经典设计,它是密钥验证器的容器,当验证密钥时遍历内部的验证器进行验证,只要有一个成功则跳出遍历后返回

客户端密钥验证器IClientSecretValidator

接口名很贴切,它使用密钥验证器ISecretValidator对客户端进行密钥验证,如:携带客户端id 密码 code 向ids请求token时,ids会通过它来验证客户密钥。核心流程如下:

  1. 使用密钥解析器ISecretsListParser从当前请求获取密码
  2. 通过客户端存取器(可能是数据库或硬编码配置)获取客户端实体
  3. 若客户端配置RequireClientSecret不是必须的,或者client.IsImplicitOnly()则认为直接验证通过
  4. 否则使用密钥验证器ISecretsListValidator进行验证,注意:若验证器有返回Confirmation则设置到返回结果的同名属性上

token请求验证器ITokenRequestValidator

客户端携带客户端id 密码 和其它参数向ids请求token时会使用此接口验证此请求,核心逻辑如下:

  1. 将客户端、密钥、和密钥验证结果中的Confirmation设置到当前token请求对象ValidatedTokenRequest上
  2. 检查客户端的协议类型是否是 IdentityServerConstants.ProtocolTypes.OpenIdConnect
  3. 检查请求参数中是否包含了授权类型字段
  4. 检查授权类型的字符串长度是否超过ids选项对象的_options.InputLengthRestrictions.GrantType
  5. 将授权类型设置到当前token请求对象ValidatedTokenRequest上
  6. 最后根据授权类型做不同的验证
     1 switch (grantType)
     2             {
     3                 case OidcConstants.GrantTypes.AuthorizationCode:
     4                     return await RunValidationAsync(ValidateAuthorizationCodeRequestAsync, parameters);
     5                 case OidcConstants.GrantTypes.ClientCredentials:
     6                     return await RunValidationAsync(ValidateClientCredentialsRequestAsync, parameters);
     7                 case OidcConstants.GrantTypes.Password:
     8                     return await RunValidationAsync(ValidateResourceOwnerCredentialRequestAsync, parameters);
     9                 case OidcConstants.GrantTypes.RefreshToken:
    10                     return await RunValidationAsync(ValidateRefreshTokenRequestAsync, parameters);
    11                 case OidcConstants.GrantTypes.DeviceCode:
    12                     return await RunValidationAsync(ValidateDeviceCodeRequestAsync, parameters);
    13                 default:
    14                     return await RunValidationAsync(ValidateExtensionGrantRequestAsync, parameters);
    15             }

     

token端点ToekenEndpoint

  1. 使用客户端密钥验证器IClientSecretValidator验证客户端
  2. 使用请求验证器验证当前token请求
  3. 创建token响应并返回

 

未完待续...

目前只在研究登陆部分,涉及到的核心类在其它流程中也可能被使用到,后续研究其它流程中涉及到的核心类的说明也会在这里补充...