跳转到内容

受信第三方登录配置 ​

本文说明如何在 SwanLab 私有化环境中配置受信第三方 Provider,以及第三方平台如何签发短期 JWT 并发起 SwanLab 登录。

TIP

适用场景: 希望将 SwanLab 集成到内部训练平台,用户在内部训练平台登录后,可以实现 SwanLab 私有化服务的「无感免登录跳转」。

一、功能简介 ​

“受信第三方”(TRUSTED)是 SwanLab 为受信第三方平台提供的简化登录方式,适用于第三方平台 没有 OAuth2、OIDC 或 SAML2 IdP 能力,但能够自行认证用户并使用非对称密钥签发 JWT 的场景。

受信第三方登录由第三方平台发起:

  1. 用户已在第三方平台完成登录。
  2. 第三方平台后端为当前用户签发短期、一次性的 JWT。
  3. 第三方前端通过新窗口向 SwanLab 认证服务提交 JWT(即携带凭证访问 SwanLab 私有化服务)。
  4. SwanLab 使用 Provider 中配置的公钥验证 JWT,并读取第三方用户 ID 和用户名。
  5. 已绑定的第三方用户直接登录;未绑定用户自动创建 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:

yaml
global:
  settings:
    host: https://swanlab.example.com
...

global.settings.host 应填写用户通过浏览器访问 SwanLab 时使用的网关外部 URL:

  • 应包含 http:// 或 https://,生产环境必须使用 HTTPS。
  • 不要填写 /api/auth、/sso 或受信第三方 callback 路径。

示例:

text
✅ 正确:https://swanlab.example.com
❌ 错误:https://swanlab.example.com/api/auth
❌ 错误:https://swanlab.example.com/sso
  • 如果已经配置过域名,则该步骤可以跳过;
  • 如果配置完成,需要更新部署服务使全局配置生效
bash
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 私钥和对应公钥:

bash
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 创建验证方 ​

  1. 使用 SwanLab 管理员账号进入 「身份验证」 管理页面,选择 「添加验证方」->「受信第三方登录」。

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

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

  1. 配置用户字段映射

用户字段映射决定了 SwanLab 从 JWT payload 的哪些字段中读取第三方用户身份

字段是否必填推荐值说明
用户唯一 ID 字段是subJWT 解码后的 payload 中,第三方用户稳定且唯一的 ID 对应字段
用户名字段是usernameJWT 解码后的 payload 中,第三方用户名对应字段,首次自动创建 SwanLab 账号时作为默认用户名

使用推荐配置时,JWT payload 至少应包含 sub 和 username 两个 key 字段,例如:

json
{
  "sub": "external-user-123", // 标准字段,表示用户唯一标识
  "username": "alice",      //  自定义字段,通常用于表示展示用户名
  ...
}

字段映射可以使用其他名称。例如,字段配置为:

text
用户唯一 ID 字段:employee_id
用户名字段:login_name

则在业务系统的 JWT payload 中,必须包含 employee_id 和 login_name 两个 key:

json
{
  "employee_id": "employee-123", // employee_id 为唯一 ID 身份标识
  "login_name": "alice"         // login_name 为用于展示的用户名
  ...
}
  • 用户唯一 ID 应具有唯一性,且在用户更换名称、邮箱或组织后,仍保持不变。

  • 若希望 首次登录时,用户无需修改用户名,JWT payload 中,用户名字段应保持如下性质:

    • 只包含字母、数字、下划线和连字符,且长度不超过 25 个字符。
    • 此用户名未被其他 SwanLab 用户占用。

如果用户名不合法或已经存在,SwanLab 会在新窗口中要求用户修改用户名后再创建账号。

  1. 启用 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 示例:

json
{
  "alg": "RS256", // 签名算法
  "typ": "JWT" // 令牌类型
}

JWT payload 示例:

json
{
  "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 作为示例后端代码:

ts
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,应同步修改 SignJWT payload 中的字段名称。

  • 受信第三方平台需要确认当前用户已经登录后,才能签发 JWT。不可允许浏览器任意指定第三方用户 ID 或用户名后请求签发。

五、第三方平台发起登录 ​

5.1 Callback 地址 ​

受信第三方平台的回调地址格式如下所示:

text
https://<SwanLab 外部访问地址>/api/auth/sso/trusted/callback/<Provider 名称>

例如,SwanLab 私有化服务域名为 https://swanlab.example/com,受信第三方验证方 Provider 名称为 partner-platform:

text
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。先打开窗口可以避免异步请求完成后被浏览器当作弹窗拦截。

ts
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;
  }
}

推荐的按钮处理流程:

  1. 用户请求访问 SwanLab。
  2. 前端立即调用 window.open('', target) 创建窗口。
  3. 签发当前用户的短期 JWT。
  4. 获取成功后使用隐藏表单向新窗口提交 JWT。
  5. 获取或提交失败时关闭空白窗口,并在第三方平台显示错误。

注意

必须使用 window.open 打开新窗口,因为 SwanLab 在登录失败时提供“关闭”按钮,点击后自动关闭由 window.open 打开的 SwanLab 窗口。

不允许使用以下方式传递 token:

text
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 无效 ​

依次检查:

  1. JWT alg 是否属于 SwanLab 支持的 RSA/ECDSA 算法。
  2. Provider 公钥是否与第三方签名私钥配对。
  3. Provider 三方平台标识是否与 JWT iss 完全一致。
  4. iat、exp 是否为 Unix 秒级时间戳,而不是毫秒时间戳。
  5. exp 是否晚于 iat,且两者相差不超过 300 秒。
  6. 第三方服务器与 SwanLab Auth 的系统时间是否同步。
  7. 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 映射应使用稳定、不可复用的第三方用户标识。