跳到内容

IAM

受众:开发者 摘要:统一身份 IAM:统一账号 / RBAC / 菜单权限 / 按钮权限 / 数据权限 / 审计 / 在线用户 / 外部身份。

IAM 是统一身份中心。当前项目不拆 admin_useruser 两套账号体系,而是使用统一账号、统一 token、统一权限和多身份扩展。

核心思想

统一账号主体:

text
iam_user

不同登录方式、业务身份和扩展资料分别放到独立表:

text
iam_user_identity
iam_user_profile
iam_admin_profile
iam_merchant_profile
iam_developer_profile

权限与组织模型:

text
iam_role
iam_permission
iam_menu
iam_user_role
iam_role_permission
iam_role_menu

iam_tenant
iam_organization
iam_department
iam_user_department
organization_post
iam_role_data_scope

这样一个账号可以同时是普通用户、管理员、商户、开发者或运营人员,身份由角色、权限、profile 和数据范围共同表达。

用户关联键

所有用户相关数据关联只能使用稳定的用户主键:

text
user_id

不要使用用户名、邮箱、手机号、钱包地址、OAuth openId 等可变登录标识作为业务关联键。

原因:

  • iam_user.id 是唯一不可变账号身份。
  • username、email、mobile 可能会修改、解绑、换绑或禁用。
  • OAuth、钱包、Telegram、Passkey、LDAP、SAML 等外部身份都属于登录身份扩展,不是账号主体。
  • 审计、订单、通知、配置操作人、Web3 归集、支付流水等长期数据必须能在登录标识变化后继续准确关联用户。

推荐关系:

text
业务表.user_id -> iam_user.id
iam_user_identity.user_id -> iam_user.id
profile.user_id -> iam_user.id

username、email、mobile 只用于登录、检索、展示或通知投递,不作为跨表关系的事实来源。

为什么不拆账号表

不建议当前阶段拆成:

text
admin_user
user
merchant_user

原因:

  • 登录入口会重复。
  • Token 生命周期会重复。
  • 权限、审计、风控、多端登录都会重复。
  • 后续 SSO、OAuth、钱包登录、Passkey、Telegram 等外部身份难以统一。

当前阶段统一使用 Sa-Token 的 StpUtil 主链路。只有未来出现完全独立安全域或完全独立业务系统时,才考虑多账号体系。

登录方式

当前支持:

方式入口说明
账号密码POST /api/v1/auth/login通过 iam_user_identity 查询 username/email/mobile
短信验证码POST /api/v1/auth/login/sms先校验并消费验证码,再通过 mobile identity 登录
邮箱验证码POST /api/v1/auth/login/email先校验并消费验证码,再通过 email identity 登录
用户注册POST /api/v1/auth/register创建统一账号主体,并写入 username / email / mobile identity
认证能力GET /api/v1/auth/capabilities返回注册、登录等前端需要的公开能力开关
找回密码POST /api/v1/auth/password/reset通过短信或邮箱验证码校验身份后重置密码
身份提供方GET /api/v1/auth/identity/providers查询当前已启用的 Identity Provider descriptor
当前用户外部身份GET /api/v1/iam/users/me/identity-bindings查询当前用户已绑定的 OAuth、Telegram、钱包、微信、Passkey 等外部身份
身份提供方登录POST /api/v1/auth/identity/providers/{code}/login按 descriptor 的 flow 校验外部凭证载荷,校验通过后进入统一账号登录链路
身份提供方绑定POST /api/v1/auth/identity/providers/{code}/bind按 descriptor 的 flow 校验外部凭证载荷,校验通过后绑定到当前用户
身份提供方重认证POST /api/v1/auth/identity/providers/{code}/reauth按 descriptor 的 flow 校验外部凭证载荷,并确认该外部主体已属于当前用户
OAuth/api/v1/auth/oauth/{provider}/authorize/api/v1/auth/oauth/{provider}/login第三方 provider 校验通过后进入统一账号
OAuth2 授权服务器/oauth2/**/api/v1/auth/oauth2/**Sa-Token OAuth2 协议入口,默认关闭,用于给第三方系统接入本账号中心
钱包签名/api/v1/auth/wallet/challenge/api/v1/auth/wallet/loginchallenge 一次性短 TTL 缓存,验签通过后进入统一账号

短信验证码发送入口:

http
POST /api/v1/auth/verify/sms-codes
POST /api/v1/auth/verify/sms-codes/check
POST /api/v1/auth/login/sms

认证类短信验证码发送和校验都会按手机号与 IP 组合限流,验证码哈希后写入 Cache,校验成功后立即消费,并通过 Notification 的 sms 通道发送。sms-codes 是 REST 资源集合命名,POST /sms-codes 一次仍然只创建并发送一个验证码。

短信登录直接调用 POST /api/v1/auth/login/sms,入参包含 mobilecodescene、可选 countryCode / region 和可选 device。后端会先复用同一套验证码校验消费规则,再通过统一账号体系中的 mobile identity 登录并返回 LoginVO。登录不会回退直查 iam_user.mobile,避免绕过身份中心;如果历史用户缺少 mobile identity,应通过身份修复或绑定流程补齐。

手机号规范化已经接入服务端:mobile 字段会去除空格、横线、括号等常见分隔符,保留国际号码前导 +,并把 00 国际冠码转换为 +。服务端通过 libphonenumber 按默认地区 CN 解析并统一保存为 E.164,例如 13800138000 会保存为 +8613800138000。验证码缓存、限流、注册、找回密码、短信登录和绑定 / 换绑都会使用规范化后的手机号,避免 +86 138-0013-80000086 13800138000 写成两份 identity。认证短信发送 / 校验、短信登录、注册、找回密码和绑定手机号均可传入 countryCode / region,由服务端按用户选择地区合并解析。前端登录、注册、找回密码和绑定手机号页面应提供国家 / 地区选择器,默认地区可来自用户偏好、站点配置、Accept-Language 或设备环境。

如果 spring.open.iam.auth.login.verification.sms.enabled=false,短信验证码发送 / 校验资源不会注册,短信登录入口会返回“短信验证码未启用”的配置错误;账号密码、OAuth、Telegram 和钱包登录不受影响。

邮箱验证码入口:

http
POST /api/v1/auth/verify/email-codes
POST /api/v1/auth/verify/email-codes/check
POST /api/v1/auth/login/email

认证类邮箱验证码与短信验证码遵循同一边界:按邮箱和 IP 组合限流,验证码哈希后写入 Cache,校验成功后立即消费,并通过 Notification 的 mail 通道发送。邮箱验证码登录不会回退直查 iam_user.email,只信任统一身份中心里的 email identity;如果历史用户缺少 email identity,应通过身份修复或绑定流程补齐。

自助注册入口:

http
POST /api/v1/auth/register

注册会创建统一账号主体,并同步写入 usernameemailmobile 三类本地 identity。email 会统一转为小写后保存,避免大小写导致登录查找不一致。第一阶段最小入参为 usernamepasswordconfirmPasswordnicknameemailmobile 可选。请求中如果带有 code,后端会根据 mobileemail 先校验并消费对应验证码;如果不带验证码,则只按账号、密码、邮箱、手机号等基础字段创建账号。

注册前会先检查统一身份中心中同类型 usernameemailmobile 是否已存在,不回退直查 iam_user 字段。历史数据如果缺少 identity,应通过身份修复或绑定流程补齐。请求可选传入 sourcedevice,用于认证审计里的注册来源和设备记录;未传 source 时默认按 password 记录。

账号密码注册由配置控制:

yaml
spring:
  open:
    auth:
      register:
        password:
          enabled: true

默认允许普通用户通过账号密码注册。数据库配置可以覆盖同名 key。关闭后,POST /api/v1/auth/register 会直接返回“账号密码注册暂未开放”,且不会先消费短信或邮箱验证码。

注册限流由 IAM 登录限流配置统一管理:

yaml
spring:
  open:
    iam:
      auth:
        login:
          rate-limit:
            register:
              enabled: true
              identifier-max-attempts: 3
              identifier-window: 1m
              ip-max-attempts: 10
              ip-window: 1m

注册限流同时按注册标识和 IP 生效,限流 key 会哈希处理,不把用户名、邮箱、手机号或 IP 明文写入 Redis。被限流时返回统一业务码 429,并写入注册失败审计。

前端视图不要本地硬编码注册开关,应先读取:

http
GET /api/v1/auth/capabilities

响应中的 register.password.enabled 用于决定是否展示注册入口,以及提交前是否给出友好提示。响应中的 passwordPolicy 用于展示当前密码规则,默认 level=low、最少 6 位、最多 72 位、允许简单弱口令,并要求前端提供确认密码输入。

密码策略配置:

yaml
spring:
  open:
    iam:
      password:
        policy:
          level: low
          min-length: 6
          max-length: 72
          allow-weak: true
          min-character-types: 1

low 级别保持低门槛体验;medium / high / custom 可按站点安全要求增强。注册、后台创建用户、后台重置密码、当前用户修改密码、手机号 / 邮箱找回密码和安装向导创建首个管理员均复用同一套策略,避免前端和后端各自硬编码规则。

当前用户可以自助修改用户名:

http
PUT /api/v1/iam/users/me/username

请求体:

json
{
  "username": "new-name"
}

修改用户名会同步更新统一身份中心里的 username identity,避免出现展示用户名和登录标识不一致。默认限制为 365 天内最多修改 3 次;提交相同用户名不会消耗次数。限额状态写在 iam_user 的用户名修改窗口字段中,公开能力接口会返回 usernameChangePolicy 供前端展示提示。

邮箱地址、有效手机号和合法 EVM 钱包地址格式都属于需要归属验证的受保护用户名:只有目标用户已经绑定同一邮箱、手机号或钱包地址时,当前用户改名或后台用户更新才允许保存;未绑定、绑定在其他用户下或只手动输入都会被拒绝。普通用户名、非法邮箱 / 手机号 / 钱包地址格式字符串以及未启用钱包身份提供方的运行环境不受钱包策略影响。钱包签名登录自动创建用户时,会优先使用已验签的钱包地址作为用户名;如果该地址用户名已经被占用,则回退到生成的外部身份用户名。手机号 / 邮箱登录或注册仍按默认用户名规则生成用户名,不强制用户名等于手机号或邮箱,避免误伤正常注册登录流程。

用户名修改策略配置:

yaml
spring:
  open:
    iam:
      user:
        username-change:
          window-days: 365
          max-count: 3

找回密码入口:

http
POST /api/v1/auth/password/reset

找回密码必须提供 mobileemailcode 和新密码。短信找回可同时传入 countryCode / region。后端先复用短信 / 邮箱验证码校验消费逻辑,再通过 IAM identity 找到用户并重置密码;不会回退直查 iam_user.mobileiam_user.email。如果历史用户缺少对应 identity,应先通过身份修复或绑定流程补齐。

当前用户账号设置

当前登录用户账号设置统一放在 /api/v1/iam/users/me

API说明
PUT /api/v1/iam/users/me/password校验旧密码后修改当前用户密码
GET /api/v1/iam/users/me/bindings查询当前用户手机号和邮箱绑定状态
PUT /api/v1/iam/users/me/mobile校验短信验证码后绑定 / 换绑当前用户手机号
DELETE /api/v1/iam/users/me/mobile解绑当前用户手机号;如用户名等于该手机号,会自动切换为兜底用户名
PUT /api/v1/iam/users/me/email校验邮箱验证码后绑定 / 换绑当前用户邮箱
DELETE /api/v1/iam/users/me/email解绑当前用户邮箱;如用户名等于该邮箱,会自动切换为兜底用户名
POST /api/v1/iam/users/me/avatar上传当前用户头像,写入文件元数据并回写用户头像 URL
GET /api/v1/iam/users/me/profile查询当前用户自己的普通用户扩展资料
PUT /api/v1/iam/users/me/profile保存当前用户自己的普通用户扩展资料,后端强制使用当前登录用户 ID
GET /api/v1/iam/users/me/preferences查询当前用户账号偏好
PUT /api/v1/iam/users/me/preferences保存当前用户账号偏好
GET /api/v1/iam/users/me/roles查询当前用户自己的角色列表
GET /api/v1/iam/users/me/permissions查询当前用户自己的权限列表
GET /api/v1/iam/users/me/menus查询当前用户自己的菜单树

手机号和邮箱绑定变更继续复用认证验证码服务。请求中传入 mobileemailcode 和可选 scene,手机号绑定还可传入 countryCode / region。后端先校验并消费验证码,再写入 iam_user_identity。同一 identity 已属于当前用户时不会重复绑定;如果已属于其他用户,会返回 identity 已存在的业务错误。换绑会移除当前用户旧的同类型手机号 / 邮箱 identity,解绑会清空 iam_user.mobile / iam_user.email 展示字段;如果被移除的身份正好是当前用户名的归属来源,IAM 会自动把用户名切换为 user_<userId> 形式的兜底用户名并同步 username identity,防止已解绑邮箱、手机号或钱包地址继续被占用。后台身份管理的创建、更新和启停同样走这条用户服务链路,修改或停用邮箱 / 手机号 identity 时会同步展示字段并触发必要的用户名兜底;已失去归属的受保护 username 登录别名也会被清理。

当前用户 profile 接口只操作登录用户自己的 iam_user_profile,不要求 iam:profile:* 后台管理权限。普通用户端、PC 用户中心和移动端个人资料页面应优先使用 /users/me/** 自助接口,不要调用 /users/{userId}/roles/iam/users/{userId}/profile 等后台管理接口。

当前用户偏好写入 iam_user_preference,按 user 分组保存键值,例如账号消息、系统消息、待办消息、营销消息、语言、时区和主题。偏好值为空时会禁用对应记录,不物理删除,避免唯一约束冲突。

头像上传默认使用 Storage 的 public disk,并在文件元数据中记录 user-avatar 业务类型。生产环境如需裁剪、压缩、尺寸校验或私有头像策略,应在 Storage / Image 处理能力完善后继续扩展。

公开用户资料

普通用户公开读取接口只返回可展示字段,不返回邮箱、手机号等敏感登录标识:

API说明
GET /api/v1/iam/users/{id}/public-profile查询用户公开资料
GET /api/v1/iam/users/{id}/activities查询用户公开动态聚合出口

公开动态由 IAM 模块提供聚合出口,底层通过 core UserActivitySource 聚合可选业务来源。没有来源时返回真实空分页;当前 Blog 会贡献已发布文章,Forum 会贡献公开可见主题,CMS 会贡献当前用户创建的已发布内容,Mall 会贡献当前用户的已发布商品评价。响应返回稳定 idtypedisplayTypetitlesummarytargetPathurloccurredAtcreatedAt 字段;新增业务来源继续按同一 contract 扩展,不需要让业务模块之间建立 Maven 强依赖。

联系人和用户关系

用户侧联系人、关注、粉丝、拉黑、好友申请和资料卡关系状态统一放在 IAM 用户域:

API说明
GET /api/v1/iam/contacts查询当前用户好友联系人列表
GET /api/v1/iam/contacts/search搜索联系人候选
GET /api/v1/iam/contacts/summary查询联系人摘要
GET /api/v1/iam/contacts/following查询当前用户关注的联系人
GET /api/v1/iam/contacts/followers查询关注当前用户的联系人
GET /api/v1/iam/contacts/blocked查询当前用户拉黑的联系人
GET /api/v1/iam/contacts/friend-requests/sent查询当前用户发出的好友申请
GET /api/v1/iam/contacts/friend-requests/received查询当前用户收到的好友申请
POST /api/v1/iam/contacts/{id}/friend-request申请添加联系人
PUT /api/v1/iam/contacts/{id}/friend-request/accept通过联系人申请
PUT /api/v1/iam/contacts/{id}/friend-request/reject拒绝联系人申请
DELETE /api/v1/iam/contacts/{id}/friend删除联系人
PUT /api/v1/iam/contacts/{id}/follow关注联系人
DELETE /api/v1/iam/contacts/{id}/follow取消关注联系人
PUT /api/v1/iam/contacts/{id}/block拉黑联系人
DELETE /api/v1/iam/contacts/{id}/block解除拉黑联系人
PUT /api/v1/iam/contacts/{id}/preferences保存联系人偏好
POST /api/v1/iam/contacts/{id}/conversation从联系人或资料卡发起私聊

自助用户关系接口保留更贴近关系操作的入口:/api/v1/iam/user-relations/**;公开用户资料卡相关的关注接口也可通过 /api/v1/iam/users/{id}/follow 访问。新前端统一使用 IAM 域入口。

验证码风控开关

验证码风控默认关闭,不影响开发环境和基础登录体验。相关开关可以放在本地配置,也可以写入数据库配置:

yaml
spring:
  open:
    iam:
      auth:
        login:
          captcha:
            enabled: false
            risk-based: true
            sms-enabled: false
            email-enabled: false
            image-enabled: false
            slider-enabled: false
            rotate-enabled: false
            drag-enabled: false

设计边界:

  • enabled=false 时不要求验证码。
  • risk-based=true 表示后续接入风控后只在风险命中时展示验证码。
  • 短信、邮箱、图形、滑动、旋转和拖动验证码是独立开关,可按项目需要逐步接入不同 Provider。
  • 当前已落地短信验证码、邮箱验证码的发送、校验和登录;图形、滑动、旋转和拖动验证码已提供 challenge / check 接口,Admin / App / PC 登录页会根据 GET /api/v1/auth/capabilities 返回的开关渲染真实可交互控件,先校验挑战通过后再提交原登录请求。控件使用普通请求 / 响应链路,不依赖 SSE,适合多端和受限运行环境。

登录成功后统一返回 JWT token。前后端分离交互使用:

http
Authorization: Bearer <token>

不依赖 Cookie 和 HTTP Session。

账号密码登录默认接入通用限流底座:

yaml
spring:
  open:
    iam:
      auth:
        login:
          rate-limit:
            identifier-max-attempts: 5
            identifier-window: 1m
            ip-max-attempts: 20
            ip-window: 1m

规则:

  • 按登录标识和 IP 同时限流。
  • 登录标识和 IP 会在限流 key 中做哈希处理,避免敏感信息明文进入 Redis。
  • 被限流时返回统一业务码 429,并写入登录失败审计。
  • 多实例生产环境复用 Rate Limit starter 的 Redis store;lite profile 使用 memory store。

短信验证码默认限流策略:

yaml
spring:
  open:
    iam:
      auth:
        login:
          verification:
            sms:
              mobile-max-attempts: 1
              mobile-window: 1m
              ip-max-attempts: 30
            ip-window: 1h
            verify-mobile-max-attempts: 5
            verify-mobile-window: 5m
            verify-ip-max-attempts: 60
            verify-ip-window: 1h

验证码短信发送会复用 Notification 的 sms 通道,因此后台短信发送日志和失败重试入口仍查看 Notification 发送日志。短信登录和后续手机号绑定都应复用同一 scene、缓存 key 和校验消费规则,不要另起一套验证码存储。

国际化手机号场景下,限流和缓存 key 必须使用服务端规范化后的手机号,不使用用户原始输入;日志和审计只能保存脱敏号码或 hash。SMS provider、厂商模板和签名由 SMS 路由按国家 / 地区、locale 和 scene 选择,IAM 只关心验证码是否发送、校验和消费成功。

首个超级管理员初始化

项目不创建固定默认管理员账号,也不提供默认测试密码。公开注册只能创建普通用户,不能自动创建超级管理员。

首次部署通过 安装向导 或 Install 模块 API 创建首个超级管理员。不再提供平行的公开初始化入口,避免同一职责在安装链路和系统链路中重复出现。

初始化入口由 Install 模块承担:

方法路径说明
POST/api/v1/install/ + administrator创建或更新首个超级管理员
POST/api/v1/install/execute执行完整安装流程,包含管理员创建和安装锁定

Install 会按安装令牌、安装状态、系统基础数据和 super-admin 角色绑定执行受控初始化;已安装或已有用户时按安装模块幂等语义处理,不另开平行初始化入口。

普通用户账号应通过自助注册、短信 / 邮箱验证码注册、OAuth / Telegram / 钱包等外部身份登录创建。不要在生产文档、部署脚本或种子数据中写死普通用户测试账号;联调需要账号时,先通过用户端注册或由管理员在后台受控创建。

Auth Facade

业务代码优先使用 Auth facade:

java
Long userId = CurrentUser.longId();
String loginId = Auth.idString();
Auth.checkLogin();
Auth.checkRole("admin");
Auth.checkPermission("iam:user:view");

Auth 的登录主体 ID 不绑定固定 Java 类型。框架默认支持 String、常用数字类型和 UUID 的转换:

java
String loginId = Auth.idString();
Long legacyUserId = Auth.idLong();
UUID externalId = Auth.idAs(UUID.class);

当前 iam_user.id 和多数业务表仍使用 BIGINT,所以存量业务可继续使用 CurrentUser.longId() / CurrentOperator.longId()。新能力如果需要字符串或 UUID 登录主体,应优先走 Auth.idString()Auth.idAs(...)CurrentUser.idAs(...),避免在业务代码里直接散落 Auth.id(Long.class)

如需支持 ULID、Mongo ObjectId 等自定义 ID 类型,可注册 AuthIdConverter Bean;Auth starter 会把自定义 converter 注入 AuthIdConverterRegistry,默认 converter 继续作为兜底。

常用能力:

能力说明
Auth.login(loginId)登录默认设备
Auth.login(loginId, device)登录指定设备
Auth.logout()当前 token 退出
Auth.id() / Auth.idAs(type)当前登录主体 ID
Auth.idString() / Auth.idLong()当前登录主体 ID 的常用类型快捷入口
CurrentUser.* / CurrentOperator.*当前用户 / 当前操作人语义入口
Auth.token()当前 token
Auth.sessions(loginId)指定账号多端会话
Auth.renewTimeout(...)token 续期
Auth.checkRole(code)角色校验
Auth.checkPermission(code)权限校验

设备常量位于 AuthDevices

text
default
web
pc
mobile
android
ios
mini_program
api

用户管理

用户表 iam_user 是唯一账号主体。密码使用 BCrypt 哈希,接口 VO 不返回 password_hash

主要接口:

方法路径说明权限
GET/api/v1/admin/iam/users分页查询用户iam:user:view
POST/api/v1/admin/iam/users创建用户iam:user:create
GET/api/v1/admin/iam/users/{id}查询用户详情iam:user:view
PUT/api/v1/admin/iam/users/{id}修改用户iam:user:update
PUT/api/v1/admin/iam/users/{id}/password重置用户密码iam:user:update
POST/api/v1/admin/iam/users/{id}/impersonation-sessions创建管理员代登录会话iam:user:impersonate
GET/api/v1/admin/iam/impersonation-sessions分页查询代登录会话iam:user:impersonate
POST/api/v1/admin/iam/impersonation-sessions/{id}/revocation撤销代登录会话iam:user:impersonate
DELETE/api/v1/admin/iam/users/{id}删除用户iam:user:delete
DELETE/api/v1/admin/iam/users批量删除用户iam:user:delete

创建用户时会自动绑定可用的 usernameemailmobile 身份。密码登录只查身份表,不直接回退查 iam_user.username。后台重置密码会重新写入 BCrypt 哈希,不返回密码明文。

后台用户管理支持管理员代登录目标用户。请求体必须填写 reasontimeoutSeconds,单位为秒;Admin 前端默认填写 3600 秒,也提供“永久”快捷项。有效期遵循认证层机制:timeoutSeconds <= 0 会统一归一为 -1 永久有效,正数表示有限秒数;Admin 输入框不单独拦截非正数,本模块不额外限制最大或最小时长。后端会校验后台权限、数据范围、目标用户启用状态和内置管理员保护规则,随后以内部 admin-impersonation 设备签发目标用户登录 token,并把代登录会话写入 iam_impersonation_session;永久会话的 expires_at 为空。代登录会记录登录审计和 token 审计,token 明文只在响应中返回一次,表内只保存 hash 与尾部短后缀。

代登录期间默认不内置固定操作黑名单,目标用户会话按目标用户自身角色、权限和数据范围执行。生产环境如需禁止提现、支付密钥、绑定身份等高风险操作,应在后续可配置策略中基于代登录会话上下文做统一拦截,而不是在代登录签发流程写死限制。

代登录会话支持运维闭环:列表接口可按操作人、目标用户和状态(active 在途 / expired 已过期未撤销 / ended 已撤销)过滤,active 但已超过 expires_at 的会话在查询结果中展示为 expiredexpires_at 为空的永久会话保持 active,不额外落库。撤销接口会按目标用户与代登录设备注销 token(token 表内只存哈希,无法按值注销;同一用户同设备的并发代登录会话会一并失效)、把会话状态置为 ended 并记录 impersonate_revoke token 审计;撤销前同样校验数据范围。后台用户列表行操作的"代登录会话"入口可查看并撤销目标用户的会话。

代登录对 App / PC 等普通用户前端保持透明:用户侧 /api/v1/auth/sessions 不返回内部代登录会话,用户侧按设备退出 / 踢下线接口也不能操作内部代登录设备;代登录状态、操作人、目标用户、审计记录、退出入口和风险控制只在后端与 Admin 管理端处理。

身份绑定

身份表:

text
iam_user_identity

内置身份类型:

类型说明
username用户名
email邮箱
mobile手机号
oauth第三方 OAuth
telegramTelegram Login Widget
wallet钱包地址
wechat-official-openid / wechat-miniapp-openid / wechat-unionid微信外部身份
passkeyPasskey 凭证
ldapLDAP / AD 企业目录身份
samlSAML 企业 SSO 身份

主要接口:

方法路径说明权限
GET/api/v1/iam/identities分页查询身份绑定iam:identity:view
GET/api/v1/iam/identities/{id}查询身份详情iam:identity:view
POST/api/v1/iam/identities创建身份绑定iam:identity:create
PUT/api/v1/iam/identities/{id}修改身份绑定iam:identity:update
PUT/api/v1/iam/identities/{id}/enabled启用或停用身份绑定iam:identity:update
PUT/api/v1/iam/identities/{id}/verified标记身份绑定已验证iam:identity:update
DELETE/api/v1/iam/identities/{id}删除身份绑定iam:identity:delete
DELETE/api/v1/iam/identities批量删除身份绑定iam:identity:delete

身份禁用后不能继续用于登录;后台可以单独启停身份绑定,也可以将已线下核验的身份标记为已验证。

当前用户账号中心的外部身份绑定接口:

方法路径说明
GET/api/v1/iam/users/me/identity-bindings只返回当前用户已绑定的外部身份,不包含 username / email / mobile 等凭证身份
DELETE/api/v1/iam/users/me/identity-bindings/{id}解绑当前用户自己的外部身份;如果是他人身份、凭证身份或不存在的绑定,统一按身份不存在处理
POST/api/v1/auth/identity/providers/{code}/bind复用对应 provider 的 verify() 步骤,把已验证外部主体绑定到当前用户;主体已属于其他用户时返回身份已存在
POST/api/v1/auth/identity/providers/{code}/reauth复用对应 provider 的 verify() 步骤,确认已验证外部主体已绑定到当前用户;不创建用户、不新增绑定

解绑会保留至少一个登录身份,避免用户把最后一个可登录身份删掉。绑定和重认证只接收 flow 对应的原始凭证载荷,例如 OAuth 的 code/state/redirectUri、Telegram 的 signed payload、钱包的 challengeId/signature、微信公众号的 code/lang 或小程序的 code。如果解绑的是当前用户名归属的钱包地址,IAM 会自动切换为兜底用户名并同步 username identity;OAuth、Telegram、微信、Passkey、企业 SSO 等外部身份继续通过同一绑定中心支持绑定、重认证和解绑。

钱包地址也可以作为用户名展示,但前提是该地址已经作为当前用户的 wallet 身份绑定并通过后端验签确认归属。邮箱和手机号也可以作为用户名展示,但前提是该邮箱或手机号已经作为目标用户已验证 identity 绑定。后台或用户侧不能把用户名改成未绑定、已归属他人或已解绑的邮箱、手机号或钱包地址;钱包登录自动建号只会使用本次验签通过的同一地址作为候选用户名,地址已被其他用户占用时会生成 wallet_<provider>_* 形式的用户名。

当前内置 Identity Provider discovery 包含:

Provider codeFlowProtocol说明
OAuth provider code,例如 github / oidc / wechat-mpauthorization-codeoauth2 / oidc使用 /api/v1/auth/oauth/{provider}/authorize/login
telegramsigned-payloadtelegram-login-widget使用 /api/v1/auth/identity/providers/telegram/login 校验 Telegram Login Widget signed payload
evm-walletchallenge-signaturewallet-signature钱包签名消息 + signature
passkeychallenge-signaturewebauthnAuth Passkey 负责 WebAuthn 注册、凭据持久化、assertion 验签和登录;descriptor 暴露注册 / 登录路径
wechat-official:<accountCode>authorization-codeoauth2core-wechat 中启用的公众号账号;metadata 带 identityType=wechat-official-openididentityProvider=<accountCode 小写>
wechat-miniapp:<accountCode>miniapp-codewechat-miniappcore-wechat 中启用的小程序账号;metadata 带 identityType=wechat-miniapp-openididentityProvider=<accountCode 小写>
企业目录 provider code,例如 corp-ldapldap-passwordldap / ad使用 /api/v1/auth/identity/providers/{code}/login 校验目录用户名和密码
企业 SAML provider code,例如 corp-samlsaml-assertionsaml2使用 /api/v1/auth/identity/providers/{code}/login 校验 SAMLResponse / Assertion
企业 OIDC provider code,例如 corp-oidcauthorization-codeoidc复用 /api/v1/auth/oauth/{provider}/authorize/login,支持自定义 issuer / 端点

微信 provider code 中的 accountCode 来自 wechat_account.account_code 原值,用于微信服务查找账号;descriptor metadata 中的 identityProvider 使用小写账号编码,用于 IAM 身份表的 openid provider 查询。统一绑定中心会从 provider code 解析账号编码,忽略 payload 中可被篡改的 accountCode;后台绑定数量和绑定列表会优先使用 descriptor metadata 中的 identityType / identityProvider,因此微信多账号会按真实 openid 绑定范围查询。微信专用 /api/v1/auth/wechat-*/bind 仍保留完整 openid + unionid 绑定行为;通用 /identity/providers/{code}/bind 只绑定本次 verify 得到的主 openid subject。

Passkey provider 的 discovery descriptor 只告诉前端可用入口,不在 registry 层做 WebAuthn 校验。真实链路归属 Auth Passkey:

方法路径说明
POST/api/v1/auth/passkey/registration-options当前登录用户生成 WebAuthn 注册 challenge,返回 RP、用户句柄、算法参数、exclude credentials 和注册提交路径
POST/api/v1/auth/passkey/register当前登录用户提交 attestation,校验 challenge / origin / rpId / authenticator data,持久化 credential public key 并绑定 passkey 身份
POST/api/v1/auth/passkey/login-options公开登录入口生成 assertion challenge;可按用户名返回 allow credentials,匿名场景可让浏览器选择 resident credential
POST/api/v1/auth/passkey/login提交 assertion,校验 challenge / origin / rpId / signature / signCount 后进入统一登录、风控、审计和 Token 链路

凭据数据落在 iam_passkey_credential,包含 credential id hash、COSE public key、算法、AAGUID、signCount、RP ID、origin、transports 和最后使用时间。注册成功后会同步写入 iam_user_identity,身份类型为 passkey,provider 固定为 passkey,identifier 使用 credential id hash,便于后台身份绑定、解绑保护和登录审计统一消费。当前实现支持 ES256 和 RS256 公钥验签;attestation 证书链不作为准入条件,生产如需硬件根证书白名单,可以在 Auth Passkey 层继续补充策略,不应放到 Identity Provider registry。

企业目录 provider 通过 spring.open.iam.providers.<code> 配置启用。LDAP / AD adapter 只做目录凭据校验,不处理登录状态、用户创建、绑定或审计;校验成功后统一收敛为 identityType=ldapprovider=<code>identifier=<目录稳定属性>,再由 IAM 登录链路处理风控、审计、Token 和可选自动创建用户。

yaml
spring:
  open:
    iam:
      providers:
        corp-ldap:
          enabled: true
          provider: Corporate Directory
          flow: ldap-password
          protocol: ldap
          auto-create-user: true
          ldap:
            url: ldap://127.0.0.1:389
            manager-dn: cn=manager,dc=example,dc=com
            manager-password: ${LDAP_MANAGER_PASSWORD}
            base-dn: ou=People,dc=example,dc=com
            user-search-filter: "(uid={username})"
            identifier-attribute: uid
            nickname-attribute: cn
            email-attribute: mail
            mobile-attribute: mobile
            connect-timeout: 5s
            read-timeout: 5s

如果不配置 manager-dn,可改用 user-dn-pattern 直接按用户 DN bind:

yaml
spring:
  open:
    iam:
      providers:
        corp-ad:
          enabled: true
          provider: Corporate AD
          flow: ldap-password
          protocol: ad
          ldap:
            url: ldap://ad.example.com:389
            user-dn-pattern: "{username}@example.com"
            identifier-attribute: sAMAccountName
            nickname-attribute: displayName
            email-attribute: mail

PC / App 登录页会读取 provider descriptor;当 flow=ldap-password 时展示目录用户名 / 密码表单,并向 descriptor 的 loginPath 提交:

json
{
  "payload": {
    "username": "alice",
    "password": "secret"
  },
  "device": "pc",
  "createUserIfAbsent": true
}

SAML provider 同样通过 spring.open.iam.providers.<code> 配置启用。SAML adapter 只做 issuer、audience、时间窗口、XML Signature 和属性映射校验;校验成功后统一收敛为 identityType=samlprovider=<code>identifier=<NameID 或配置属性>。默认要求 Assertion 自身带有效 XML Signature,并使用配置的 IdP X.509 证书验签。开发或本地联调可临时关闭 require-signed-assertion,生产环境不建议关闭。

yaml
spring:
  open:
    iam:
      providers:
        corp-saml:
          enabled: true
          provider: Corporate SAML
          flow: saml-assertion
          protocol: saml2
          auto-create-user: true
          saml:
            issuer: https://idp.example.com/saml
            audience: spring-open
            certificate: ${SAML_IDP_CERTIFICATE}
            require-signed-assertion: true
            clock-skew: 2m
            assertion-field: samlResponse
            identifier-attribute: employeeNumber
            nickname-attribute: displayName
            email-attribute: email
            mobile-attribute: mobile

SAML 登录由 IdP 或前置网关拿到 SAMLResponse 后回传平台的通用外部登录入口:

json
{
  "payload": {
    "samlResponse": "<Base64 SAMLResponse>"
  },
  "device": "pc",
  "createUserIfAbsent": true
}

企业 OIDC 不需要单独新建 adapter;它仍是 authorization-code flow,只是在配置中把自定义 provider 声明为 protocol=oidc。后端会把 corp-oidc 这类别名路由到通用 OIDC provider,并读取 spring.open.iam.providers.corp-oidc.* 的 issuer、端点、client 和字段映射。

yaml
spring:
  open:
    iam:
      providers:
        corp-oidc:
          enabled: true
          provider: Corporate OIDC
          flow: authorization-code
          protocol: oidc
          client-id: ${OIDC_CLIENT_ID}
          client-secret: ${OIDC_CLIENT_SECRET}
          redirect-uri: https://app.example.com/login/oauth-callback
          scope: openid profile email
          properties:
            issuer-uri: https://idp.example.com
            identifier-field: sub
            nickname-field: name
            email-field: email
            pkce: true

SAML / LDAP / 企业 OIDC 都只负责“外部凭证校验为已验证主体”。登录、绑定、重认证、自动创建用户、风控、审计和 Token 签发仍复用 IAM 通用链路,不在 adapter 中重复实现业务流程。

后台身份提供方运行时配置台归属 IAM 管理面:

方法路径说明权限
GET/api/v1/admin/iam/identity-providers分页查询当前可发现的 provider descriptor、运行时配置和密钥引用摘要iam:identity-provider:view
GET/api/v1/admin/iam/identity-providers/{code}查询 provider 详情iam:identity-provider:view
PUT/api/v1/admin/iam/identity-providers/{code}保存展示名、排序、启停状态、公开元数据和备注iam:identity-provider:update
PUT/api/v1/admin/iam/identity-providers/{code}/status启用或停用 provideriam:identity-provider:update
POST/api/v1/admin/iam/identity-providers/{code}/test执行本地 descriptor 可用性测试并记录测试状态iam:identity-provider:test
POST/api/v1/admin/iam/identity-providers/{code}/secrets/rotate轮换 secret 引用、提示、摘要和版本iam:identity-provider:secret:rotate
GET/api/v1/admin/iam/identity-providers/{code}/bindings查询指定 provider 的外部身份绑定iam:identity-provider:view

运行时配置写入 iam_identity_provider_config,只覆盖公开发现和绑定入口需要的启停、排序、展示名、公开 metadata 与测试结果。密钥引用写入 iam_identity_provider_secret,只保存 secretRefsecretHint、摘要和版本;请求和响应都不承载明文 secret,返回给前端的引用会做掩码。停用 provider 后,GET /api/v1/auth/identity/providers 的公开发现结果、绑定和重认证都会同步拒绝该 provider。

外部身份登录支持 LoginRiskGuard 扩展点。平台会在外部身份 verify 完成、签发会话前依次调用已注册的 guard;没有 guard 时等同放行,不产生额外开销。guard 可以基于用户、登录类型、provider、外部主体、设备、IP 和是否本次自动创建用户返回拒绝决策;拒绝后不会签发会话,并会把失败原因写入登录审计。

Profile

Profile 用于承载不同业务身份的扩展资料。

Profile说明
普通用户iam_user_profile普通用户资料
管理员iam_admin_profile管理员资料
商户iam_merchant_profile商户资料
开发者iam_developer_profile开发者资料

Profile 支持两类接口:

  • 资源型接口:按 profile ID 分页、详情、新增、修改、删除、批量删除。
  • 用户路径接口:按 userId 查询或保存对应 profile,并支持启停。

示例:

方法路径说明
GET/api/v1/admin/iam/user-profiles分页查询普通用户 profile
PUT/api/v1/admin/iam/users/{userId}/profile保存普通用户 profile
GET/api/v1/admin/iam/admin-profiles分页查询管理员 profile
PUT/api/v1/admin/iam/admins/{userId}/profile/enabled启停管理员 profile
GET/api/v1/admin/iam/merchant-profiles分页查询商户 profile
GET/api/v1/admin/iam/developer-profiles分页查询开发者 profile

权限前缀:

text
iam:profile:*

Profile 查询和 ID 型接口按目标用户接入数据范围校验。

RBAC

RBAC 使用角色、权限和菜单三类资源。

说明
iam_role角色
iam_permission权限
iam_menu菜单
iam_user_role用户角色关系
iam_role_permission角色权限关系
iam_role_menu角色菜单关系

内置角色编码:

text
super-admin
admin
operator
merchant
developer
user

super-admin 在迁移初始化时绑定内置权限、菜单和 ALL 数据范围。adminoperator 作为第一阶段后台模板角色,会默认绑定一组低风险后台菜单、权限和数据范围:

  • admin:用户、角色、权限、菜单、配置、字典、模板、文件、通知、会话、认证审计、在线用户和操作日志等常规后台管理入口。
  • operator:通知消息、发送日志、会话运维、内容安全审核和操作日志等运营 / 客服入口。

数据范围第一阶段采用保守模板:admin 默认 ALL,用于常规后台管理;operator 默认 SELF,只访问本人处理或被显式分配的数据。默认模板不会授予 SQL 控制台、安装执行、Web3 资金执行、危险删除等高风险权限。developer 已由 Open Platform 模块声明开放平台后台只读模板,覆盖应用、套餐、Scope、路由 Scope、调用日志、用量事件、账务和 AI 运营只读入口;merchant 已由 Mall 模块声明商城后台只读模板,覆盖商品、订单、售后、优惠券、履约、支付 / 交易 / 库存观测入口,不授予发货、退款、作废、删除等写操作;user 暂不绑定后台菜单和默认数据范围,普通用户默认不进入后台。

Admin 角色管理页会展示内置角色模板策略,包含权限策略、菜单策略、默认数据范围和注意事项。内置角色编码由迁移保留,后端会拒绝手动创建、改码和删除;super-admin 禁止停用,菜单权限、API 权限和数据范围不能在角色管理页保存变更。前端会禁用或提示这些危险操作,服务层校验仍是最终准入。

权限标识使用稳定字符串:

text
iam:user:view
iam:user:create
iam:user:update
iam:user:delete

业务代码引用内置权限时优先使用 PermissionCodes,不要散落手写字符串。

角色、权限、菜单接口

角色:

方法路径说明
GET/api/v1/admin/iam/roles分页查询角色
POST/api/v1/admin/iam/roles创建角色
GET/api/v1/admin/iam/roles/{id}查询角色详情
PUT/api/v1/admin/iam/roles/{id}修改角色
DELETE/api/v1/admin/iam/roles/{id}删除角色
DELETE/api/v1/admin/iam/roles批量删除角色
GET/api/v1/admin/iam/roles/{roleId}/permissions查询角色权限
PUT/api/v1/admin/iam/roles/{roleId}/permissions分配角色权限
GET/api/v1/admin/iam/roles/{roleId}/menus查询角色菜单
PUT/api/v1/admin/iam/roles/{roleId}/menus分配角色菜单

权限:

方法路径说明
GET/api/v1/admin/iam/permissions分页查询权限
POST/api/v1/admin/iam/permissions创建权限
GET/api/v1/admin/iam/permissions/{id}查询权限详情
PUT/api/v1/admin/iam/permissions/{id}修改权限
DELETE/api/v1/admin/iam/permissions/{id}删除权限
DELETE/api/v1/admin/iam/permissions批量删除权限

菜单:

方法路径说明
GET/api/v1/admin/iam/menus查询菜单列表
GET/api/v1/admin/iam/menus/tree查询菜单树
POST/api/v1/admin/iam/menus创建菜单
GET/api/v1/admin/iam/menus/{id}查询菜单详情
PUT/api/v1/admin/iam/menus/{id}修改菜单
DELETE/api/v1/admin/iam/menus/{id}删除菜单
DELETE/api/v1/admin/iam/menus批量删除菜单

菜单支持目录、菜单、按钮三种类型。菜单用于前端导航和展示,权限用于后端鉴权,两者通过权限标识保持弱关联。

用户授权

用户角色、权限和菜单接口:

方法路径说明
GET/api/v1/admin/iam/users/{userId}/roles查询用户角色
PUT/api/v1/admin/iam/users/{userId}/roles分配用户角色
GET/api/v1/admin/iam/users/{userId}/permissions查询用户权限
GET/api/v1/admin/iam/users/{userId}/menus查询用户菜单树
GET/api/v1/organization/users/{userId}/posts查询用户岗位
PUT/api/v1/organization/users/{userId}/posts分配用户岗位

关系表通常通过聚合接口管理,不开放裸关系表 CRUD。

租户、组织、部门、岗位

组织模型用于数据权限和后台管理。

资源路径说明
租户/api/v1/iam/tenants租户管理
组织/api/v1/iam/organizations组织管理,归属租户
部门/api/v1/iam/departments部门管理,归属租户和组织
岗位/api/v1/organization/posts岗位管理,归属租户和组织
用户租户归属/api/v1/iam/user-tenants用户多租户关系
用户组织归属/api/v1/iam/user-organizations用户多组织关系
用户部门归属/api/v1/iam/user-departments用户多部门关系

这些资源均提供标准后台接口:

text
GET /resources
GET /resources/{id}
POST /resources
PUT /resources/{id}
DELETE /resources/{id}
DELETE /resources

批量删除 ID 通过请求体传递,不放到 URL 中。

工作空间

工作空间由请求头解析:

http
X-Tenant-Id: 1
X-Organization-Id: 10
X-Department-Id: 100

当前工作空间接口:

方法路径说明
GET/api/v1/iam/workspace/current查询当前工作空间,并返回当前成员、席位占用、角色和权限快照

工作空间不固定写入 token。每次请求按当前用户归属关系校验请求头中的租户、组织和部门。currentMember / seat / roleCodes / permissionCodes 只表示当前登录用户在当前工作空间下的可用上下文;成员列表仍通过 GET /api/v1/iam/workspace/members 分页查询。

数据权限

数据权限由角色数据范围控制:

text
iam_role_data_scope

内置角色模板的数据范围默认值:

角色默认数据范围说明
super-adminALL最高权限角色,迁移预置全量数据范围
adminALL常规后台管理员,默认可访问平台后台管理数据
operatorSELF运营 / 客服角色,默认只访问本人处理或被显式分配的数据
merchant暂不绑定Mall 已声明后台只读菜单和权限;不绑定默认数据范围,商户写操作仍需商户 / 店铺 ownership 和数据范围强校验
developer暂不绑定Open Platform 已声明后台只读菜单和权限;不绑定默认数据范围,开发者写操作仍需 workspace / 应用归属强校验
user暂不绑定普通用户默认不进入后台,用户端数据按当前登录用户校验

支持范围:

类型说明
ALL全部数据
TENANT当前租户
ORGANIZATION当前组织
ORGANIZATION_AND_CHILDREN当前组织及下级组织
DEPARTMENT当前部门
DEPARTMENT_AND_CHILDREN当前部门及下级部门
SELF本人数据
CUSTOM自定义租户、组织、部门

角色数据范围接口:

方法路径说明
GET/api/v1/iam/role-data-scopes分页查询角色数据范围
GET/api/v1/iam/role-data-scopes/{id}查询详情
POST/api/v1/iam/role-data-scopes创建数据范围
PUT/api/v1/iam/role-data-scopes/{id}修改数据范围
DELETE/api/v1/iam/role-data-scopes/{id}删除数据范围
DELETE/api/v1/iam/role-data-scopes批量删除数据范围

super-adminALL 数据范围是安装基线的一部分,角色数据范围接口会拒绝在角色管理页新增、修改或删除该角色的数据范围。

当前采用显式数据范围管理器:

text
DataScopeManager
DataScopeQuerySupport
DataScopeAccessSupport
UserDataScopeQuerySupport

没有使用全局 SQL 拦截器。这样更清晰,也更容易审计每个后台接口到底按哪些字段做范围限制。

认证审计

登录日志:

方法路径说明
GET/api/v1/iam/auth/audit/login-logs分页查询登录日志
GET/api/v1/iam/auth/audit/risk-summary查询最近登录风控摘要
GET/api/v1/iam/auth/audit/login-logs/{id}查询登录日志详情
DELETE/api/v1/iam/auth/audit/login-logs/{id}删除登录日志
DELETE/api/v1/iam/auth/audit/login-logs批量删除登录日志

外部身份 LoginRiskGuard 拒绝登录时会写入 success=false 的登录日志,failureReason 默认使用 {messages.auth.login.risk-rejected},也可以由 guard 返回业务自定义 message key。后台 /iam/login-logs 页面和 risk-summary 摘要会消费这些失败记录,不需要额外创建风控表。

Token 审计:

方法路径说明
GET/api/v1/iam/auth/audit/token-logs分页查询 Token 审计日志
GET/api/v1/iam/auth/audit/token-logs/{id}查询 Token 审计详情
DELETE/api/v1/iam/auth/audit/token-logs/{id}删除 Token 审计日志
DELETE/api/v1/iam/auth/audit/token-logs批量删除 Token 审计日志

Token 审计只保存 token SHA-256 hash 和尾部短后缀,不保存 token 明文。

在线会话

当前用户会话:

方法路径说明
GET/api/v1/auth/sessions查询当前用户会话
POST/api/v1/auth/sessions/{device}/logout当前用户按设备退出
POST/api/v1/auth/sessions/{device}/kickout当前用户按设备踢下线

后台在线会话:

方法路径说明
GET/api/v1/admin/auth/online-sessions分页查询在线会话
POST/api/v1/admin/auth/online-sessions/{userId}/devices/{device}/kickout后台踢下线用户设备

接口不返回 token 明文。

不同 Sa-Token JWT 模式对多端会话的能力边界不同。项目默认使用 simple,确保后台在线会话强退、当前用户按设备退出 / 踢下线和 Token 审计可用。mixinstateless 更适合特殊轻状态场景,可能禁用按 loginId / token 生命周期操作;如果主动切换这些模式,前端应隐藏对应按钮或展示“不支持当前操作”。

操作日志

Audit 会自动记录 /api/v1/admin/** 下的写请求:

text
POST
PUT
PATCH
DELETE

操作日志接口:

方法路径说明
GET/api/v1/admin/audit/operation-logs分页查询操作日志
GET/api/v1/admin/audit/operation-logs/{id}查询操作日志详情
DELETE/api/v1/admin/audit/operation-logs/{id}删除操作日志
DELETE/api/v1/admin/audit/operation-logs批量删除操作日志

操作日志记录 URI、查询参数、路径变量、operationId、状态、traceId、来源 IP、User-Agent 等信息,不采集请求 body,避免敏感数据泄露。

配置参数

IAM 身份提供方统一使用 spring.open.iam.providers.*。GitHub、Google、OIDC、支付宝、微博、百度、抖音、Gitee、QQ、微信、钉钉、飞书和企业微信属于 OAuth/OIDC provider;Telegram 属于 Login Widget provider;EVM Wallet 属于钱包签名 provider。配置层只表达“身份提供方”,OAuth、签名、hash 校验等协议细节由对应 provider 内部处理。

登录页可以先调用:

http
GET /api/v1/auth/identity/providers

该接口只返回当前已启用的身份提供方入口,例如 OAuth / 企业 OIDC provider 的 authorizePath / loginPath、Telegram 的 loginPath 和 Login Widget 公开元数据、钱包的 challengePath / loginPath、Passkey 的 registrationOptionsPath / registrationPath / challengePath / loginPath、LDAP / SAML 的通用 loginPath,以及 core-wechat 中启用公众号 / 小程序账号产生的微信 descriptor。前端不需要硬编码哪些 provider 已启用;账号密码登录仍按业务页面自身决定是否展示。Telegram 的 botUsernamebuttonSizecornerRadiusrequestAccess 可以返回给前端,bot-token 只用于后端校验 hash,不会出现在响应中;SAML 只返回字段名、是否要求签名和是否配置证书等公开 metadata,不返回证书或密钥。

SSO 边界

当前 IAM 已经可以作为 OAuth / OIDC Client 接入外部身份中心,例如 GitHub、Google、企业 OIDC、钉钉、飞书、企业微信、Gitee、QQ、微信和 Telegram;也可以通过 ldap-passwordsaml-assertion flow 接入企业目录和 SAML 身份中心。接入后仍然会落到统一账号、统一 token、统一 RBAC、统一审计和统一数据范围链路。

Auth starter 同时提供 Sa-Token OAuth2 Authorization Server 的框架底座,默认关闭。开启后,本项目可以作为授权服务器向第三方系统签发 token;这条链路和上面的“接入外部 OAuth/OIDC provider 登录”不是同一个方向。当前底座提供协议端点、客户端配置、授权模式开关、scope 分级、token 生命周期配置、后台客户端管理和 OIDC 发现元数据。授权确认仍复用 Sa-Token 协议确认端点,客户端可在后台设置是否自动确认 scope;JWKS、ID Token 非对称签名和多租户隔离策略可在此底座上继续扩展。

OAuth2 客户端支持两类来源:

  • 静态配置:spring.open.auth.oauth2.clients.<client>.*,适合部署期固定客户端。
  • 后台维护:IAM 菜单“OAuth2 客户端”,接口前缀 /api/v1/admin/iam/oauth2-clients,权限为 iam:oauth2-client:*,数据落表 iam_oauth2_client。启用状态的数据库客户端会通过 Auth starter provider 合并进 Sa-Token OAuth2 server config;同 clientId 时以后台维护的启用客户端为准。

Sa-Token OAuth2 协议端点同时支持标准根路径和平台版本化路径:

能力标准路径平台版本化路径
授权/oauth2/authorize/api/v1/auth/oauth2/authorize
换取令牌/oauth2/token/api/v1/auth/oauth2/token
刷新令牌/oauth2/refresh/api/v1/auth/oauth2/refresh
撤销令牌/oauth2/revoke/api/v1/auth/oauth2/revoke
客户端令牌/oauth2/client_token/api/v1/auth/oauth2/client_token
授权页登录/oauth2/doLogin/api/v1/auth/oauth2/doLogin
授权确认/oauth2/doConfirm/api/v1/auth/oauth2/doConfirm

OIDC 发现元数据同时支持:

  • /.well-known/openid-configuration
  • /api/v1/auth/oauth2/.well-known/openid-configuration
配置默认值说明
spring.open.auth.enabledtrue是否启用 Auth starter
spring.open.auth.default-login-devicedefault默认登录设备
spring.open.auth.persistence.storeredis应用默认登录态存储,支持 memoryredis
spring.open.auth.persistence.key-prefixauth:satokenSa-Token Redis key 前缀
spring.open.auth.jwt.enabledtrue是否启用 JWT StpLogic
spring.open.auth.jwt.modesimpleJWT 模式:simplemixinstateless;默认使用 simple,保留 token 映射和终端会话能力,支持后台强退、按设备下线和 Token 审计
sa-token.jwt-secret-key${SPRING_OPEN_AUTH_JWT_SECRET_KEY:${spring.application.secret:&#125;&#125;JWT 签名密钥统一入口;未配置专用密钥时使用应用共享密钥,数据库配置中心播种的共享密钥可覆盖派生默认值(见下方读取顺序)
spring.open.auth.oauth2.enabledfalse是否启用 Sa-Token OAuth2 授权服务器
spring.open.auth.oauth2.authorization-code-enabledtrue是否启用授权码模式
spring.open.auth.oauth2.implicit-enabledfalse是否启用隐式模式
spring.open.auth.oauth2.password-enabledfalse是否启用密码模式
spring.open.auth.oauth2.client-credentials-enabledtrue是否启用客户端凭证模式
spring.open.auth.oauth2.clients.<client>.client-secretOAuth2 客户端密钥;<client> 可作为 clientId 兜底
spring.open.auth.oauth2.clients.<client>.redirect-uris允许的 OAuth2 回调地址列表
spring.open.auth.oauth2.clients.<client>.scopesopenid,profile客户端允许申请的 scope
spring.open.auth.oauth2.clients.<client>.grant-typesauthorization_code,refresh_token客户端允许使用的授权模式
spring.open.auth.multi-device.concurrenttrue是否允许同账号多端同时在线
spring.open.auth.multi-device.share-tokentrue多端是否共享 token
spring.open.auth.multi-device.max-login-count12同账号最大登录数量,-1 表示不限
spring.open.iam.user.enabledtrue是否启用 IAM 用户能力
spring.open.iam.enabledtrue是否启用 IAM 身份和 profile 能力
spring.open.iam.rbac.enabledtrue是否启用 RBAC
spring.open.iam.tenant.enabledtrue是否启用租户、组织、部门归属能力
spring.open.iam.data-scope.enabledtrue是否启用数据权限
spring.open.iam.workspace.enabledtrue是否启用工作空间解析
spring.open.iam.auth.audit.enabledtrue是否启用认证审计
spring.open.audit.operation.enabledtrue是否启用操作日志
spring.open.iam.auth.session.online.enabledtrue是否启用在线会话后台管理
spring.open.iam.auth.login.rate-limit.enabledtrue是否启用登录限流配置
spring.open.iam.auth.login.rate-limit.password.enabledtrue是否启用账号密码登录限流
spring.open.iam.auth.login.rate-limit.password.identifier-max-attempts5单个登录标识在窗口内最大密码登录尝试次数
spring.open.iam.auth.login.rate-limit.password.identifier-window1m登录标识限流窗口
spring.open.iam.auth.login.rate-limit.password.ip-max-attempts20单个 IP 在窗口内最大密码登录尝试次数
spring.open.iam.auth.login.rate-limit.password.ip-window1mIP 登录限流窗口
spring.open.iam.auth.login.rate-limit.register.enabledtrue是否启用账号注册限流
spring.open.iam.auth.login.rate-limit.register.identifier-max-attempts3单个注册标识在窗口内最大尝试次数
spring.open.iam.auth.login.rate-limit.register.identifier-window1m注册标识限流窗口
spring.open.iam.auth.login.rate-limit.register.ip-max-attempts10单个 IP 在窗口内最大注册尝试次数
spring.open.iam.auth.login.rate-limit.register.ip-window1mIP 注册限流窗口
spring.open.iam.auth.login.verification.sms.enabledtrue是否启用短信验证码入口
spring.open.iam.auth.login.verification.sms.ttl5m短信验证码有效期
spring.open.iam.auth.login.verification.sms.mobile-max-attempts1单个手机号在窗口内最大发送次数
spring.open.iam.auth.login.verification.sms.verify-mobile-max-attempts5单个手机号在窗口内最大校验次数
spring.open.iam.auth.login.verification.sms.provider短信验证码发送 Provider;为空时使用 Notification / SMS 默认路由
spring.open.iam.auth.login.verification.email.enabledtrue是否启用邮箱验证码入口
spring.open.iam.auth.login.verification.email.ttl5m邮箱验证码有效期
spring.open.iam.auth.login.verification.email.email-max-attempts1单个邮箱在窗口内最大发送次数
spring.open.iam.auth.login.verification.email.verify-email-max-attempts5单个邮箱在窗口内最大校验次数
spring.open.iam.auth.login.verification.email.subjectVerification code未配置模板时使用的邮件标题
spring.open.iam.auth.login.captcha.enabledfalse是否启用验证码风控
spring.open.iam.auth.login.captcha.risk-basedtrue是否仅在检测到登录风险时展示验证码
spring.open.iam.auth.login.captcha.sms-enabledfalse是否允许登录页使用短信验证码校验
spring.open.iam.auth.login.captcha.email-enabledfalse是否允许登录页使用邮箱验证码校验
spring.open.iam.auth.login.captcha.image-enabledfalse是否允许登录页使用图形验证码校验
spring.open.iam.auth.login.captcha.slider-enabledfalse是否允许登录页使用滑块验证码校验
spring.open.iam.auth.login.captcha.rotate-enabledfalse是否允许登录页使用旋转验证码校验
spring.open.iam.auth.login.captcha.drag-enabledfalse是否允许登录页使用拖动验证码校验
spring.open.iam.auth.login.qr-login.enabledtrue是否启用扫码登录能力
spring.open.iam.auth.login.qr-login.ttl5m扫码登录请求有效期
spring.open.iam.auth.login.qr-login.default-target-clientadmin扫码登录默认目标客户端
spring.open.iam.providers.github.enabledfalse是否启用 GitHub OAuth provider
spring.open.iam.providers.github.client-idGitHub OAuth App Client ID
spring.open.iam.providers.github.client-secretGitHub OAuth App Client Secret
spring.open.iam.providers.github.state-ttl5mGitHub OAuth state 有效期
spring.open.iam.providers.google.enabledfalse是否启用 Google OAuth provider
spring.open.iam.providers.google.client-idGoogle OAuth Client ID
spring.open.iam.providers.google.client-secretGoogle OAuth Client Secret
spring.open.iam.providers.google.state-ttl5mGoogle OAuth state 有效期
spring.open.iam.providers.oidc.enabledfalse是否启用通用 OIDC provider
spring.open.iam.providers.oidc.client-idOIDC Client ID
spring.open.iam.providers.oidc.client-secretOIDC Client Secret;公共客户端可为空
spring.open.iam.providers.oidc.scopeopenid profile emailOIDC 授权 scope
spring.open.iam.providers.oidc.authorize-uri授权端点;为空时从 issuer-uri discovery 获取
spring.open.iam.providers.oidc.token-uriToken 端点;为空时从 issuer-uri discovery 获取
spring.open.iam.providers.oidc.user-info-uriUserInfo 端点;为空时从 issuer-uri discovery 获取
spring.open.iam.providers.oidc.properties.issuer-uriOIDC issuer 地址,用于读取 /.well-known/openid-configuration
spring.open.iam.providers.oidc.properties.identifier-fieldsubUserInfo 中稳定用户标识字段
spring.open.iam.providers.oidc.properties.nickname-fieldnameUserInfo 中昵称字段
spring.open.iam.providers.oidc.properties.avatar-fieldpictureUserInfo 中头像字段
spring.open.iam.providers.oidc.properties.email-fieldemailUserInfo 中邮箱字段
spring.open.iam.providers.oidc.properties.mobile-fieldphone_numberUserInfo 中手机号字段
spring.open.iam.providers.oidc.properties.pkcefalse是否启用 OIDC PKCE S256;启用后授权地址会带 code_challenge,回调换 token 会带 code_verifier
spring.open.iam.providers.alipay.enabledfalse是否启用支付宝登录
spring.open.iam.providers.alipay.client-id支付宝应用 AppID
spring.open.iam.providers.alipay.client-secret支付宝应用私钥
spring.open.iam.providers.alipay.scopeauth_user支付宝授权 scope
spring.open.iam.providers.alipay.properties.alipay-public-key支付宝公钥,用于验签
spring.open.iam.providers.weibo.enabledfalse是否启用微博登录
spring.open.iam.providers.weibo.client-id微博应用 App Key
spring.open.iam.providers.weibo.client-secret微博应用 App Secret
spring.open.iam.providers.weibo.scope微博授权 scope,按开放平台申请能力填写
spring.open.iam.providers.baidu.enabledfalse是否启用百度登录
spring.open.iam.providers.baidu.client-id百度应用 API Key
spring.open.iam.providers.baidu.client-secret百度应用 Secret Key
spring.open.iam.providers.baidu.scopebasic百度授权 scope
spring.open.iam.providers.douyin.enabledfalse是否启用抖音登录
spring.open.iam.providers.douyin.client-id抖音应用 Client Key
spring.open.iam.providers.douyin.client-secret抖音应用 Client Secret
spring.open.iam.providers.douyin.scopeuser_info抖音授权 scope
spring.open.iam.providers.gitee.enabledfalse是否启用 Gitee OAuth provider
spring.open.iam.providers.gitee.client-idGitee OAuth App Client ID
spring.open.iam.providers.gitee.client-secretGitee OAuth App Client Secret
spring.open.iam.providers.gitee.scopeuser_info emailsGitee 授权 scope
spring.open.iam.providers.qq.enabledfalse是否启用 QQ 互联 OAuth provider
spring.open.iam.providers.qq.client-idQQ 互联 App ID
spring.open.iam.providers.qq.client-secretQQ 互联 App Key
spring.open.iam.providers.qq.scopeget_user_infoQQ 授权 scope;如已申请 unionId,可设置 properties.union-id=true
spring.open.iam.providers.wechat-open.enabledfalse是否启用微信开放平台网站应用登录
spring.open.iam.providers.wechat-open.client-id微信开放平台 AppID
spring.open.iam.providers.wechat-open.client-secret微信开放平台 AppSecret
spring.open.iam.providers.wechat-open.scopesnsapi_login微信开放平台授权 scope
spring.open.iam.providers.wechat-open.properties.langzh_CN微信用户信息语言
spring.open.iam.providers.wechat-mp.enabledfalse是否启用微信公众号 OAuth 登录
spring.open.iam.providers.wechat-mp.client-id微信公众号 AppID
spring.open.iam.providers.wechat-mp.client-secret微信公众号 AppSecret
spring.open.iam.providers.wechat-mp.scopesnsapi_userinfo微信公众号授权 scope
spring.open.iam.providers.dingtalk.enabledfalse是否启用钉钉扫码登录
spring.open.iam.providers.dingtalk.client-id钉钉 App ID / App Key
spring.open.iam.providers.dingtalk.client-secret钉钉 App Secret
spring.open.iam.providers.dingtalk.properties.ding-talk-corp-id钉钉组织 ID,可选
spring.open.iam.providers.feishu.enabledfalse是否启用飞书 OAuth 登录
spring.open.iam.providers.feishu.client-id飞书 App ID
spring.open.iam.providers.feishu.client-secret飞书 App Secret
spring.open.iam.providers.wecom.enabledfalse是否启用企业微信 Web 登录
spring.open.iam.providers.wecom.client-id企业微信 CorpID
spring.open.iam.providers.wecom.client-secret企业微信应用 Secret
spring.open.iam.providers.wecom.scopesnsapi_privateinfo企业微信授权 scope
spring.open.iam.providers.wecom.properties.agent-id企业微信应用 AgentID
spring.open.iam.providers.telegram.enabledfalse是否启用 Telegram Login Widget 登录
spring.open.iam.providers.telegram.bot-tokenTelegram Bot Token,用于校验 Login Widget hash
spring.open.iam.providers.telegram.bot-usernameTelegram Bot Username,用于前端渲染 Login Widget;可带 @,接口响应会去掉前缀
spring.open.iam.providers.telegram.button-sizelargeTelegram Login Widget 按钮尺寸,可选 largemediumsmall
spring.open.iam.providers.telegram.corner-radiusTelegram Login Widget 按钮圆角,留空使用 Telegram 默认样式
spring.open.iam.providers.telegram.request-accessTelegram Login Widget request_access 参数,例如 write
spring.open.iam.providers.telegram.auth-date-ttl-seconds86400Telegram auth_date 有效期,单位秒
spring.open.iam.providers.evm-wallet.enabledtrue是否启用 EVM 钱包登录
spring.open.iam.providers.evm-wallet.default-chain-typeevm默认钱包链类型
spring.open.iam.providers.evm-wallet.challenge-ttl5m钱包 challenge 有效期
spring.open.iam.providers.evm-wallet.auto-create-usertrue钱包身份不存在时是否自动创建用户
spring.open.iam.providers.<code>.flow自定义身份提供方 flow,例如 ldap-passwordsaml-assertion 或企业 OIDC 的 authorization-code
spring.open.iam.providers.<code>.protocol自定义身份提供方协议,例如 ldapadsaml2oidc
spring.open.iam.providers.<code>.auto-create-usertrue外部身份不存在时是否自动创建统一账号
spring.open.iam.providers.<code>.ldap.urlLDAP / AD 服务地址
spring.open.iam.providers.<code>.ldap.manager-dnLDAP / AD 管理员 DN;配置后先搜索用户 DN 再用用户密码 bind
spring.open.iam.providers.<code>.ldap.manager-passwordLDAP / AD 管理员密码
spring.open.iam.providers.<code>.ldap.base-dnLDAP / AD 用户搜索基础 DN
spring.open.iam.providers.<code>.ldap.user-dn-pattern不使用管理员搜索时的用户 DN 模板
spring.open.iam.providers.<code>.ldap.user-search-filter(uid={username})用户搜索过滤器
spring.open.iam.providers.<code>.saml.issuerSAML Issuer;配置后必须和断言 Issuer 完全一致
spring.open.iam.providers.<code>.saml.audienceService Provider audience / entity ID
spring.open.iam.providers.<code>.saml.certificateIdP X.509 证书,支持 PEM 或纯 Base64 DER
spring.open.iam.providers.<code>.saml.require-signed-assertiontrue是否要求 Assertion 自身带有效 XML Signature
spring.open.iam.providers.<code>.saml.clock-skew2mSAML NotBefore / NotOnOrAfter 时间窗口容忍偏移
spring.open.iam.providers.<code>.saml.assertion-fieldsamlResponse外部登录 payload 中承载 SAMLResponse 的字段名
spring.open.iam.providers.<code>.saml.identifier-attribute稳定身份标识属性;为空时使用 Subject NameID

Sa-Token 原生配置放在 sa-token.* 下,例如 token 名称、token 前缀、过期时间和 JWT 签名密钥。JWT 密钥由 Auth starter 统一决策并同步到 Sa-Token 配置,读取顺序是:

  1. 显式专用密钥:SPRING_OPEN_AUTH_JWT_SECRET_KEY 或自定义的 sa-token.jwt-secret-key
  2. 显式 APP_SECRET(应用共享密钥)
  3. 数据库配置 sa-token.jwt-secret-key
  4. 数据库配置 spring.application.secret(Config 模块首次启动自动播种的高熵随机共享密钥)
  5. 本地派生共享密钥 spring.application.secret(应用名 + 节点标识派生)
  6. 生成进程内随机密钥,不写入文件

默认项目无需手动配置 JWT 专用密钥。启用数据库配置中心时,框架首次启动会自动在配置中心播种一条高熵随机的 spring.application.secret多实例只要共享同一数据库,就会零配置收敛到同一密钥,且跨重启稳定。无数据库配置时回退到本地派生共享密钥(节点标识默认来自首启生成的 storage/runtime/device-id 随机文件,跨重启稳定)。JWT 不回退到 spring.datasource.password,避免数据库口令与 token 签名密钥在模块间散落共用;未求值的 SpEL 字面量(含 #{ 的值)会被拒绝,不会被当作密钥。

注意两点:只有最终落到进程内随机密钥(极端场景:无数据库配置且节点派生不可用)时,应用重启后历史 token 才会失效,此时会输出启动 WARN;多实例部署若不共享数据库配置中心,必须显式配置共享的 SPRING_OPEN_AUTH_JWT_SECRET_KEY 或强随机 APP_SECRET,否则各实例签发的 token 互不兼容。

安全约定

  • 不创建默认账号和默认密码。
  • 不把样例账号当作框架固定凭据;本地样例填充按钮默认不应在生产环境开启。
  • 不在接口中返回密码哈希、token 明文或私钥。
  • OAuth 登录必须先验证第三方凭证。
  • OIDC 当前使用 Authorization Code + UserInfo 接入统一账号,并支持可选 PKCE S256;企业 OIDC 可通过自定义 provider code 复用该链路。需要 ID Token 验签、JWKS 缓存或企业级 SSO Server 时,应通过 provider 专用实现继续增强。
  • SAML 登录默认要求 Assertion 自身带有效 XML Signature,并使用配置的 IdP 证书验签;本地联调可关闭签名要求,生产环境不建议关闭。
  • 国内 OAuth provider 当前复用成熟生态实现,但隐藏在项目 OAuthProvider SPI 后面;业务代码只感知 alipayweibobaidudouyingiteeqqwechat-openwechat-mpdingtalkfeishuwecom 这些身份提供方名称。
  • 钱包登录必须先生成服务端 challenge,再校验签名。
  • Web3 SDK 依赖只允许出现在 Web3 starter 中。
  • Controller 不写业务逻辑,登录、身份绑定、授权、审计都在 Service 或 starter manager 中处理。
  • 写接口必须走权限校验和操作日志。
  • 涉及目标用户的数据必须通过显式数据范围校验。

后续方向

当前阶段已经完成统一账号、统一 token、RBAC、profile、数据范围和审计主线。

后续继续增强:

  • 更多 OAuth provider 可继续通过 OAuthProvider SPI 接入;后续可按使用频率和依赖边界补充淘宝、京东、小米、华为等平台。
  • OIDC 深度增强:ID Token 验签、JWKS 缓存、企业 SSO Server / Authorization Server。
  • Passkey。
  • 更完整的数据权限规则和后台 UI 元数据。

Released under the Apache License 2.0.