OntiCards API SSO單點登入接入指南(JWT模式)
概述
本文檔描述第三方系統如何通過JWT Token方式接入OntiCards單點登入系統。
一、什麼是JWT Token
JWT(JSON Web Token)是一種開放標準(RFC 7519),用于在各方之間安全地傳輸資訊。JWT由三部分組成,用點號分隔:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6InpoYW5nX3NhbiIsInVzZXJfaWQiOiJTWVNfVVNFUl8wMDEifQ.signature
|______________|.|__________________________________________|.______________|
Header | Payload | Signature
头部 | 用户数据 | 签名
| 部分 | 名稱 | 作用 | 示例內容 |
|---|---|---|---|
| 第一部分 | Header(頭部) | 聲明演算法和類別型 | {"alg":"HS256","typ":"JWT"} |
| 第二部分 | Payload(負載) | 存放實际的用戶數據 | {"username":"zhang_san","user_id":"001",...} |
| 第三部分 | Signature(簽名) | 用密鑰對前两部分簽名,確保不被篡改 | SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c |
二、快速開始
2.1 客戶需要準備的組態
| 組態項 | 說明 | 示例值 |
|---|---|---|
| SSO共享密鑰 | 用于JWT簽名的密鑰,两端必須一致 | 真实共享秘钥 |
| OntiCards API地址 | 我們的SSO登入介面地址 | https://OntiCards【后端】真实ip:端口(或域名) |
| 回調跳轉地址 | 登入成功後跳轉的前端頁面 | https://OntiCards【前端】真实ip:端口(或域名)/overview |
2.2 整體流程
1. 客户后端生成 JWT Token
↓
2. 拼接 SSO 登录 URL(带上 token 和 redirect_url)
↓
3. 用户浏览器跳转到我们的 SSO 接口
↓
4. 我们验证 JWT、创建/关联用户、生成自己的 Token
↓
5. 浏览器跳转到 redirect_url,带上我们的 Token
↓
6. 客户前端接收 Token,登录完成
三、Token 生成详解(必須由客戶後端完成)
3.1 第一部分:Header(頭部)
固定格式,聲明使用HS256演算法:
{
"alg": "HS256",
"typ": "JWT"
}
然後對這個JSON物件進行 Base64URL編碼。
3.2 第二部分:Payload(負載/用戶數據)
這是最重要的部分,包含要傳递的用戶資訊:
{
"username": "zhang_san",
"user_id": "SYS_USER_001",
"nickname": "张三",
"email": "zhangsan@example.com",
"source": "your_app",
"iat": 1713000000,
"exp": 1713000600
}
欄位說明:
| 欄位 | 類別型 | 必填 | 說明 |
|---|---|---|---|
username | string | ✅ | 用戶的唯一識別,不能為空 |
user_id | string | ✅ | 客戶系統中的用戶ID,不能為空 |
nickname | string | ❌ | 用戶昵稱 |
email | string | ❌ | 用戶邮箱 |
source | string | ❌ | 来源識別,用于區分不同系統,預設 default |
iat | number | ❌ | Token簽發時間(Unix時間戳) |
exp | number | ✅ | Token過期時間(Unix時間戳),建議設置為5分钟後 |
然後對這個JSON物件進行 Base64URL編碼。
3.3 第三部分:Signature(簽名)
將第一部分和第二部分用點號連接,然後用共享密鑰對這段字串進行簽名:
签名字符串 = Header_base64 + "." + Payload_base64
签名 = HMAC-SHA256(签名字符串, 共享密钥)
3.4 最終的JWT Token
JWT Token = Header_base64 + "." + Payload_base64 + "." + Signature_base64
四、各語言生成JWT示例
4.1 Python 示例
import jwt
from datetime import datetime, timedelta, timezone
# 客户需要配置的共享密钥(需要提供给OntiCards)
SECRET_KEY = "your_shared_secret_key"
# Payload(用户数据)
payload = {
"username": "zhang_san",
"user_id": "SYS_USER_001",
"nickname": "张三",
"email": "zhangsan@example.com",
"source": "your_app",
"iat": datetime.now(timezone.utc),
"exp": datetime.now(timezone.utc) + timedelta(minutes=5) # 5分钟后过期
}
# 生成 JWT Token
token = jwt.encode(payload, SECRET_KEY, algorithm="HS256")
print(token)
# 输出类似:eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6InpoYW5nX3NhbiJ9.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c
4.2 Java 示例
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.security.Keys;
import java.nio.charset.StandardCharsets;
import java.util.Date;
import java.util.Map;
String secretKey = "your_shared_secret_key";
Map<String, Object> payload = Map.of(
"username", "zhang_san",
"user_id", "SYS_USER_001",
"nickname", "张三",
"email", "zhangsan@example.com",
"source", "your_app"
);
String token = Jwts.builder()
.claims(payload)
.issuedAt(new Date())
.expiration(new Date(System.currentTimeMillis() + 5 * 60 * 1000)) // 5分钟后过期
.signWith(Keys.hmacShaKeyFor(secretKey.getBytes(StandardCharsets.UTF_8)))
.compact();
System.out.println(token);
4.3 Node.js 示例
const jwt = require('jsonwebtoken');
const secretKey = 'your_shared_secret_key';
const payload = {
username: 'zhang_san',
user_id: 'SYS_USER_001',
nickname: '张三',
email: 'zhangsan@example.com',
source: 'your_app'
};
const token = jwt.sign(payload, secretKey, {
algorithm: 'HS256',
expiresIn: '5m' // 5分钟后过期
});
console.log(token);
4.4 纯前端生成(仅供測試使用)
// ⚠️ 仅用于测试,生产环境Token生成必须在后端完成!
async function generateJWT(payload, secret) {
// Header
const header = { "alg": "HS256", "typ": "JWT" };
const headerB64 = btoa(JSON.stringify(header))
.replace(/\+/g, '-').replace(/\//g, '_').replace(/=/g, '');
// Payload
const payloadB64 = btoa(JSON.stringify(payload))
.replace(/\+/g, '-').replace(/\//g, '_').replace(/=/g, '');
// Signature
const signatureInput = headerB64 + '.' + payloadB64;
const encoder = new TextEncoder();
const key = await crypto.subtle.importKey(
'raw', encoder.encode(secret),
{ name: 'HMAC', hash: 'SHA-256' },
false, ['sign']
);
const signature = await crypto.subtle.sign('HMAC', key, encoder.encode(signatureInput));
const signatureB64 = btoa(String.fromCharCode(...new Uint8Array(signature)))
.replace(/\+/g, '-').replace(/\//g, '_').replace(/=/g, '');
return signatureInput + '.' + signatureB64;
}
// 使用示例
const payload = {
username: 'test_user',
user_id: 'TEST_001',
nickname: '测试用户',
exp: Math.floor(Date.now() / 1000) + 300 // 5分钟后过期
};
generateJWT(payload, 'your_secret_key').then(token => console.log(token));
五、拼接登入URL並跳轉
5.1 構造URL
{OntiCards API地址}/sso/login?token={JWT Token}&redirect_url={回调地址}
示例:
https://api.onticards.com/sso/login?token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...&redirect_url=https://your-app.com/dashboard
5.2 跳轉方式
方式1:直接跳轉(推薦)
// 生成Token后跳转
const ssoUrl = `${API_BASE}/sso/login?token=${encodeURIComponent(token)}&redirect_url=${encodeURIComponent(FRONTEND_URL)}`;
window.location.href = ssoUrl;
方式2:新視窗打開
window.open(`${API_BASE}/sso/login?token=${encodeURIComponent(token)}`, '_blank');
六、回調地址接收Token
登入成功後,瀏覽器會跳轉到:
https://frontend.onticards.com/overview?access_token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
6.1 前端接收Token的代碼
// 从URL获取 access_token 参数
function getAccessToken() {
const params = new URLSearchParams(window.location.search);
return params.get('access_token');
}
// 存储Token
const token = getAccessToken();
if (token) {
localStorage.setItem('access_token', token);
// 可选:解析Token获取用户信息(不推荐用于安全验证,仅用于显示)
const parts = token.split('.');
if (parts.length === 3) {
const payload = JSON.parse(atob(parts[1]));
console.log('登录用户:', payload.nickname || payload.username);
console.log('用户角色:', payload.role);
}
// 清理URL中的token参数(防止token泄露)
window.history.replaceState({}, document.title, window.location.pathname);
}
6.2 後续請求携带Token
// 后续API请求时在Header中携带Token
fetch('/api/your-endpoint', {
headers: {
'Authorization': 'Bearer ' + localStorage.getItem('access_token')
}
});
七、用戶創建/登入逻輯
当用戶首次通過SSO訪問時,OntiCards系統會:
- 接收JWT Token → 获取URL中的token參數
- 解析Header → 获取演算法資訊
- 驗證簽名 → 用共享密鑰驗證token是否被篡改
- 檢查過期 → 驗證exp是否有效
- 提取Payload → 获取username、user_id等用戶資訊
- 查詢用戶 → 根據
idp_user_id+idp_source查找已存在用戶
- 創建/關联 → 新用戶自動創建,老用戶關联登入
- 生成Token → 生成OntiCards自己的登入Token
- 跳轉回調 → 携带新Token跳轉到 redirect_url
八、錯誤碼說明
| HTTP狀態碼 | error欄位 | 原因 |
|---|---|---|
| 400 | 缺少token参数 | URL中沒有傳token |
| 400 | token中缺少必要的用户信息 | Payload中username或user_id為空 |
| 401 | token已过期 | Token的exp已過期 |
| 401 | token无效 | 簽名驗證失败(密鑰不匹配或內容被篡改) |
九、組態彙總
9.1 OntiCards提供
| 項目 | 值 | 用途 |
|---|---|---|
| SSO登入介面 | https://api.onticards.com{OntiCards API地址}/sso/login | 客戶跳轉的地址 |
| SSO共享密鑰 | 客戶提供,我們存储 | 用于驗證JWT簽名 |
9.2 客戶提供
| 項目 | 示例值 | 說明 |
|---|---|---|
| 共享密鑰 | K7x#9mP$2nL5@qR8 | 建議64位以上的隨機字串 |
| 回調地址 | https://frontend.onticards.com/overview | 登入成功後跳轉的前端頁面 |
9.3 環境變數組態
在OntiCards服務端組態:
SSO_SECRET_KEY=客户提供的共享密钥
十、測試驗證
10.1 使用測試頁面
OntiCards提供了本地測試頁面:
- SSO測試中心:
http://localhost:9103/static/sso_test.html
- 回調測試頁面:
http://localhost:9103/static/sso_callback.html
10.2 對接檢查清單
- 生成JWT Token(驗證三部分結構:Header.Payload.Signature)
- Payload中包含必填欄位:username、user_id、exp
- Token使用HS256演算法簽名
- 拼接SSO登入URL
- 測試跳轉流程
- 驗證回調頁面能接收access_token
- 確認Token有效期(建議5分钟)
- 生產環境使用強密鑰,不要使用示例密鑰
十一、联系方式
如有問題,請联系OntiCards技術支持。