身份验证(废弃)

飞布支持OIDC进行身份验证,实现了OIDC中定义的两种授权流程:基于cookie登录-授权码模式(Authorization Code)和基于Token登录-隐式模式(Implicit)。

支持OIDC Provider

飞布能与任意实现OIDC规范的供应商集成。目前主流OIDC供应商如下:

  • IDaaS服务商auth0、authing等

  • 开源OIDC服务:okta、casdoor等。

快速操作

基本设置

  1. 在身份验证面板中点击“+”,进入OIDC新建页

  2. 首先,输入供应商名称,自动生成 “登录回调 URL”。

  3. 然后,前往AUTHING应用配置页,获取APP ID 、App Secret和Issuer ,分别填入身份验证器表单。

  4. 随后,输入APP ID。

  5. 接着,输入Issuer。输入后,系统自动生成服务发现地址,并从中获取jwksURL和用户端点

  6. 当开发WEB应用时,开启基于cookie的模式,同时填入App Secret。

  7. 当开发移动应用时,开启基于Token的模式。

若JwksURL无法访问,可将JWKS切换到JSON模式,然后输入JSON字符串。

{
    "kty": "RSA",
    "e": "AQAB",
    "n": "1GBBv-QOtbNIvgJZqvW2nvIrNx6-YNKJAD3L3WspAcx1y-RYctI2RBb4k4GN0du8AH2UUf8wBywONHplYAw1djkWAztHgj4cc_WxqKvD1t5bNNjRW7I5EPA9ZEkFblIAxZVwhOPK5H8KLgiVaD7y9fPEks6sVhu2VUQKC0Qr85-0WJVzmXP3QH_1yLn1qRpkJtjCW1I4DPsB0TrQC6WBMy99Io8zECraueLrJFApuRx1H_MwgDwnt4VlYuaoqU17TyBUQWO077mUB-FFI-s0jALuPAUuNWHFFogTq2cbydaSfPcWQPjylYcLcIt-bBBdedLqsTk_0nTXPqREMFwexw",
    "alg": "RS256",
    "use": "sig",
    "kid": "AUQR2TFiVexgvm0j0PrbZ3ofEz9R2eG7qvEJP9Ua2f0"
}
  1. 最后,保存表单,完成配置。

值得注意的是,基于Cookie的登录,需要OIDC供应商(OIDC Provider)和飞布服务器后端(Relying-Party)同时配置回调地址。

首先是,复制 登录回调 URL, 前往AUTHING设置“登录回调 URL”,多个URL可用"英文逗号"分开。

接着,点击“配置登录回调”按钮,前往"设置->安全",设置 "重定向URL"。

系统提供了两个默认值:

  • localhost:9123/#/workbench/userInfo:用户详情页回调URL,用于测试OIDC

  • localhost:9123/#/workbench/rapi/loginBack:API预览页回调URL,用于测试需要授权的API接口。

后续,可根据集成的前端项目,添加对应URL。

回到详情页,点击右上角“测试”按钮。跳转至authing提供的登录页,登录后,可查看当前用户信息。

只有开启“基于Cookie”模式后,才能直接测试。

出于安全考虑,在回调至OIDC供应商URL时,系统会自动跳转到HTTPS链接。若想关闭该功能,可关闭 “强制 HTTPS 跳转”。

API设置

  1. 前往API管理面板,选择需要设置的API

    1. 登录访问:切换到设置面板,开启授权,限制API必须登录才能访问

    2. 数据权限:用@fromClaim修饰入参,限制API的数据权限

  2. 点击顶部菜单栏的“预览”,前往API预览页,选择当前API

  3. 输入参数,测试接口,你会发现,接口返回401(这是因为你没登录)

  4. 在预览页顶部,选择OIDC供应商,点击前往登录,登录后可查看用户信息

  5. 重复步骤3,可以看到接口执行成功,有三种情形

    1. 登录访问:未限制数据权限,正常执行,唯一区别是需要用户登录才能执行

    2. 查询请求:限制数据权限,只返回当前用户拥有的数据

    3. 变更请求:限制数据权限,插入数据时绑定当前用户的标识,如UID或EMAIL等

客户端如何使用

构建如下URL,在网页上点击跳转即可。

http://localhost:9991/api/auth/cookie/authorize/<供应商ID>?redirect_uri=<当前页URL>

  • 供应商ID:对应OIDC表单中的供应商ID

  • 当前页URL:对应"设置->安全"中的"重定向URL"

基于TOKEN登录

客户端需要向请求中添加以下请求头:

Authorization: Bearer <token>

如何获取TOKEN,可参考Authing的SDK

工作原理

专业术语

  1. EU(End-User):终端用户

  2. RP(Relying-Party):服务器后端,这里指飞布服务器后端

  3. OP(OIDC Provider): 提供身份验证的服务器,例如Authing 服务器

授权码模式是 OIDC 授权登录中最常用的模式,OP 服务器返回一个授权码 code 给开发者后端服务器,在后端完成 code 换取 access_token,再用 access_token 换取用户信息的操作,从而实现用户的身份认证。

1. 发起登录请求

发起授权需要拼接一个用来授权的 URL,并让终端用户在浏览器中访问,具体参数如下:

<Issuer>/auth
 ?response_type=code
 # RP身份标识,对应App ID
 &client_id=29352915982374239857  
 # 授权服务器接收请求后返回给浏览器的跳转访问地址,对应 设置->安全->重定向URL
 &redirect_uri=https%3A%2F%2Fexample-client.com%2Fcallback  
 &scope=create+delete
 &state=xcoiv98y2kd22vusuye3kch
Issuer:OP服务的连接,例如:https://<应用域名>.authing.cn/oidc/

2.用户登录

发起 OIDC 登录之后,如果用户先前未在 OP 登录过,OP 会将用户重定向到登录页面,引导用户完成在 OP 的认证,此时用户需要选择一种方式进行登录:

3.获取code

OP将验证此用户是否合法,验证通过后会将浏览器重定向到发起授权登录请求时指定redirect_uri 并通过 URL query 传递授权码 code 参数。

https://example-client.com/redirect
 ?code=g0ZGZmNjVmOWIjNTk2NTk4ZTYyZGI3  # 授权码,用于换取access_token,用一次就失效
 &state=xcoiv98y2kd22vusuye3

4.使用code换取token

飞布默认换取 token 身份验证方式为 client_secret_post,需要向token_endpoint发送POST请求,具体如下:

# POST <token_endpoint> // 
{
    code:"g0ZGZmNjVmOWIjNTk2NTk4ZTYyZGI3" # 授权码
    client_id: "29352915982374239857", # RP身份标识,对应App ID
    client_secret: "xxxx", # RP密钥,对应App Secret。
    grant_type: "authorization_code",# 指定RP正在使用的授权流程。
    # 与请求authorization code时使用的redirect_uri相同。
    redirect_uri:"https%3A%2F%2Fexample-client.com%2Fcallback ",
}

token_endpoint一般从服务发现地址中获取,格式:<Issuer>/.well-known/openid-configuration

5.签发访问令牌

OP将会验证第4步中的请求参数,当验证通过后(校验authorization code是否过期,client idclient secret是否匹配等),OP将向RP返回access token

{
    # 访问令牌
    "access_token": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6IjEifQ.eyJqdGkiOiJQZU41YXg1b3FabGRhcUJUMzQzeUkiLCJzdWIiOiI1Y2U1M2FlYTlmODUyNTdkZDEzMmQ3NDkiLCJpc3MiOiJodHRwczovL29hdXRoLmF1dGhpbmcuY24vb2F1dGgvb2lkYyIsImlhdCI6MTU4MTQyMDk1NywiZXhwIjoxNTgxNDI0NTU0LCJzY29wZSI6Im9wZW5pZCBwcm9maWxlIGF1dGhpbmdfdG9rZW4gZW1haWwgcGhvbmUgYWRkcmVzcyBvZmZsaW5lX2FjY2VzcyIsImF1ZCI6IjVkMDFlMzg5OTg1ZjgxYzZjMWRkMzFkZSJ9.rtpRSL3_U03zXShZUCILquSR_KEDuS-OldWpy8RLztWUNG_tMyrg2g9CG4hC7pJUwmgzZKtp7vsVrj6W0eyo_ehE4KGz9iKnyd46DFbx9W9pi-mieRW5HuVMGL2zvDH8zF467WXET2SVB3LUhFLNmEbxpvjPZ5Ksvbcd7nqHfnUN4-z3SqAvhGWWfcmt7QDFlLtWPw4LzyznEqmM9sDkNiNDnTkjmcjm7yHJR-yv5FvpzQB2kraQVOrrdAixbHf29ihOVO25CrjmgeKemg1vuLNGUcOrr_XWn7xaCSvyAfXrBuRalecW9RA4p_Cp6YslHc_572awekt3kUO2TebUQA",
    "expires_in": 3597,
    "id_token": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6IjEifQ.eyJzdWIiOiI1Y2U1M2FlYTlmODUyNTdkZDEzMmQ3NDkiLCJiaXJ0aGRhdGUiOiIiLCJmYW1pbHlfbmFtZSI6IiIsImdlbmRlciI6IiIsImdpdmVuX25hbWUiOiIiLCJsb2NhbGUiOiIiLCJtaWRkbGVfbmFtZSI6IiIsIm5hbWUiOiIiLCJuaWNrbmFtZSI6IiIsInBpY3R1cmUiOiJodHRwczovL3VzZXJjb250ZW50cy5hdXRoaW5nLmNuL2F1dGhpbmctYXZhdGFyLnBuZyIsInByZWZlcnJlZF91c2VybmFtZSI6IiIsInByb2ZpbGUiOiIiLCJ1cGRhdGVkX2F0IjoiIiwid2Vic2l0ZSI6IiIsInpvbmVpbmZvIjoiIiwiY29tcGFueSI6IiIsImJyb3dzZXIiOiIiLCJsb2dpbnNfY291bnQiOjEwMywicmVnaXN0ZXJfbWV0aG9kIjoiZGVmYXVsdDp1c2VybmFtZS1wYXNzd29yZCIsImJsb2NrZWQiOmZhbHNlLCJsYXN0X2lwIjoiMTIxLjIxLjU2LjE3MSIsInJlZ2lzdGVyX2luX3VzZXJwb29sIjoiNWM5NTkwNTU3OGZjZTUwMDAxNjZmODUzIiwibGFzdF9sb2dpbiI6IjIwMjAtMDItMTFUMTE6MzU6MTUuNjk2WiIsInNpZ25lZF91cCI6IjIwMTktMDUtMjJUMTI6MDQ6NTguMjk0WiIsInRva2VuIjoiZXlKaGJHY2lPaUpJVXpJMU5pSXNJblI1Y0NJNklrcFhWQ0o5LmV5SmtZWFJoSWpwN0ltVnRZV2xzSWpvaWRHVnpkRE5BTVRJekxtTnZiU0lzSW1sa0lqb2lOV05sTlROaFpXRTVaamcxTWpVM1pHUXhNekprTnpRNUlpd2lZMnhwWlc1MFNXUWlPaUkxWXprMU9UQTFOVGM0Wm1ObE5UQXdNREUyTm1ZNE5UTWlmU3dpYVdGMElqb3hOVGd4TkRJd09URTFMQ0psZUhBaU9qRTFPREkzTVRZNU1URjkuM0l0X0NJQTNFbUpoYWcyMW92WjNwd0RfY0owcTVTZkJjSURSZThRX3FoayIsImVtYWlsIjoidGVzdDNAMTIzLmNvbSIsImVtYWlsX3ZlcmlmaWVkIjpmYWxzZSwicGhvbmVfbnVtYmVyIjoiMTMxMTIzNDEyMzQiLCJhZGRyZXNzIjoiIiwiYXRfaGFzaCI6IjV6QnNUOHF4RHc1RmNYdU55UFg4YUEiLCJzaWQiOiJkNmZiOTE5Ny00NmE3LTQ1ZGEtOGVkMC05ODhjZjg0ZjQwZWUiLCJhdWQiOiI1ZDAxZTM4OTk4NWY4MWM2YzFkZDMxZGUiLCJleHAiOjE1ODE0MjQ1NTQsImlhdCI6MTU4MTQyMDk1NywiaXNzIjoiaHR0cHM6Ly9vYXV0aC5hdXRoaW5nLmNuL29hdXRoL29pZGMifQ.VZzqULytIteyBfouww5TsHQ50gEhM06kUWMeDiO3FVFSCW9ys2bFPos5p6LFzliK4Ce09ypOwVQiRnE2gNYsukLvlUPlKDIP_Xk5W19frKi1Z8ImuIPvUqVMKbFutVNS0TfIPCPJVBl8C1j5OXeIs6z0V90QrvyJao6FqVEa3axOHxbhpo1fH2hP04-wkGOp_l10d7RFhGcnPyPnz9-C5X6A4UEsCSDCVw1mDQHxDSFP9OPaB_OlCG_Bi6G-CeLhPa3V5hyIefdBvxC9SIpK-6qY-_BfsNKkBHDVKMb0xodgN2hzn3UTUGBuuoiaB4JhCv72EZ7eiXKIXFz6zVcogA",
    "refresh_token": "DuSPlrUFPAvCZ1WQKarv5MbEsXN",
    "scope": "openid profile authing_token email phone address offline_access",
    "token_type": "Bearer"
}

基于Token登录-隐式模式

OIDC 隐式模式不会返回授权码 code,而是直接将 access_tokenid_token 通过 URL hash 发送到回调地址前端后端无法获取到这里返回的值,因为 URL hash 不会被直接发送到后端。该模式常结合移动应用或 Web App 使用。

1. 用户授权请求

发起隐式模式的授权登录需要拼接一个 URL,并让终端用户在浏览器中访问,不能直接输入认证地址域名。具体参数如下:

<authorization_endpoint>
# RP身份标识,对应App ID
?client_id=0oabv6kx4qq6h1U5l0h7
&response_type=token # 为token 或 id_token
# 回调链接,用户在 OP 认证成功后,OP 会将 id_token、access_token 以 URL hash 的形式发送到这个地址。
&redirect_uri=http%3A%2F%2Flocalhost%3A8080
&state=state-296bc9a0-a2a2-4a57-be1a-d0e2fd9bb601
&nonce=foo

authorization_endpoint一般从服务发现地址中获取,常见格式:<Issuer>/authorize

服务发现地址:<Issuer>/.well-known/openid-configuration

2.用户授权应用(略)

3.用访问令牌重定向URI

假设用户授予访问权限,跳转后链接示例:

http://localhost:8080/
# 访问令牌, 以 URL hash 形式传递
#access_token=eyJhb[...]erw
# 当且仅当response_type设置为 token 时返回,值恒为 Bearer
&token_type=Bearer 
&expires_in=3600
&scope=openid
&state=state-296bc9a0-a2a2-4a57-be1a-d0e2fd9bb601

为什么信息在 URL hash 里而不是 query 里?因为 hash 内容不会直接发送到服务器,避免 id_token、access_token 被盗用。

1-3步骤为标准流程,不同客户端获取access_token 的流程不同,需要根据实际情况处理。从工程实践中看,常用的另一种方式是直接调用OIDC供应商的登录接口,从中获取access_token。

4.传递给应用程序的访问令牌

浏览器向RP发送access token。RP采用两种方式校验令牌:

  • 公钥签名校验:优先使用使用公钥验证签名。公钥地址(jwks_uri)一般为: <Issuer>/.well-known/jwks.json

  • **在线接口校验:**若公钥验签失败,则调用供应商的token验证接口进行在线验证。userinfo_endpoint?

参考链接

  1. 可以在线检验 JWT 的签名的网站:https://jwt.io (opens new window)

  2. RSA 公私钥 PEM 格式 与 JWK 格式互转:https://8gwifi.org/jwkconvertfunctions.jsp (opens new window)

获取用户信息

直到access token 过期或失效之前,RP使用access_token,通过OP的userinfo_endpoint API换取用户信息。如果发起授权登录时的 scope 参数不同,这里的返回信息也会不同,返回信息中的字段取决于 scope 参数。字段符合 OIDC 规范 (opens new window),用户信息字段与 scope 对应关系请参考 scope 参数对应的用户信息

具体请求如下:

GET <userinfo_endpoint>?access_token=<access_token>
userinfo_endpoint一般从服务发现地址中获取,常见格式:<Issuer>/userinfo

返回值示例:

{
  "sub": "5f7174df27e0eb9c6d21436d",
  "birthdate": null,
  "family_name": null,
  "gender": "U",
  "given_name": null,
  "locale": null,
  "middle_name": null,
  "name": null,
  "nickname": null,
  "picture": "https://usercontents.auth0.cn/avatar.png",
  "preferred_username": null,
  "profile": null,
  "updated_at": "2020-09-28T05:33:15.892Z",
  "website": null,
  "zoneinfo": null
}

OIDC规范中不包含角色的描述,因此返回值不涉及roles字段

最后更新于