IdentityServer4中的核心类


启动配置器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:同上
  • https://openid.net/specs/openid-connect-core-1_0.html#JWTRequests。RequestObjectValues就表示解析到的参数的值

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,然后返回

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

ValidatedAuthorizeRequest

内部逻辑如下:

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

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

AuthorizationRequest。在进入ids网页登陆时、在网页登陆中的第三方登陆回调时、在用户登陆授权确认时都会使用到此方法

它啥都不干,将任务直接委托给ReturnUrlParserIConsentMessageStore将一条用户拒绝授权的消息纪录下来,默认时以加密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响应

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模式的流程上去处理。

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

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

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

    基于Cookie的消息存取器MessageCookie

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

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

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

    ParsedSecret。

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

    ISecretParser的容器,它遍历内部的机密解析器ISecretParser,只要有一个找到了,则跳出遍历。

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

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

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

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

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

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

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

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

    IClientSecretValidator验证客户端
  • 使用请求验证器验证当前token请求
  • 创建token响应并返回
  • 未完待续...

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