OpenID Connect 1.0是建立在OAuth 2.0上的一个身份验证机制,它允许客户端通过授权服务对用户进行认证并获取简单的用户信息。

前置知识:读者需要了解OAuth2.0的授权码模式和隐藏模式两种工作流程,要了解JWT、JWE、JWS等概念。这在我的前两篇文章都有详细讲解

概览

名词解释

  • OP:OpenID Provider,即OAuth2.0中的授权服务,用于对用户鉴权

  • RP:Relying Part,依赖方,即OAuth2.0中的客户端,它从OP除获取对用户的鉴权和用户信息

  • ID Token:是一个JWT,包含本次授权的基本信息。具体包含字段如下

    字段必须?说明
    iss发布者,一个https开头的地址
    sub主体,OP对用户的唯一标识,不超过255个ASCII字符
    aud客户,即使用者。值为OAuth2.0协议中客户端注册的client_id
    exp过期时间,遵从RFC 3339协议,即epoch seconds
    iat签发时间,同上
    auth_time可选鉴权时间
    nonce随机值。两个作用
    一是RP发送时带上,OP响应时带上,用于RP对比
    二是防重放攻击
    acr可选Authentication Context Class Reference,暂不知义,忽略
    amr可选Authentication Methods References,暂不知义,忽略
    azp可选Authorized party,暂不知义,忽略

总体流程

类似OAuth2.0,有一个总体流程和若干细分模式的流程,OpenID Connect协议总体流程为:RP发起请求 -> OP对用户鉴权并获取授权 -> OP响应RP并带上ID Token和Access Token -> RP通过访问凭证向OP请求用户信息 -> OP返回用户信息给RP

+--------+                                   +--------+
|        |                                   |        |
|        |---------(1) AuthN Request-------->|        |
|        |                                   |        |
|        |  +--------+                       |        |
|        |  |        |                       |        |
|        |  |  End-  |<--(2) AuthN & AuthZ-->|        |
|        |  |  User  |                       |        |
|   RP   |  |        |                       |   OP   |
|        |  +--------+                       |        |
|        |                                   |        |
|        |<--------(3) AuthN Response--------|        |
|        |                                   |        |
|        |---------(4) UserInfo Request----->|        |
|        |                                   |        |
|        |<--------(5) UserInfo Response-----|        |
|        |                                   |        |
+--------+                                   +--------+

客户鉴权

客户鉴权即OP对客户端进行鉴权,然后将鉴权结果返回给RP。鉴权流程有三种方式:授权码模式、隐藏模式、混合模式。如果了解OAuth2.0,对前两个模式一定不会模式,OpenID流程类似,而混合模式则是前两种模式的结合。具体OP采用什么模式,取决于RP请求时response_type给的值。

response_type采用的模式
code授权码模式
id_token隐藏模式
Id_token token隐藏模式
code id_token混合模式
code token混合模式
code id_token token混合模式

规律:id_token和token等同:只有id_token或/和token的使用隐藏模式,只有code的使用授权码模式i,同时存在他们的则使用混合模式

授权码模式

总计八个步骤

  1. RP准备用于鉴权请求的参数
  2. RP发送请求,给OP
  3. OP对用户鉴权
  4. OP手机用户的鉴权信息和授权信息
  5. OP发送授权码给RP
  6. RP使用授权码向一个端点换取访问凭证。协议称之为Token端点,但没说这个端点是不是由OP提供的。不过一般来说是
  7. RP收到访问凭证,包含ID Token、Access Token
  8. 客户端验证ID Token,并从中提取用户的唯一标识。前面说过这是一个JWT,唯一标识就是subject identifier

授权码的请求与响应

其中RP准备的请求参数包含OAuth2.0规定的所有字段,也包含一些额外的字段

参数必须?来自OAuth2.0?说明
scope写死,openid
response_type写死,code
client_idRP在OP处注册得到的唯一标识
redirect_uri用于OP鉴权成功后的回调地址,RP在OP处注册时提供
state推荐请求来回中包含的不透明值,用户防范CSRF攻击
response_modeOP返回数据的模式
nonce会被放在ID Token的nonce字段,用于防重放攻击
display定义OP通过什么方式展示用户鉴权界面
page:完整的网页
popup:弹窗
touch:触摸设备
wap:“feature phone” type display
prompt定义OP通过什么方式对用户二次鉴权
none:不进行二次鉴权
login:重新登录
consent:获取用户同意使用上次采集到的结果即可
select_account:选择用户账户
max_age本次鉴权的有效期。超过该时间后,OP必须对用户再次进行鉴权
ui_locales用户使用的区域信息
Id_token_hint忽略
login_hint忽略
acr_values忽略

如果授权成功,得到的返回会包含code、state等参数,举个例子

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

如果授权失败,也会有相应的错误码,具体参考手册的3.1.2.6

凭证的请求与响应

这个就完全和OAuth2.0一样了。

请求上主要包含:grant_type写死authorization_codecode填上一步获取的授权码、redirect_uri填上一步的重定向地址,举个例子

POST /token HTTP/1.1
Host: server.example.com
Content-Type: application/x-www-form-urlencoded
# 你可能注意到这里有个Basic鉴权,这是因为Client也是需要被验证的
Authorization: Basic czZCaGRSa3F0MzpnWDFmQmF0M2JW

grant_type=authorization_code&code=SplxlOBeZQQYbYS6WxSbIA&redirect_uri=https%3A%2F%2Fclient.example.org%2Fcb

响应上多出一个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"
}

在上面的每一方,比如OP收到请求、RP收到响应,都会对得到的数据进行验证,我们都忽略了,不过这里重点将RP收到ID Token后的验证逻辑列出来

  1. 依据JWT协议解密该Token
  2. iss字段必须匹配RP提前获取到的OP的issuer值
  3. aud字段必须是RP在OP处注册时填写的client_id
  4. 如果包含多个aud字段,则还要验证azp字段
  5. 如果azp字段存在,则它的值必须是RP在OP处注册时填写的client_id
  6. 如果ID Token是RP直接从OP处获取,没有走授权码这一步(比如走隐藏模式的流程),必须走JWS流程验证该JWT的签名
  7. alg的值要么为默认的RS256,要么是RP在OP处注册时通过id_token_signed_response_alg字段指定的算法
  8. exp所展示的时间必须比当前时间晚
  9. iat可以用来识别签发时间过久的JWT,太久的可以拒掉,多久算久,这个取决于RP自己
  10. nonce必须和请求授权码时对应上
  11. acr必须和请求授权码时对应上
  12. 接和auth_time、max_age,可以判断举例上一次时间是不是太久,从而决定是否需要重新发起鉴权请求

隐藏模式

这个模式就比较简单了

  1. RP准备请求参数
  2. RP发送请求
  3. OP认证用户
  4. OP获取用户的认证和授权信息
  5. OP发送ID Token,可能还有Access Token给RP
  6. RP验证ID Token,提取用户的标识

请求

请求参数和授权码模式基本一样,这里列出差别

  • response_type:id_token,或者id_token token,差别是,如果加上token,步骤5会返回Access Token,否则就么有
  • redirect_uri:处于安全考虑,这个地方必须使用HTTPS传输
  • nonce:这个变成了必须的

响应

给个例子就好了

HTTP/1.1 302 Found
Location: https://client.example.org/cb#
  access_token=SlAV32hkKG
  &token_type=bearer
  &id_token=eyJ0 ... NiJ9.eyJ1c ... I6IjIifX0.DeWt4Qu ... ZXso
  &expires_in=3600
  &state=af0ifjsldkj

与前面不同的是,这里会多一个Access Token的验证,它要结合ID Token中的at_hash字段验证

  • 使用ID Token中头部alg字段指定的算法对Access Token进行哈希
  • 对哈希值进行base64url编码,取左半边
  • 上一步得到的值必须和ID Token中的at_hash字段指定的值匹配

混合模式

  1. RP准备请求
  2. RP发送请求给OP
  3. OP对用户鉴权
  4. OP采集用户的鉴权和授权信息
  5. OP发送给RP授权码,同时根据请求时指定的response_type,发送额外的参数
  6. RP通过授权码向OP请求
  7. RP从OP处得到ID Token和Access Token
  8. RP验证ID Token,解析用户的唯一标识

授权码请求

混合模式在response_type上做文章,允许的值包括:code id_tokencode tokencode id_token token。可以看出,在授权码请求这一步,可能会返回授权码、ID Token、Access Token。下面是一个返回授权码和ID Token的例子

HTTP/1.1 302 Found
Location: https://client.example.org/cb#
code=SplxlOBeZQQYbYS6WxSbIA
&id_token=eyJ0 ... NiJ9.eyJ1c ... I6IjIifX0.DeWt4Qu ... ZXso
&state=af0ifjsldkj

如果返回值有ID Token,它会包含授权码的签名,对应其c_hash字段,计算方式和隐藏模式的Access Token验证差不多。

至此,混合模式看起来很奇怪,体现在两处

  • ID Token既能在授权码请求时返回,也能在Access Token请求时返回
  • Access Token也有上面的情况

这样做有什么意义呢?

小结

ID Token的意义,主要在于以安全的方式分发用户的唯一标识,Access Token才是用来作为访问凭证的。

获取用户信息

上面介绍了获取访问凭证之前的动作,这里介绍使用访问凭证访问ID Token指定的用户的信息的问题。

能获得什么

获得的内容和请求访问凭证时传的scope有关,具体如下。

字段scope说明
sub-唯一标识,对应JWT的sub
nameprofile用户全名
given_nameprofile
family_nameprofile
middle_nameprofile中间名,这个某些文化有关
nicknameprofile昵称
perferred_usernameprofile用户希望展示给RP的名字
profileprofile用户的概览信息的页面URI
pictureprofile用户图片的URI
websiteprofile用户的网站或者博客URI
emailemail用户邮箱
email_verifiedemail邮箱是否通过验证。如果没有,这可能是用户随便填写的一个邮箱
genderprofile性别
birthdateprofile生日
zoneinfoprofile用户所处时区信息
localeprofile用户所处地域,语言环境
phone_numberphone电话号码
phone_number_verifiedphone电话号码是否通过验证
addressaddress地址,这是一个JSON对象
updated_atprofile用户的这些信息最后一次被更新的时间,epoch seconds

scope可以指定多个值,得到的结果就是并集。比如scope=profile email phone address

一个成功的例子

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"
}

另类获取信息的方式 - claims

可以在请求时加上claims参数,指定能够从用户信息端点或者在ID Token中包含什么信息,它是一个类似json schema的东西,如下是例子:要求给的字段,以及字段是否必要。

{
  "userinfo":
  {
    "given_name": {"essential": true},
    "nickname": null,
    "email": {"essential": true},
    "email_verified": {"essential": true},
    "picture": null,
    "http://example.info/claims/groups": null
  },
  "id_token":
  {
    "auth_time": {"essential": true},
    "acr": {"values": ["urn:mace:incommon:iap:silver"] }
  }
}

总结

理解了OAuth2.0的工作流程,再理解OpenID Connect就很容易了,相较而言,它的特点如下

  • OP = 授权服务+资源服务,资源服务的唯一作用就是分发用户信息
  • 规定了用户信息包含的内容
  • Token响应中多了ID Token,它用来指定用户ID,以便在访问用户信息时作为标识
  • ID Token使用了JWT
  • 多了混合模式
Logo

Authing 是一款以开发者为中心的全场景身份云产品,集成了所有主流身份认证协议,为企业和开发者提供完善安全的用户认证和访问管理服务

更多推荐