文檔中心 / OntiCards API SSO單點登入接入指南(JWT模式)

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
}

欄位說明

欄位類別型必填說明
usernamestring用戶的唯一識別,不能為空
user_idstring客戶系統中的用戶ID,不能為空
nicknamestring用戶昵稱
emailstring用戶邮箱
sourcestring来源識別,用于區分不同系統,預設 default
iatnumberToken簽發時間(Unix時間戳)
expnumberToken過期時間(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系統會:

  1. 接收JWT Token → 获取URL中的token參數
  1. 解析Header → 获取演算法資訊
  1. 驗證簽名 → 用共享密鑰驗證token是否被篡改
  1. 檢查過期 → 驗證exp是否有效
  1. 提取Payload → 获取username、user_id等用戶資訊
  1. 查詢用戶 → 根據 idp_user_id + idp_source 查找已存在用戶
  1. 創建/關联 → 新用戶自動創建,老用戶關联登入
  1. 生成Token → 生成OntiCards自己的登入Token
  1. 跳轉回調 → 携带新Token跳轉到 redirect_url

八、錯誤碼說明

HTTP狀態碼error欄位原因
400缺少token参数URL中沒有傳token
400token中缺少必要的用户信息Payload中username或user_id為空
401token已过期Token的exp已過期
401token无效簽名驗證失败(密鑰不匹配或內容被篡改)

九、組態彙總

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技術支持。