跳到主要内容

OAuth 接入流程

本文档介绍如何将你的应用接入 CaelLabID OAuth 2.0 认证。

整体流程

用户浏览器 → 你的应用跳转到 CaelLabID
→ 用户登录并授权
→ CaelLabID 重定向回你的回调地址(带 code)
→ 你的应用用 code 换 token
→ 你的应用用 token 获取用户信息

第一步:引导用户授权

构造授权 URL,让用户浏览器跳转到 CaelLabID:

https://id.caellab.com/oauth/authorize
?response_type=code
&client_id=YOUR_CLIENT_ID
&redirect_uri=YOUR_CALLBACK_URL
&scope=openid
&state=RANDOM_STRING

请求参数

参数类型必需说明
response_typestring固定值 code
client_idstring你的应用 Client ID
redirect_uristring回调地址,必须与注册时填写的一致
scopestring权限范围。默认 openid,可选 openid profile
statestring推荐随机字符串,用于防止 CSRF

验证规则

  • response_type 必须为 code,否则返回 400
  • client_id 必须在系统中已注册且状态为启用
  • redirect_uri 必须在应用注册时的回调 URL 列表中
  • scope 中不支持的值会被自动过滤,仅保留 openid

行为

条件行为
用户未登录存储 OAuth 请求到 Session → 跳转登录页 → 登录后返回授权页
用户已授权且 scope 足够直接发放授权码 → 重定向到 redirect_uri(免确认)
用户未授权或 scope 不足显示授权确认页
已授权免确认

如果用户之前已授权过且当前请求的 scope 是之前已授权 scope 的子集,则自动发放授权码,不再弹出授权页面。

第二步:处理回调

用户同意授权后,CaelLabID 会 302 重定向到你的 redirect_uri

成功:

YOUR_CALLBACK_URL?code=AUTHORIZATION_CODE&state=RANDOM_STRING

用户拒绝:

YOUR_CALLBACK_URL?error=access_denied&state=RANDOM_STRING

你的处理逻辑:

  1. 验证 state 参数与第一步发送的一致(防 CSRF)
  2. 提取 code 参数
  3. 如果有 error 参数,处理用户拒绝授权的情况

第三步:换取 Token

用授权码换取 Access Token。这一步是服务端对服务端的调用,不能在浏览器端执行。

POST /oauth/token

方式一:POST body 传参

POST https://id.caellab.com/oauth/token
Content-Type: application/x-www-form-urlencoded

grant_type=authorization_code
&code=AUTHORIZATION_CODE
&redirect_uri=YOUR_CALLBACK_URL
&client_id=YOUR_CLIENT_ID
&client_secret=YOUR_CLIENT_SECRET

方式二:HTTP Basic Auth

POST https://id.caellab.com/oauth/token
Content-Type: application/x-www-form-urlencoded
Authorization: Basic BASE64(client_id:client_secret)

grant_type=authorization_code
&code=AUTHORIZATION_CODE
&redirect_uri=YOUR_CALLBACK_URL

请求参数

参数必需说明
grant_type固定值 authorization_code
code授权码
redirect_uri必须与授权请求中一致
client_idClient ID(Basic Auth 时可省略)
client_secretClient Secret(Basic Auth 时可省略)

成功响应

{
"access_token": "a1b2c3d4...",
"token_type": "Bearer",
"expires_in": 3600,
"refresh_token": "e5f6g7h8...",
"scope": "openid"
}
字段说明
access_token访问令牌,用于调用 API
token_type固定值 Bearer
expires_inAccess Token 有效期(秒)
refresh_token刷新令牌,用于获取新的 Access Token
scope实际授予的权限范围
安全注意

redirect_uri 参数必须与授权请求中完全一致,否则会被拒绝。

第四步:获取用户信息

使用 Access Token 调用用户信息接口:

GET https://id.caellab.com/api/userinfo
Authorization: Bearer ACCESS_TOKEN

响应示例

{
"sub": "9402013ac8b214059694f64f130da6f2a9a4603e06215f2c6998929eb511ce08",
"id": "9402013ac8b214059694f64f130da6f2a9a4603e06215f2c6998929eb511ce08"
}

返回字段取决于你的 Scope:

字段需要 Scope说明
sub, idopenid应用级用户标识
display_name, avatarprofile昵称、头像
跨应用隐私保护

sub应用级别的用户标识。同一用户在不同应用中会获得不同的 sub 值。这是设计如此,不是 bug。请勿尝试通过 sub 跨应用关联用户。

第五步:刷新 Token

Access Token 过期后(1 小时),使用 Refresh Token 获取新的 Token:

POST https://id.caellab.com/oauth/token
Content-Type: application/x-www-form-urlencoded

grant_type=refresh_token
&refresh_token=YOUR_REFRESH_TOKEN
&client_id=YOUR_CLIENT_ID
&client_secret=YOUR_CLIENT_SECRET

响应

{
"access_token": "NEW_ACCESS_TOKEN",
"token_type": "Bearer",
"expires_in": 3600,
"refresh_token": "NEW_REFRESH_TOKEN",
"scope": "openid"
}
Refresh Token 轮换

每次使用 Refresh Token 刷新后:

  • 旧的 Refresh Token 立即失效
  • 返回全新的 Access Token + Refresh Token
  • 请保存新的 Refresh Token,丢弃旧的

第六步(可选):撤销 Token

用户退出或需要强制下线时,撤销 Token:

POST https://id.caellab.com/oauth/revoke
Content-Type: application/x-www-form-urlencoded

token=ACCESS_TOKEN_OR_REFRESH_TOKEN

撤销后 Token 立即失效。响应始终返回 {} 和状态码 200。

代码示例

PHP

// 1. 构造授权 URL
$authUrl = 'https://id.caellab.com/oauth/authorize?' . http_build_query([
'response_type' => 'code',
'client_id' => 'your_client_id',
'redirect_uri' => 'https://yourapp.com/callback',
'scope' => 'openid',
'state' => bin2hex(random_bytes(16)),
]);

// 2. 回调处理 - 用 code 换 token
$code = $_GET['code'] ?? '';
$tokenRes = json_decode(file_get_contents(
'https://id.caellab.com/oauth/token',
false,
stream_context_create([
'http' => [
'method' => 'POST',
'header' => 'Content-Type: application/x-www-form-urlencoded',
'content' => http_build_query([
'grant_type' => 'authorization_code',
'code' => $code,
'redirect_uri' => 'https://yourapp.com/callback',
'client_id' => 'your_client_id',
'client_secret' => 'your_client_secret',
]),
],
])
));

// 3. 获取用户信息
$userInfo = json_decode(file_get_contents(
'https://id.caellab.com/api/userinfo',
false,
stream_context_create([
'http' => [
'header' => "Authorization: Bearer {$tokenRes->access_token}",
],
])
));

echo "欢迎, 用户ID: " . htmlspecialchars($userInfo->sub);

JavaScript (Node.js)

// 1. 用 code 换 token(服务端调用)
const tokenRes = await fetch('https://id.caellab.com/oauth/token', {
method: 'POST',
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
body: new URLSearchParams({
grant_type: 'authorization_code',
code: authorizationCode,
redirect_uri: 'https://yourapp.com/callback',
client_id: 'your_client_id',
client_secret: 'your_client_secret',
}),
});
const { access_token, refresh_token } = await tokenRes.json();

// 2. 获取用户信息
const userRes = await fetch('https://id.caellab.com/api/userinfo', {
headers: { Authorization: `Bearer ${access_token}` },
});
const user = await userRes.json();

// 3. Token 过期时刷新
async function refreshToken(rt) {
const res = await fetch('https://id.caellab.com/oauth/token', {
method: 'POST',
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
body: new URLSearchParams({
grant_type: 'refresh_token',
refresh_token: rt,
client_id: 'your_client_id',
client_secret: 'your_client_secret',
}),
});
return res.json(); // 返回新的 access_token + refresh_token
}

cURL

# 1. 授权(浏览器打开)
open "https://id.caellab.com/oauth/authorize?response_type=code&client_id=YOUR_ID&redirect_uri=YOUR_URI&scope=openid&state=$(openssl rand -hex 16)"

# 2. 换 token
curl -X POST https://id.caellab.com/oauth/token \
-d "grant_type=authorization_code" \
-d "code=AUTH_CODE" \
-d "redirect_uri=YOUR_URI" \
-d "client_id=YOUR_ID" \
-d "client_secret=YOUR_SECRET"

# 3. 获取用户信息
curl -H "Authorization: Bearer ACCESS_TOKEN" \
https://id.caellab.com/api/userinfo