Freud's Blog

Stay hungry, stay foolish. 少年辛苦终身事,莫向光阴惰寸功。

权限系列 - 02 - Open-ID-Connect(OIDC)

Posted on By Freud Kang

1. 什么是OIDC

OIDC是OpenID Connect的简称,OIDC=(Identity, Authentication) + OAuth 2.0。它在OAuth2上构建了一个身份层,是一个基于OAuth2协议的身份认证标准协议

2. OIDC 协议族

3. OIDC 核心概念

  • OIDC 主要术语
    • EU:End User:用户。
    • RP:Relying Party ,用来代指OAuth2中的受信任的客户端,身份认证和授权信息的消费方;
    • OP:OpenID Provider,有能力提供EU认证的服务(比如OAuth2中的授权服务),用来为RP提供EU的身份认证信息;
    • ID Token:JWT格式的数据,包含EU身份认证的信息。
    • UserInfo Endpoint:用户信息接口(受OAuth2保护),当RP使用Access Token访问时,返回授权用户的信息,此接口必须使用HTTPS。
  • OIDC 工作流程
    • RP发送一个认证请求给OP;
    • OP对EU进行身份认证,然后提供授权;
    • OP把ID Token和Access Token(需要的话)返回给RP;
    • RP使用Access Token发送一个请求UserInfo EndPoint;
    • UserInfo EndPoint返回EU的Claims。 image
  • ID Token: OIDC对OAuth2最主要的扩展就是提供了ID Token。ID Token是一个安全令牌,是一个授权服务器提供的包含用户信息(由一组Cliams构成以及其他辅助的Cliams)的JWT格式的数据结构。ID Token的主要构成部分如下(使用OAuth2流程的OIDC)。
    • iss = Issuer Identifier:必须。提供认证信息者的唯一标识。一般是一个https的url(不包含querystring和fragment部分)。
    • sub = Subject Identifier:必须。iss提供的EU的标识,在iss范围内唯一。它会被RP用来标识唯一的用户。最长为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发送AuthN请求的时候携带max_age的参数,则此Claim是必须的。
    • nonce:RP发送请求的时候提供的随机字符串,用来减缓重放攻击,也可以来关联ID Token和RP本身的Session信息。
    • acr = Authentication Context Class Reference:可选。表示一个认证上下文引用值,可以用来标识认证上下文类。
    • amr = Authentication Methods References:可选。表示一组认证方法。
    • azp = Authorized party:可选。结合aud使用。只有在被认证的一方和受众(aud)不一致时才使用此值,一般情况下很少使用。
    • ID Token通常情况下还会包含其他的Claims(毕竟上述claim中只有sub是和EU相关的,这在一般情况下是不够的,必须还需要EU的用户名,头像等其他的资料,OIDC提供了一组公共的cliams,请移步这里http://openid.net/specs/openid-connect-core-1_0.html#StandardClaims)。另外ID Token必须使用JWS进行签名和JWE加密,从而提供认证的完整性、不可否认性以及可选的保密性。
{
"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"
}
  • 认证 因为OIDC基于OAuth2,所以OIDC的认证流程主要是由OAuth2的几种授权流程延伸而来的,有以下3种:
    • Authorization Code Flow:使用OAuth2的授权码来换取Id Token和Access Token。
    • Implicit Flow:使用OAuth2的Implicit流程获取Id Token和Access Token。
    • Hybrid Flow:混合Authorization Code Flow+Implici Flow。

OAuth2中还有基于Resource Owner Password Credentials Grant和Client Credentials Grant的方式来获取Access Token,为什么OIDC没有扩展这些方式呢?

  • Resource Owner Password Credentials Grant是需要用途提供账号密码给RP的,账号密码给到RP了,还要什么自行车(ID Token)。。。
  • Client Credentials Grant这种方式根本就不需要用户参与,更谈不上用户身份认证了。这也能反映授权和认证的差异,以及只使用OAuth2来做身份认证的事情是远远不够的,也是不合适的。

4. OIDC基于Authorization Code认证请求的基本流程

4.1 OIDC基于Authorization Code的认证请求

这种方式使用OAuth2的Authorization Code的方式来完成用户身份认证,所有的Token都是通过Token EndPoint(OAuth2中定义:https://tools.ietf.org/html/rfc6749#section-3.2)来发放的。构建一个OIDC的Authentication Request需要提供如下的参数:

  • scope:必须。OIDC的请求必须包含值为“openid”的scope的参数。
  • response_type:必选。同OAuth2。
  • client_id:必选。同OAuth2。
  • redirect_uri:必选。同OAuth2。
  • state:推荐。同OAuth2。防止CSRF, XSRF。

以上这5个参数是和OAuth2相同的。除此之外,还定义了如下的参数:

  • response_mode:可选。OIDC新定义的参数(OAuth 2.0 Form Post Response Mode),用来指定Authorization Endpoint以何种方式返回数据。
  • nonce:可选。ID Token中的出现的nonce就是来源于此。
  • display : 可选。指示授权服务器呈现怎样的界面给EU。有效值有(page,popup,touch,wap),其中默认是page。page=普通的页面,popup=弹出框,touch=支持触控的页面,wap=移动端页面。
  • prompt:可选。这个参数允许传递多个值,使用空格分隔。用来指示授权服务器是否引导EU重新认证和同意授权(consent,就是EU完成身份认证后的确认同意授权的页面)。有效值有(none,login,consent,select_account)。none=不实现现任何认证和确认同意授权的页面,如果没有认证授权过,则返回错误login_required或interaction_required。login=重新引导EU进行身份认证,即使已经登录。consent=重新引导EU确认同意授权。select_account=假如EU在授权服务器有多个账号的话,允许EU选择一个账号进行认证。
  • max_age:可选。代表EU认证信息的有效时间,对应ID Token中auth_time的claim。比如设定是20分钟,则超过了时间,则需要引导EU重新认证。
  • ui_locales:可选。用户界面的本地化语言设置项。
  • id_token_hint:可选。之前发放的ID Token,如果ID Token经过验证且是有效的,则需要返回一个正常的响应;如果有误,则返回对应的错误提示。
  • login_hint:可选。向授权服务器提示登录标识符,EU可能会使用它登录(如果需要的话)。比如指定使用用户使用blackheart账号登录,当然EU也可以使用其他账号登录,这只是类似html中input元素的placeholder。
  • acr_values:可选。Authentication Context Class Reference values,对应ID Token中的acr的Claim。此参数允许多个值出现,使用空格分割。

以上是基于Authorization Code方式的OIDC的认证请求所需的参数。在OIDC的其他认证流程中也会有其他的参数或不同的参数值(稍有差异)。一个简单的示例如下:

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

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

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

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

4.3 基于Authorization Code的认证请求,获取ID Token

RP使用上一步获得的code来请求Token EndPoint,这一步同OAuth2,就不再展开细说了。然后Token EndPoint会返回响应的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.ewogImlzcyI6ICJodHRwOi8vc2VydmVyLmV4YW1wbGUuY29tIiwKICJzdWIiOiAiMjQ4Mjg5NzYxMDAxIiwKICJhdWQiOiAiczZCaGRSa3F0MyIsCiAibm9uY2UiOiAibi0wUzZfV3pBMk1qIiwKICJleHAiOiAxMzExMjgxOTcwLAogImlhdCI6IDEzMTEyODA5NzAKfQ.ggW8hZ1EuVLuxNuuIJKX_V8a_OMXzR0EHR9R6jgdqrOOF4daGU96Sr_P6qJp6IcmD3HP99Obi1PRs-cwh3LO-p146waJ8IhehcwL7F09JdijmBqkvPeB2T9CJNqeGpe-gccMg4vfKjkM8FcGvnzZUN4_KSP0aAp1tOJ1zZwgjxqGByKHiOtX7TpdQyHE5lcMiKPXfEIQILVq0pc_E2DzL7emopWoaoZTF_m0_N0YzFC6g6EJbOEoRoSK5hoDalrcvRYLSrQAZZKflyuVCyixEoV9GfNQC3_osjzw2PAithfubEEBLuVVk4XUVrWOLrLl0nx7RkKU8NXNHq-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)。至此,可以说用户身份认证就可以完成了,后续可以根据UserInfo EndPoint获取更完整的信息。

4.4 Implicit Flow和Hybrid Flow

Implicit Flow的工作方式是在OAuth2 Implicit Flow上附加提供id_token,当然,认证请求的参数和基于Authorization Code的稍有不同,具体的差异参见http://openid.net/specs/openid-connect-core-1_0.html#ImplicitAuthRequest,这里就不做详细介绍了。

4.5 Hybrid Flow

Hybrid Flow=Authorization Code Flow+Implicit Flow

4.6 UserInfo Endpoint

UserIndo EndPoint是一个受OAuth2保护的资源。在RP得到Access Token后可以请求此资源,然后获得一组EU相关的Claims,这些信息可以说是ID Token的扩展,比如如果你觉得ID Token中只需包含EU的唯一标识sub即可(避免ID Token过于庞大),然后通过此接口获取完整的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": "Jane Doe",
   "given_name": "Jane",
   "family_name": "Doe",
   "preferred_username": "j.doe",
   "email": "janedoe@example.com",
   "picture": "http://example.com/janedoe/me.jpg"
}

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

5. 常用OIDC endpoints

- Authorization endpoint URL: https://<host_name>:<port_number>/oidc/endpoint/<provider_name>/authorize

- Coverage map endpoint URL: https://<host_name>:<port_number>/oidc/endpoint/<provider_name>/coverage_mapendpoint/<provider_name>/coverage_map)

- Discovery endpoint URL: https://<host_name>:<port_number>/oidc/endpoint/<provider_name>/.well-known/openid-configurationopenid-configuration)

- Introspection endpoint URL: https://<host_name>:<port_number>/oidc/endpoint/<provider_name>/introspect

- JSON Web Key (JWK) endpoint URL: https://<host_name>:<port_number>/oidc/endpoint/<provider_name>/jwk

- Logout endpoint URL: https://<host_name>:<port_number>/oidc/endpoint/<provider_name>/logout

- Registration endpoint URL: https://<host_name>:<port_number>/oidc/endpoint/<provider_name>/registrationendpoint/<provider_name>/registration)

- Revocation endpoint URL: https://<host_name>:<port_number>/oidc/endpoint/<provider_name>/revoke

- Token endpoint URL: https://<host_name>:<port_number>/oidc/endpoint/<provider_name>/token

6. 优势

  • OIDC使得身份认证可以作为一个服务存在。
  • OIDC可以很方便的实现SSO(跨顶级域)。
  • OIDC兼容OAuth2,可以使用Access Token控制受保护的API资源。
  • OIDC可以兼容众多的IDP作为OIDC的OP来使用。
  • OIDC的一些敏感接口均强制要求TLS,除此之外,得益于JWT,JWS,JWE家族的安全机制,使得一些敏感信息可以进行数字签名、加密和验证,进一步确保整个认证过程中的安全保障。

7. 参考文档

[认证授权] 4.OIDC(OpenId Connect)身份认证(核心部分): https://www.cnblogs.com/linianhui/p/openid-connect-core.html

https://w3.innovate.ibm.com/tools/sso/home.html

https://github.ibm.com/Danube-Engine/RAM-GUARDS

Oauth2.0: https://tools.ietf.org/html/rfc6749

JWT: https://tools.ietf.org/html/rfc7519

OpenID Connect core: https://openid.net/specs/openid-connect-core-1_0.html#ImplicitAuthRequest

https://www.ibm.com/support/knowledgecenter/en/SSEQTP_liberty/com.ibm.websphere.wlp.doc/ae/rwlp_oidc_endpoint_urls.html