Skip to main content

OpenID Connect介绍

分类:  Asp.net Core认证和授权 标签:  #Asp.Net core基础 #认证 #授权 #.Net 发布于: 2023-05-27 20:17:42

我们前面一篇学习了Qauth2.0,  那么openidconnect呢?

前面我们学些了Qauth2.0是一个授权的过程,同时授权的对象也没有兼顾到业务的逻辑,如果我们需要更复杂更精细的业务逻辑,那么在API这一侧还是要做很多的工作的,Qauth2.0有一个极大的缺陷导致了这个问题的发生,那就是第三方应用虽然拿到了Access Token 但是实际上它并不知道这个Access Token到底代表了什么,也就是用户只是给了一个黑盒给它。那么openidConnect作为Qauth 2.0的一个扩展,其实际代表的意思就是会将业务逻辑需要的ClaimPrinpcal 放入到其中,给Qauth 2.0添加了一个叫做Id token的返回,这个Id Token就是我们前面文章讲的JWT, 关于JWT, 你可以参考文章:

Json Web Token介绍

关于OpenId的参与角色,以及流程这些都是和Qauth2.0 大同小异的,关于Qauth 2.0您也可以参考如下的文章: 

OAuth2介绍

举一个简单的例子说明什么是OpenIdConnect:  身份认证(ClaimPrinpcal)+ Qauth 2.0 = OpenIdConnect

 

OpenIdConnect的流程

  • 简易流程

  • 授权码流程

  • 混合流程


OAuth2提供了AccessToken来解决授权第三方客户端访问受保护资源的问题;相似的,OIDC在这个基础上提供了ID Token来解决第三方客户端标识用户身份认证的问题。OIDC的核心在于在OAuth2的授权流程中,一并提供用户的身份认证信息(ID-Token)给到第三方客户端,ID-Token使用JWT格式来包装,得益于JWT(JSONWeb Token)的自包含性,紧凑性以及防篡改机制,使得ID-Token可以安全的传递给第三方客户端程序并且容易被验证。应有服务器,在验证ID-Token正确只有,使用Access-Token向UserInfo的接口换取用户的更多的信息。

由上述可知,OIDC是遵循OAuth协议流程,在申请Access-Token的同时,也返回了ID-Token来验证用户身份。

  1. 相关定义

  • EU:End User,用户。

  • RP:Relying Party     ,用来代指OAuth2中的受信任的客户端,身份认证和授权信息的消费方;

  • OP:OpenID     Provider,有能力提供EU身份认证的服务方(比如OAuth2中的授权服务),用来为RP提供EU的身份认证信息;

  • ID-Token:JWT格式的数据,包含EU身份认证的信息。

  • UserInfo     Endpoint:用户信息接口(受OAuth2保护),当RP使用ID-Token访问时,返回授权用户的信息,此接口必须使用HTTPS。

下面我们来看看OIDC的具体协议流程。

根据应用客户端的不同,OIDC的工作模式也应该是不同的。和OAuth类似,主要看是否客户端能保证client_secret的安全性。

如果是JS应用,其所有的代码都会被加载到浏览器而暴露出来,没有后端可以保证client_secret的安全性,则需要是使用默认模式流程(Implicit Flow)。

如果是传统的客户端应用,后端代码和用户是隔离的,能保证client_secret的不被泄露,就可以使用授权码模式流程(Authentication Flow)。

此外还有混合模式流程(Hybrid Flow),简而言之就是以上二者的融合。

OAuth2中还有口令模式和“应有访问模式”的方式来获取AccessToken(关于OAuth2的内容,可以参见OAuth2.0 协议入门指南),为什么OIDC没有扩展这些方式呢?

"口令模式"是需要用户提供账号和口令给RP的,既然都已经有用户名和口令了,就不需要在获取什么用户身份了。至于“应有访问模式”,这种方式不需要用户参与,也就无需要认证和获取用户身份了。这也能反映授权和认证的差异,以及只使用OAuth2来做身份认证的事情是远远不够的,也是不合适的。

  1.  授权码模式流程

授权码模式流程

和OAuth认证流程类似

  1. RP发送一个认证请求给OP,其中附带client_id;

  2. OP对EU进行身份认证;

  3. OP返回响应,发送授权码给RP;

  4. RP使用授权码向OP索要ID-Token和Access-Token,RP验证无误后返回给RP;

  5. RP使用Access-Token发送一个请求到UserInfo     EndPoint;    UserInfo EndPoint返回EU的Claims。

  1.  基于Authorization     Code的认证请求

RP使用OAuth2的Authorization-Code的方式来完成用户身份认证,所有的Token都是通过OP的TokenEndPoint(OAuth2中定义)来发放的。构建一个OIDC的AuthenticationRequest需要提供如下的参数:

  • scope:必须。OIDC的请求必须包含值为“openid”的scope的参数。

  • response_type:必选。同OAuth2。

  • client_id:必选。同OAuth2。

  • redirect_uri:必选。同OAuth2。

  • state:推荐。同OAuth2。防止CSRF,     XSRF。

示例如下:

GET/authorize?
    response_type=code
   &scope=openid%20profile%20email
    &client_id=s6BhdRkqt3
    &state=af0ifjsldkj
   &redirect_uri=https%3A%2F%2Fclient.example.org%2FcbHTTP/1.1
  Host: server.example.com

4 基于Authorization Code的认证请求的响应

在OP接收到认证请求之后,需要对请求参数做严格的验证,具体的规则参见http://openid.net/specs/openid-connect-core-1_0.html#AuthRequestValidation,验证通过后引导EU进行身份认证并且同意授权。在这一切都完成后,会重定向到RP指定的回调地址(redirect_uri),并且把code和state参数传递过去。比如:

  HTTP/1.1 302 Found
  Location: https://client.example.org/cb?
   code=SplxlOBeZQQYbYS6WxSbIA
    &state=af0ifjsldkj

  1. 获取ID Token

RP使用上一步获得的code来请求Token EndPoint,这一步同OAuth2,就不再展开细说了。然后TokenEndPoint会返回响应的Token,其中除了OAuth2规定的部分数据外,还会附加一个id_token的字段。id_token字段就是上面提到的ID Token。例如:

  HTTP/1.1 200 OK
  Content-Type:application/json
  Cache-Control: no-store
  Pragma: no-cache

{
   "access_token":"SlAV32hkKG",
   "token_type":"Bearer",
   "refresh_token":"8xLOxBtZp8",
   "expires_in":3600,
   "id_token":"eyJhbGciOiJSUzI1NiIsImtpZCI6IjFlOWdkazcifQ.ewogImlzc
    yI6ICJodHRwOi8vc2VydmVyLmV4YW1wbGUuY29tIiwKICJzdWIiOiAiMjQ4Mjg5
    NzYxMDAxIiwKICJhdWQiOiAiczZCaGRSa3F0MyIsCiAibm9uY2UiOiAibi0wUzZ
    fV3pBMk1qIiwKICJleHAiOiAxMzExMjgxOTcwLAogImlhdCI6IDEzMTEyODA5Nz
    AKfQ.ggW8hZ1EuVLuxNuuIJKX_V8a_OMXzR0EHR9R6jgdqrOOF4daGU96Sr_P6q
    Jp6IcmD3HP99Obi1PRs-cwh3LO-p146waJ8IhehcwL7F09JdijmBqkvPeB2T9CJ
    NqeGpe-gccMg4vfKjkM8FcGvnzZUN4_KSP0aAp1tOJ1zZwgjxqGByKHiOtX7Tpd
    QyHE5lcMiKPXfEIQILVq0pc_E2DzL7emopWoaoZTF_m0_N0YzFC6g6EJbOEoRoS
    K5hoDalrcvRYLSrQAZZKflyuVCyixEoV9GfNQC3_osjzw2PAithfubEEBLuVVk4
    XUVrWOLrLl0nx7RkKU8NXNHq-rvKMzqg"
  }

其中看起来一堆乱码的部分就是JWT格式的ID-Token。在RP拿到这些信息之后,需要对id_token以及access_token进行验证(具体的规则参见http://openid.net/specs/openid-connect-core-1_0.html#IDTokenValidation和http://openid.net/specs/openid-connect-core-1_0.html#ImplicitTokenValidation)。至此,可以说用户身份认证就可以完成了,后续可以根据UserInfoEndPoint获取更完整的信息。

  1.  安全令牌 ID-Token

上面提到过OIDC对OAuth2最主要的扩展就是提供了ID-Token。下面我们就来看看ID-Token的主要构成:

  • iss     = Issuer Identifier:必须。提供认证信息者的唯一标识。一般是Url的host+path部分;

  • sub     = Subject Identifier:必须。iss提供的EU的唯一标识;最长为255个ASCII个字符;

  • aud     = Audience(s):必须。标识ID-Token的受众。必须包含OAuth2的client_id;

  • exp     = Expiration time:必须。ID-Token的过期时间;

  • iat     = Issued At Time:必须。JWT的构建的时间。

  • auth_time     = AuthenticationTime:EU完成认证的时间。如果RP发送认证请求的时候携带max_age的参数,则此Claim是必须的。

  • nonce:RP发送请求的时候提供的随机字符串,用来减缓重放攻击,也可以来关联ID-Token和RP本身的Session信息。

  • acr     = Authentication Context Class Reference:可选。表示一个认证上下文引用值,可以用来标识认证上下文类。

  • amr     = Authentication Methods References:可选。表示一组认证方法。

  • azp     = Authorized party:可选。结合aud使用。只有在被认证的一方和受众(aud)不一致时才使用此值,一般情况下很少使用。

{
   "iss": "https://server.example.com",
   "sub":"24400320",
   "aud":"s6BhdRkqt3",
   "nonce": "n-0S6_WzA2Mj",
   "exp":1311281970,
   "iat":1311280970,
   "auth_time":1311280969,
   "acr":"urn:mace:incommon:iap:silver"
  }

另外IDToken必须使用JWT(JSONWeb Token)进行签名和JWE(JSONWeb Encryption)加密,从而提供认证的完整性、不可否认性以及可选的保密性。关于JWT的更多内容,请参看JSONWeb Token - 在Web应用间安全地传递信息

 

默认模式流程

默认模式流程

默认流程和OAuth中的类似,只不过也是添加了ID-Token的相关内容。

这里需要说明的是:OIDC的说明文档里很明确的说明了用户的相关信息都要使用JWT形式编码。在JWT中,不应该在载荷里面加入任何敏感的数据。如果传输的是用户的UserID。这个值实际上不是什么敏感内容,一般情况下被知道也是安全的。

但是现在工业界已经不推荐使用OAuth默认模式,而推荐使用不带client_Secret的授权码模式。

 

混合模式

混合模式简而言之就是以上提到的两种模式的混合,不过也有一些小的改变,就是允许直接向客户端返回Access-Token。

业界普遍认为,后端传递Token(比如服务器之间通信)要比前端(比如页面之间)可靠,所以如果直接返回令牌的情况下会把令牌的过期时间设置较短,但是比较

UserInfo Endpoint

可能有的读者发现了,ID-Token只有sub是和EU相关的,这在一般情况下是不够的,必须还需要EU的用户名,头像等其他的资料,OIDC提供了一组公共的cliams,来提供更多用户的信息,这就是——UserIndoEndPoin。

在RP得到AccessToken后可以请求此资源,然后获得一组EU相关的Claims,这些信息可以说是ID-Token的扩展,ID-Token中只需包含EU的唯一标识sub即可(避免IDToken过于庞大和暴露用户敏感信息),然后在通过此接口获取完整的EU的信息。此资源必须部署在TLS之上,例如:

  GET /userinfo HTTP/1.1
  Host: server.example.com
  Authorization: Bearer SlAV32hkKG

成功之后响应如下:

  HTTP/1.1 200 OK
  Content-Type: application/json

{
   "sub":"248289761001",
   "name": "JaneDoe",
   "given_name": "Jane",
   "family_name":"Doe",
   "preferred_username":"j.doe",
   "email":"janedoe@example.com",
   "picture": "http://example.com/janedoe/me.jpg"
  }

其中sub代表EU的唯一标识,这个claim是必须的,其他的都是可选的。