隐式模式

OIDC除了授权码模式,还有隐式模式,即基于token的登录。该模式常结合移动应用或 Web App 使用。

OIDC 隐式模式不会返回授权码 code,而是直接将 access_tokenid_token 通过 URL hash 发送到回调地址前端后端无法获取到这里返回的值,因为 URL hash 不会被直接发送到后端。

OIDC配置

我们仍以授权码模式中提到的这张图为例:

隐式模式不需要配置App Secret,但开启该模式后,需要保证JWKS可用,有两种指定方式:

{
    "kty": "RSA",
    "e": "AQAB",
    "n": "1GBBv-QOtbNIvgJZqvW2nvIrNx6-YNKJAD3L3WspAcx1y-RYctI2RBb4k4GN0du8AH2UUf8wBywONHplYAw1djkWAztHgj4cc_WxqKvD1t5bNNjRW7I5EPA9ZEkFblIAxZVwhOPK5H8KLgiVaD7y9fPEks6sVhu2VUQKC0Qr85-0WJVzmXP3QH_1yLn1qRpkJtjCW1I4DPsB0TrQC6WBMy99Io8zECraueLrJFApuRx1H_MwgDwnt4VlYuaoqU17TyBUQWO077mUB-FFI-s0jALuPAUuNWHFFogTq2cbydaSfPcWQPjylYcLcIt-bBBdedLqsTk_0nTXPqREMFwexw",
    "alg": "RS256",
    "use": "sig",
    "kid": "AUQR2TFiVexgvm0j0PrbZ3ofEz9R2eG7qvEJP9Ua2f0"
}

其中,JWKS URL可以从服务发现地址中获取。

https://dev-5kzk7gzc.us.auth0.com/.well-known/openid-configuration

OIDC使用

获取Token

获取Token有两种方式:id_token Flow和登录接口。

区别是:id_token Flow需要依赖OIDC的登录页,而登录接口获取可以自定义登录页!

id_token Flow

以Authing为例,构建如下 id_token flow URL。

# 授权地址,与授权码模式类似
https://xxxx.authing.cn/oidc/auth?
# 客户端ID,即应用ID
client_id=644512df9e360e3f7a40e1e4&
# 重定向URL,授权完成后,跳转的页面
redirect_uri=https://example.com&
scope=openid%20profile&
# 授权类型,注意与授权码模式不同,非常重要
response_type=id_token%20token&
state=6223573295&nonce=1831289

访问后,进入登录页,输入账号密码登录。

登录成功后,将跳转到如下链接:

https://example.com/#
# 授权令牌
access_token=part1.part2.part3&
# 令牌类型,Bearer
token_type=Bearer&
# 令牌过期时间
expires_in=1209600&
scope=openid%20profile&
# id令牌
id_token=part1.part2.part3&
state=6223573295

access_token 保存到客户端使用,例如:local storage

登录接口

下面是Authing用账户密码登录的HTTP请求,详情可参考Authing的SDK

curl --location --request POST 'https://xxxx.authing.cn/api/v3/signin' \
# 用户池ID
--header 'x-authing-userpool-id: 64452248da5930463241789f' \ 
# 应用ID
--header 'x-authing-app-id: 644513df9e260e3f7a10e3e4' \
--header 'Content-Type: application/json' \
--header 'Accept: */*' \
--header 'Connection: keep-alive' \
--data-raw '{
    "connection":"PASSWORD", # 登录模式
    "passwordPayload":{
        "password":"123456", # 密码
        "phone":"18189156130" # 账户
    }
}'
{
    "statusCode": 200,
    "message": "",
    "data": {
        "scope": "profile openid tenant_id",
        "token_type": "Bearer",
        "access_token": "part1.part2.part3", // 保存使用
        "expires_in": 1209600,
        "id_token": "part1.part2.part3"
    }
}

不同的OIDC供应商,实现不同!

Fireboom官方也开源一个简单的OIDC服务,当前仅支持隐式模式,详情请查看:fb-oidc

使用Token

假设,有如下OPERATION,使用 @fromClaim 指令修饰,意思是根据当前登录用户的UID,查询待做事项列表:

FromClaim.graphql
query MyQuery($uid: String! @fromClaim(name: USERID)) {
  todo_findManyTodo(where: {uid: {equals: $uid}}) {
    id
    title
  }
}

其将被编译为如下REST API,请求如下:

curl -X GET "http://localhost:9991/operations/FromClaim" \
# 添加以下请求头
--header 'Authorization: Bearer <access_token>' \
 -H "accept: application/json" \
{
  "data": {
    "todo_findUniqueClaim": {
      "email": "[email protected]",
      "emailVerified": false,
      "location": "",
      "name": "[email protected]",
      "nickname": "test",
      "provider": "auth0",
      "roles": null,
      "userId": "auth0|637b4ab0c0cb508c49de7cf3"
    }
  }
}

工作原理

接下来,我们学习下OIDC协议隐式模式的工作原理。

正常流程

该时序图有3个主体:客户端、Fireboom服务器、OIDC供应商 authing。

1.客户端到OIDC(Authing)

在客户端构造下述URL,跳转到Authing授权端点。

# Authing 授权端点
https://xxx.authing.cn/oidc/auth?
# 客户端ID,用于区分是哪个应用
client_id=xxxx&
# 重定向URL,当前客户端的前端页面(不是Fireboom的地址)
redirect_uri=[https://example.com]&
# 授权范围
scope=openid profile&
# 授权类型,这是与授权码模式的区别之处
response_type=id_token token&
state=xxx

2.跳转到OIDC登录页

若用户未登录,则跳转到authing的登录页,支持账户密码、手机验证码,甚至是社交登录,如微信、QQ等。

3.OIDC跳转到客户端

登录成功后,authing再跳转到客户端回调地址上,同时携带授权码access_tokenid_token

和授权码模式不同这里拿到的不是code,且此时未经过飞布服务。

https://example.com/#
# 授权令牌
access_token=part1.part2.part3&
# 令牌类型,Bearer
token_type=Bearer&
# 令牌过期时间
expires_in=1209600&
scope=openid%20profile&
# id令牌
id_token=part1.part2.part3&
state=6223573295

4.客户端保存令牌

客户端保存access_tokenexpires_in ,供下次使用,一般存储到local storage中。

5.客户端获取OIDC用户

客户端使用access_token,访问OIDC的用户信息端点,获得当前用户。

curl --location --request GET 'https://xxx.authing.cn/oidc/me' \
--header 'Authorization: Bearer <access_token>' \
--header 'Accept: */*' \
{
    "name": "anson",
    "given_name": null,
    "middle_name": null,
    "family_name": null,
    "nickname": "18189156130",
    "preferred_username": "18189156130",
    "profile": null,
    "picture": null,
    "website": null,
    "birthdate": "2023-06-29T12:47:00.173Z",
    "gender": "male",
    "zoneinfo": null,
    "locale": null,
    "updated_at": "2023-08-09T09:07:37.988Z",
    "tenant_id": "",
    "sub": "645b472d9df8868fe52074d6"
}

总结一下,1-5步骤,都不需要飞布服务参与,完全可以由客户端和OIDC服务器完成。

和授权码模式最大的不同是,隐式模式不需要client secret,也不需要使用 code 换 token,更无需请求 token 端点,access_token 和 id_token 会直接从授权端点返回。

6.客户端请求Fireboom API

客户端请求Fireboom API时,携带如下请求头 ,示例见 使用Token

Authorization: Bearer <access_token>

7.Fireboom校验令牌

8.客户端获取Fireboom用户

若Fireboom服务未缓存用户信息,则会用access_token请求OIDC用户端点,获得用户,同 #5.-ke-hu-duan-huo-qu-oidc-yong-hu

9.Fireboom调用钩子

Fireboom 调用授权钩子,在钩子中修改用户信息,返回后,由Fireboom 缓存用户信息,详情见 身份验证钩子,并返回用户信息到客户端。

简化流程

刚刚介绍的,无论是授权码模式还是隐式模式都需要使用OIDC原生的登录页。那如果想自定义登录页,又该如何做呢?

其实只要基于隐式模式稍作调整,就能支持该需求。我们知道,隐式模式的本质是获得access_token,然后用其请求接口。

那如果OIDC服务提供了根据账户密码获得accest_token的接口,是不是就可以供我们在自己编写的界面中直接调用了呢?

我们仍以authing为例,上述流程图就展示了该过程。

1.调用登录接口

通过post请求,输入账户、密码或手机号、验证码,获得acces_token。不同OIDC供应商有对应实现,详情可参考其文档。例如前文中提到的 登录接口

2.客户端存储令牌

客户端存储 acces_token,供后续使用。

其他流程没有变化,同 正常流程 的步骤6-9。

总结

总结一下,隐式模式的流程更加简洁。

由于其不依赖cookie,所以适用范围更广,不仅支持浏览器,也能支持类似微信小程序或者原生APP。

但缺点是安全性不足,因为其直接将access_token泄露给了客户端。

最后更新于