受信第三方登录配置
本文说明如何在 SwanLab 私有化环境中配置受信第三方 Provider,以及第三方平台如何签发短期 JWT 并发起 SwanLab 登录。
TIP
适用场景: 希望将 SwanLab 集成到内部训练平台,用户在内部训练平台登录后,可以实现 SwanLab 私有化服务的「无感免登录跳转」。
一、功能简介
“受信第三方”(TRUSTED)是 SwanLab 为受信第三方平台提供的简化登录方式,适用于第三方平台 没有 OAuth2、OIDC 或 SAML2 IdP 能力,但能够自行认证用户并使用非对称密钥签发 JWT 的场景。
受信第三方登录由第三方平台发起:
- 用户已在第三方平台完成登录。
- 第三方平台后端为当前用户签发短期、一次性的 JWT。
- 第三方前端通过新窗口向 SwanLab 认证服务提交 JWT(即携带凭证访问 SwanLab 私有化服务)。
- SwanLab 使用 Provider 中配置的公钥验证 JWT,并读取第三方用户 ID 和用户名。
- 已绑定的第三方用户直接登录;未绑定用户自动创建 SwanLab 账号、建立绑定关系并登录。
受信第三方登录与其他 SSO 协议存在以下区别:
- 仅适用于私有化环境。
- 只支持登录,不支持绑定或 Provider 测试。
- 不会出现在 SwanLab 公共登录方式列表中。
- 不调用 SSO Redirect 接口,由第三方平台直接向受信第三方 callback 接口提交 JWT。
- 第三方平台不能指定
state、action或 SwanLab 回调地址。
角色分工
受信第三方登录的配置与接入需要 SwanLab 系统管理员 与 第三方平台研发人员 的协作,各角色职责如下:
| 角色 | 任务分工 |
|---|---|
| SwanLab 系统管理员 | 确认访问地址、后台配置 Provider、配置公钥与字段映射 |
| 第三方平台研发人员 | 生成签名密钥对、后端实现 JWT 签发、前端实现弹窗与表单提交 |
前置条件
在进入登录跳转开发与联调前,必须先由 SwanLab 系统管理员完成域名确认、 受信第三方 Provider 的创建与启用。平台研发人员据此获取 Provider 名称(用于拼装 Callback URL)与确认 第三方平台标识(iss)。
二、使用前准备
开始配置前,请确认:
- 已部署支持受信第三方登录的私有化版本。
- 已按照value 配置说明完成
global.settings.host配置。 - 第三方平台后端可以安全保存 JWT 私钥。
- 第三方平台和 SwanLab 服务器均已进行时间同步。
- 「建议」SwanLab 私有化服务域名使用 HTTPS 提供服务。
2.1 配置 SwanLab 外部访问地址
由于受信第三方平台登录完成后,需要跳转到 SwanLab 私有化服务域名下的 /sso 子路由。该地址必须由私有化部署配置确定,不能从第三方请求中读取。
在私有化部署脚本使用的 values.yaml 中配置 global.settings.host:
global:
settings:
host: https://swanlab.example.com
...global.settings.host 应填写用户通过浏览器访问 SwanLab 时使用的网关外部 URL:
- 应包含
http://或https://,生产环境必须使用 HTTPS。 - 不要填写
/api/auth、/sso或受信第三方 callback 路径。
示例:
✅ 正确:https://swanlab.example.com
❌ 错误:https://swanlab.example.com/api/auth
❌ 错误:https://swanlab.example.com/sso- 如果已经配置过域名,则该步骤可以跳过;
- 如果配置完成,需要更新部署服务使全局配置生效
helm upgrade swanlab-self-hosted <PATH_TO_SELF_HOSTED_CHART> -f value.yaml -n <YOUR_NAMESPACE>三、配置受信第三方 Provider
3.1 生成签名密钥
受信第三方登录使用非对称签名,需要在可信的服务器上,先生成一对公私钥,再配置验证方(Provider):
- 第三方平台保存私钥,用于签发 JWT。
- SwanLab Provider 保存公钥,用于验证 JWT。
执行角色与执行环境
- 执行角色:由第三方平台研发人员(或企业安全团队)执行。
- 执行环境:在第三方平台后端的安全受信任环境(如生产服务器、运维堡垒机或企业密钥管理系统 KMS)中执行,严禁在客户端、浏览器或前端代码中执行或保存私钥。
推荐使用 RSA 2048 位密钥和 RS256 算法。在可信服务器的终端环境下,使用 OpenSSL 生成 PKCS#8 私钥和对应公钥:
openssl genpkey \
-algorithm RSA \
-pkeyopt rsa_keygen_bits:2048 \
-out trusted-private.pem
openssl pkey \
-in trusted-private.pem \
-pubout \
-out trusted-public.pem生成后:
trusted-private.pem私钥,需要严格保存 (如密钥管理系统等),用于签发 JWT Token;trusted-public.pem公钥,用于填写到 SwanLab 管理后台 -> 受信第三方 Provider -> JWT 公钥字段。- 不要通过代码仓库、前端环境变量、浏览器存储或日志保存私钥。
受信第三方登录同时支持以下算法:
- RSA:
RS256、RS384、RS512 - ECDSA:
ES256、ES384、ES512
不支持 HS256 等使用共享密钥的 HMAC 算法。
3.2 创建验证方
- 使用 SwanLab 管理员账号进入 「身份验证」 管理页面,选择 「添加验证方」->「受信第三方登录」。

- 填写基础配置
| 字段 | 是否必填 | 说明 |
|---|---|---|
| 名称 | 是 | 受信第三方Provider 唯一标识,最多 25 个字符,只允许字母、数字、下划线(_)和连字符(-)。该值会出现在 callback URL 中,创建后建议不要修改 |
| 展示名称 | 是 | 受信第三方 Provider 的管理和登录流程展示名称,不会作为公共登录按钮显示,仅为管理使用 |

- 填写受信第三方配置
| 字段 | 是否必填 | 说明 |
|---|---|---|
| 受信第三方平台标识 | 是 | 受信第三方平台的稳定标识,对应 JWT 的 iss,两者必须完全一致,否则后续校验将不通过 |
| JWT 公钥 | 是 | 用于验证 JWT 签名的 RSA 或 ECDSA PEM 公钥,应包含完整的 BEGIN PUBLIC KEY 和 END PUBLIC KEY |

- 配置用户字段映射
用户字段映射决定了 SwanLab 从 JWT payload 的哪些字段中读取第三方用户身份
| 字段 | 是否必填 | 推荐值 | 说明 |
|---|---|---|---|
| 用户唯一 ID 字段 | 是 | sub | JWT 解码后的 payload 中,第三方用户稳定且唯一的 ID 对应字段 |
| 用户名字段 | 是 | username | JWT 解码后的 payload 中,第三方用户名对应字段,首次自动创建 SwanLab 账号时作为默认用户名 |

使用推荐配置时,JWT payload 至少应包含 sub 和 username 两个 key 字段,例如:
{
"sub": "external-user-123", // 标准字段,表示用户唯一标识
"username": "alice", // 自定义字段,通常用于表示展示用户名
...
}字段映射可以使用其他名称。例如,字段配置为:
用户唯一 ID 字段:employee_id
用户名字段:login_name则在业务系统的 JWT payload 中,必须包含 employee_id 和 login_name 两个 key:
{
"employee_id": "employee-123", // employee_id 为唯一 ID 身份标识
"login_name": "alice" // login_name 为用于展示的用户名
...
}用户唯一 ID 应具有唯一性,且在用户更换名称、邮箱或组织后,仍保持不变。
若希望 首次登录时,用户无需修改用户名,JWT payload 中,用户名字段应保持如下性质:
- 只包含字母、数字、下划线和连字符,且长度不超过 25 个字符。
- 此用户名未被其他 SwanLab 用户占用。
如果用户名不合法或已经存在,SwanLab 会在新窗口中要求用户修改用户名后再创建账号。
- 启用 Provider
创建受信第三方 Provider 后,在 Provider 列表中将状态切换为 「已启用」。
受信第三方 Provider 不会出现在 SwanLab 普通登录页面,也不会提供 Provider 测试按钮或登录入口 Logo 配置。排序值只影响管理列表中的 Provider 顺序。第三方平台必须通过 callback URL 主动发起登录。

四、第三方平台签发 JWT
WARNING
📚 JWT payload 字段说明参考: https://www.jwt.io/introduction
4.1 JWT 必填内容
JWT protected header 示例:
{
"alg": "RS256", // 签名算法
"typ": "JWT" // 令牌类型
}JWT payload 示例:
{
"iss": "partner-platform", // 签发者(平台标识)
"sub": "external-user-123", // 用户唯一标识
"username": "alice", // 用户名
"iat": 1784000000, // 签发时间(Unix 秒级时间戳)
"exp": 1784000060, // 过期时间(Unix 秒级时间戳)
"jti": "e39f79d8-faf5-4da8-8fc5-f9115d2e9568" // JWT 凭证自身唯一 ID(防重放)
}字段要求:
| Key | 语义 | 是否必填 | 说明 |
|---|---|---|---|
iss | 签发者(平台标识) | 是 | 必须与 SwanLab 管理后台登录配置的 「受信第三方平台标识」 字段完全一致 |
iat | 签发时间 | 是 | JWT 签发时间,Unix 秒级时间戳,不能晚于请求到达 SwanLab 时的当前时间 |
exp | 过期时间 | 是 | JWT 过期时间,Unix 秒级时间戳,必须晚于 iat,并且 exp - iat <= 300 秒 |
jti | 凭证唯一 ID | 是 | 防重放唯一标识,必须是非空字符串,每次签发都应生成全新随机值(推荐使用 UUID) |
sub | 用户唯一标识 | 是 | 默认 sub,由 Provider 的 「用户唯一 ID 标识字段」决定,值必须稳定且唯一标识该用户 |
username | 用户名 | 是 | 默认 username,由 Provider 的「用户名字段」决定,值不能为空 |
nbf | 生效时间 | 否 | 若提供,则 JWT 在此时间前不可用,且不能晚于 SwanLab Auth 服务的当前时间戳 |
推荐将 JWT 有效期设置为 60 秒。Auth 接受的最大签发有效期为 300 秒。
同一 Provider、三方平台标识和 jti 组合只能成功使用一次。重复提交同一个 JWT 会返回凭证已使用错误;请求失败后需要重新签发新的 JWT,不能重复使用旧 token。
4.2 在第三方后端签发 JWT
WARNING
⚠️ 注意:以下示例代码为受信第三方平台后端逻辑示例,私钥需要存储在第三方平台后端服务可以访问的环境中
签发 JWT 的逻辑在受信第三方平台后端服务中,以下使用 Node.js 和 jose 作为示例后端代码:
import { randomUUID } from "node:crypto";
import { readFile } from "node:fs/promises";
import { importPKCS8, SignJWT } from "jose";
const algorithm = "RS256";
const issuer = "partner-platform";
// 示例代码的私钥存放在本地
// 生产环境请确保后端服务能够从安全环境中访问到私钥
const privateKeyPEM = await readFile("./trusted-private.pem", "utf8");
const privateKey = await importPKCS8(privateKeyPEM, algorithm);
export async function issueSwanLabToken(user: { id: string; username: string }) {
const now = Math.floor(Date.now() / 1000);
return new SignJWT({
sub: user.id,
username: user.username,
})
.setProtectedHeader({ alg: algorithm, typ: "JWT" })
.setIssuer(issuer)
.setIssuedAt(now)
.setExpirationTime(now + 60)
.setJti(randomUUID())
.sign(privateKey);
}如果在 SwanLab 上配置身份验证方时使用的用户信息映射字段不是
sub和username,应同步修改SignJWTpayload 中的字段名称。受信第三方平台需要确认当前用户已经登录后,才能签发 JWT。不可允许浏览器任意指定第三方用户 ID 或用户名后请求签发。
五、第三方平台发起登录
5.1 Callback 地址
受信第三方平台的回调地址格式如下所示:
https://<SwanLab 外部访问地址>/api/auth/sso/trusted/callback/<Provider 名称>例如,SwanLab 私有化服务域名为 https://swanlab.example/com,受信第三方验证方 Provider 名称为 partner-platform:
https://swanlab.example.com/api/auth/sso/trusted/callback/partner-platform回调请求体要求如下:
- 请求方法必须为
POST。 - Content-Type 推荐使用
application/x-www-form-urlencoded。 - 表单字段名必须为
token。 - 整个表单请求体大小不能超过 16 KiB
- token 不得放在 URL query 参数中。
5.2 使用隐藏表单打开 SwanLab
前端运行逻辑
本示例代码为第三方平台前端逻辑,运行在用户浏览器中,负责在用户点击时预打开空白标签页、请求第三方后端生成凭据,并通过隐藏表单向 SwanLab 提交 Token 发起免登。
第三方平台前端获取刚签发的 JWT 后,应先同步打开空白窗口,再通过隐藏表单向该窗口提交 token。先打开窗口可以避免异步请求完成后被浏览器当作弹窗拦截。
function submitToken(token: string, target: string) {
const provider = "partner-platform";
const form = document.createElement("form");
const input = document.createElement("input");
form.method = "POST";
form.action = `https://swanlab.example.com/api/auth/sso/trusted/callback/${encodeURIComponent(provider)}`;
form.enctype = "application/x-www-form-urlencoded";
form.target = target;
form.hidden = true;
input.type = "hidden";
input.name = "token";
input.value = token;
form.append(input);
document.body.append(form);
try {
form.submit();
} finally {
form.remove();
}
}
export async function startSwanLabLogin() {
const target = `swanlab-trusted-${crypto.randomUUID()}`;
// 必须使用 window.open 打开
const popup = window.open("", target);
if (!popup) throw new Error("浏览器阻止了新窗口,请允许本站打开弹窗");
try {
// 该接口属于第三方平台,由第三方校验当前登录用户并签发 JWT
const response = await fetch("/api/swanlab/trusted-token", {
method: "POST",
credentials: "same-origin",
});
if (!response.ok) throw new Error("获取 SwanLab 登录凭证失败");
const { token } = (await response.json()) as { token: string };
submitToken(token, target);
} catch (error) {
popup.close();
throw error;
}
}推荐的按钮处理流程:
- 用户请求访问 SwanLab。
- 前端立即调用
window.open('', target)创建窗口。 - 签发当前用户的短期 JWT。
- 获取成功后使用隐藏表单向新窗口提交 JWT。
- 获取或提交失败时关闭空白窗口,并在第三方平台显示错误。
注意
必须使用 window.open 打开新窗口,因为 SwanLab 在登录失败时提供“关闭”按钮,点击后自动关闭由 window.open 打开的 SwanLab 窗口。
不允许使用以下方式传递 token:
https://swanlab.example.com/api/auth/sso/trusted/callback/partner-platform?token=<jwt>URL 可能被浏览器历史记录、访问日志、代理日志和 Referer 保存。
5.3 登录后的账号行为
SwanLab 根据「验证方 Provider + 第三方用户唯一 ID」维护第三方用户与 SwanLab 用户的绑定关系:
- 已存在绑定:直接创建 SwanLab 登录态并进入 SwanLab。
- 不存在绑定:使用映射出的用户名创建 SwanLab 用户(席位不足时将提示失败),建立绑定关系后登录。
- 用户名不合法或重复:在 SwanLab 新窗口中要求用户修改用户名。
- 已绑定 SwanLab 用户被禁用:拒绝登录并显示账号已禁用。
登录成功后,SwanLab 默认进入用户空间。业务错误会在新窗口中显示对应信息,用户可以关闭窗口后从第三方平台重新发起登录。
六、错误说明与排查
| 错误 | 含义 | 排查建议 |
|---|---|---|
| 受信第三方登录配置不可用 | Provider 不存在、未启用、协议不是受信第三方(TRUSTED)或 SwanLab Auth 服务无法读取配置 | 检查 callback 中的 Provider 名称、Provider 状态以及 SwanLab Server/Auth 连接 |
| 登录凭证无效或已过期 | token 缺失、签名失败、算法不支持、iss 不匹配或时间字段不符合要求 | 重新签发 token;检查公私钥、算法、iss、iat、exp 和服务器时间 |
| 登录凭证已经使用 | 相同的 jti 已经成功提交 | 每次登录生成新的 jti 和 JWT,不要重试旧 token |
| 无法获取完整用户信息 | 用户字段映射对应的 Claim 缺失、为空或类型不支持 | 检查 Provider 映射和 JWT payload,确保用户 ID、用户名均为非空字符串 |
| 登录服务暂时不可用 | Auth 无法写入 JWT 消费记录或 Redis 暂时不可用 | 检查 Auth Redis 连接,恢复后重新签发 JWT |
常见问题:
6.1 提交后返回 500
检查私有化部署的 values.yaml 是否配置了正确的 global.settings.host,以及修改后是否已经重新执行部署或升级脚本。该值必须与用户实际访问 SwanLab 的网关外部 URL 一致。
6.2 提示 token 无效
依次检查:
- JWT
alg是否属于 SwanLab 支持的 RSA/ECDSA 算法。 - Provider 公钥是否与第三方签名私钥配对。
- Provider 三方平台标识是否与 JWT
iss完全一致。 iat、exp是否为 Unix 秒级时间戳,而不是毫秒时间戳。exp是否晚于iat,且两者相差不超过 300 秒。- 第三方服务器与 SwanLab Auth 的系统时间是否同步。
- JWT 是否包含非空且未使用过的
jti。
6.3 用户首次登录时仍需填写用户名
检查 JWT 中映射出的用户名是否:
- 只包含字母、数字、下划线和连字符。
- 不超过 25 个字符。
- 未被其他 SwanLab 用户使用。
如果第三方用户名不能满足 SwanLab 用户名要求,可以在第三方平台签发 JWT 时生成一个符合要求且稳定的 SwanLab 用户名字段,并将 Provider 的“用户名字段”映射到该 Claim。
6.4 修改公钥后旧 token 无法登录
Provider 当前只保存一份 JWT 公钥。更新公钥后,使用旧私钥签发的 token 会立即失效。轮换密钥时应协调 Provider 配置更新时间和第三方平台签名密钥切换时间,并让旧 token 尽快过期。
七、安全注意事项
- 受信第三方登录只应部署在明确受信的私有化环境中。
- 私钥必须保存在第三方平台后端或专用密钥管理系统中,不得发送到 SwanLab 或浏览器。
- JWT 是短期登录凭证,应按密码同等级别保护,不得记录完整 token。
- JWT 是 Bearer Credential,首个成功提交者会以 JWT 中的用户身份继续登录;只能在已认证用户明确点击进入 SwanLab 后即时签发。
- JWT 推荐 60 秒过期,最大签发区间为 300 秒。
- 每次签发必须生成新的
jti,JWT 被消费后不能重用。 - 第三方平台必须在确认当前用户已经登录后签发 JWT,不能信任浏览器提交的用户身份参数。
- 第三方平台的 JWT 签发接口应使用自身的会话鉴权、CSRF 防护和限流策略。
- 第三方平台不得把私钥、JWT 或用户敏感信息写入前端日志、分析平台或错误上报系统。
- SwanLab 和第三方平台必须启用 HTTPS,并保持系统时间同步。
- Provider 的用户 ID 映射应使用稳定、不可复用的第三方用户标识。