Skip to content

Latest commit

 

History

History
1085 lines (900 loc) · 43.9 KB

File metadata and controls

1085 lines (900 loc) · 43.9 KB

鉴权接口文档(Admin Auth)

语言版本: English | 中文


本文档按当前代码实现整理,覆盖 app/http/router/internal/admin/auth/auth.go 中 27 个路由的完整功能、调用链路和数据结构。

1. 基础信息

1.1 路由前缀

  • app/http/router/handler.go:注册 /{apiPrefix}
  • app/http/router/internal/handler.go:注册 /internal
  • app/http/router/internal/admin/handler.go:注册 /adminSaveOperationRecord 挂载在 /admin/system
  • app/http/router/internal/admin/auth/handler.go:注册 /auth

其中 apiPrefix 表示 system.route_prefix;默认值为 dudu-admin-api

Auth 模块业务接口完整前缀:/{apiPrefix}/internal/admin/auth

1.2 路由总览(与 auth.go 一致)

功能 方法 路径 是否经过 CheckAdminAuth
获取 OAuth 登录地址 GET /oauth/url
换取登录 Token POST /token
获取 Passkey 登录验证请求 POST /passkey/login/options
完成 Passkey 登录 POST /passkey/login/finish
本地账号重认证 POST /reauth
查询敏感操作验证方式 GET /reauth/methods
密码验证敏感操作 POST /reauth/password
TOTP 验证敏感操作 POST /reauth/totp
获取 Passkey 敏感操作验证请求 POST /reauth/passkey/options
完成 Passkey 敏感操作验证 POST /reauth/passkey/finish
确认第三方绑定 POST /oauth/bind/confirm
查询已绑定第三方 GET /oauth/accounts
解绑第三方 POST /oauth/unbind
获取 Passkey 注册验证请求 POST /passkey/register/options
完成 Passkey 注册 POST /passkey/register/finish
查询当前用户 Passkey GET /passkeys
删除当前用户 Passkey DELETE /passkey
获取当前用户信息 GET /profile
重置密码(安全码) PUT /password/reset
修改密码 PUT /password
更新个人资料 PUT /profile
获取用户菜单 GET /menus
修改登录标识 PUT /identifier
开启 TFA PUT /tfa/enable
关闭 TFA PUT /tfa/disable
获取 TOTP Key GET /tfa/key
获取 TFA 状态 GET /tfa/status

1.3 通用响应结构

{
  "code": 0,
  "msg": "ok",
  "trace": {
    "id": "afeade2f5957-tcdtjo-gdmaj",
    "desc": ""
  },
  "data": {}
}

2. 完整调用链路

2.1 鉴权接口(需要登录)

sequenceDiagram
  participant C as Client
  participant RL as AdminAuthRateLimit
  participant MA as CheckAdminAuth
  participant H as AdminAuth Handler
  participant S as AuthService
  participant R as Repository/Redis

  C->>RL: 请求 /{apiPrefix}/internal/admin/auth/*
  RL->>MA: 进入鉴权中间件(仅受保护接口)
  MA->>S: VerifyToken + HasRole/HasPermission
  MA-->>H: 通过后写入 user_id/user_name
  H->>S: 业务方法
  S->>R: DB/Redis/外部OAuth
  R-->>S: 返回数据
  S-->>H: errCode + data
  H-->>C: 统一 JSON 响应
Loading

2.2 非鉴权接口(公开)

/oauth/url/token/passkey/login/options/passkey/login/finish/reauth/oauth/bind/confirm/password/reset 跳过 CheckAdminAuth。 这些公开接口仍按路由配置受限流控制。

3. 鉴权与权限机制

3.1 Token 解析与校验

来自 app/http/middleware/check_admin_auth.go

  1. 先读 Header:Authorization: Bearer <token>
  2. Header 无 token 时再读 Cookie:admin-token=<token>
  3. 调用 AuthService.VerifyToken 做 JWT 校验。
  4. 成功后写入上下文:user_iduser_name

JWT 使用 HS256,过期时间来自 config.System.Admin.TokenExpireIn(若 admin 配置为空则回退到系统配置)。

3.2 权限校验

  1. 先调用 HasRole(userID, "super_admin")
  2. super_admin:直接放行。
  3. 否则计算权限 hash:MD5(HTTP_METHOD + RequestPathWithoutQuery)
  4. 调用 HasPermission(userID, permissionHash)

3.3 鉴权失败错误码

Code 含义 触发位置
10001 未登录/未携带 token 中间件默认值(未拿到 token)
11005 用户授权已过期 jwt.ErrTokenExpired
11007 标识结构异常 jwt.ErrTokenMalformed
11009 Token 签名无效 jwt.ErrTokenSignatureInvalid
11006 用户授权失败 其他 token 校验失败
11008 用户权限不足 非 super_admin 且无接口权限

4. 核心数据结构

4.1 AuthParamPOST /token 请求体)

字段 类型 必填 说明
identifier string password 模式传邮箱/手机号;totp 模式传 safe_code
grant_type string password / totp / feishu / wechat
state string OAuth 回调 state(feishu/wechat 场景使用)
credentials string password 模式必须传 md5(明文密码)totp 模式传验证码;OAuth 模式传 code

4.2 AccessTokenPOST /token / POST /passkey/login/finish / POST /oauth/bind/confirm 成功响应 data

字段 类型 说明
safe_code string NeedTfa(11028) / NeedResetPWD(11015) 返回
token string 登录成功返回 JWT
expires_in int64 token 过期秒数
bind_ticket string NeedBindOAuth(11042) 时返回,用于后续绑定确认
oauth_profile object NeedBindOAuth(11042) 时返回的第三方资料预览(当前支持 user_nameavatar
syncable_fields array NeedBindOAuth(11042) 时返回的可同步字段列表(当前可能包含 user_nameavatar

4.2.1 ReauthResultPOST /reauth 响应 data

字段 类型 说明
safe_code string 第一阶段密码校验通过但仍需 TFA 时返回,action=high_risk_reauth
reauth_ticket string 重认证完成后返回,用于后续绑定/解绑/删除 Passkey 等高风险操作

4.2.2 PasskeyOptionsResultPOST /passkey/login/options / POST /passkey/register/options 响应 data

字段 类型 说明
challenge_id string 服务端生成的验证请求标识,finish 阶段必须原样回传
options object WebAuthn 外层 options 对象;登录场景可直接作为 navigator.credentials.get(options) 的参数,注册场景可直接作为 navigator.credentials.create(options) 的参数

说明:

  • 登录场景返回 CredentialRequestOptions 兼容结构,其中 options.publicKeyPublicKeyCredentialRequestOptions
  • 注册场景返回 CredentialCreationOptions 兼容结构,其中 options.publicKeyPublicKeyCredentialCreationOptions

4.2.3 PasskeyItemGET /passkeys / POST /passkey/register/finish 成功响应)

字段 类型 说明
id uint Passkey 主键
display_name string 设备展示名称
aaguid string Authenticator AAGUID,十六进制字符串;可能为空
transports array 浏览器上报的传输方式,如 internalhybrid
last_used_at string/null 最近一次成功登录时间
created_at string 创建时间

4.2.4 PasskeyCredentialPOST /passkey/register/finish / POST /passkey/login/finish 请求体)

字段 类型 必填 说明
id string 浏览器返回的 credential id
raw_id string 原始 rawId;若为空会回退到 id
type string 默认 public-key
response object 浏览器返回的 WebAuthn response 结构

说明:

  • response 内支持浏览器常见 camelCase 键名,也兼容 snake_case 键名。
  • 服务端会将其转换为 WebAuthn 协议对象后再校验。

4.3 safe_code(Redis 存储结构)

  • Redis key:admin:system:auth:safeCode:{code}
  • value(JSON,按不同 action 携带不同字段):
    {
      "user_id": 1,
      "action": "tfa"
    }
  • action 可为:tfareset_passwordhigh_risk_reauth
  • high_risk_reauth 用途:本地账号 Reauth 第一阶段密码校验通过后,若该用户已开启 TFA,则返回一次性 safe_code 供第二阶段提交 totp_code
  • TTL:config.System.Admin.SafeCodeExpireIn
  • 一次性消费:parseSafeCode 读取后删除

4.4 bind_ticket / reauth_ticket(Redis 存储结构)

  • bind_ticket
    • key:admin:system:auth:bindTicket:{code}
    • value:provider/provider_tenant/provider_subject/oauth_profile
    • 用途:第三方身份已验真,但尚未与本地账号建立绑定
  • reauth_ticket
    • key:admin:system:auth:reauthTicket:{code}
    • value:user_id + action=high_risk_reauth
    • 用途:高风险操作前确认本地账号所有权
  • 两类 ticket 都不会在读取时立即消费;bind_ticket 会在绑定成功且登录态签发完成后删除,reauth_ticket 会在绑定/解绑/删除当前用户 Passkey 成功后删除。

4.5 OAuth state(Redis 存储结构)

  • Redis key:admin:system:auth:oauth:{state}
  • value:feishuwechat
  • TTL:180 秒
  • POST /token 的 OAuth 分支成功读取后删除

4.5.1 Passkey 验证请求(challenge,Redis 存储结构)

  • Redis key:admin:system:auth:passkey:challenge:{challenge_id}
  • value(JSON):
    {
      "action": "login",
      "challenge_id": "RANDOM_CHALLENGE_ID",
      "session_data": {},
      "created_at": 1738838400
    }
  • action 可为:loginregister
  • user_id / display_name 仅注册阶段会写入;无账号 Passkey 登录不会预先绑定用户。
  • TTL:config.System.Admin.WebAuthn.ChallengeExpireIn,默认 180 秒
  • POST /passkey/*/finish 成功或失败返回前都会尝试消费验证请求;验证请求丢失或超时返回 11050,内容不匹配返回 11051

4.6 菜单树结构(GET /menusdata.items[]

来自 system.Menu

字段 类型 说明
ID uint 菜单 ID(来自 gorm.Model
name string 菜单名称
path string 菜单路径
permission_id uint 关联权限 ID
parent_id uint 父菜单 ID(0 表示根)
icon string 图标
sort int 排序
children array 子菜单(递归同结构)

说明:结构体嵌入 gorm.Model,序列化时还可能包含 CreatedAt/UpdatedAt/DeletedAt 字段。

4.7 TOTP 相关数据

  • GET /tfa/key 返回:
    • totp_key:32 位 Base32 随机串
    • qr_codedata:image/png;base64,...
  • TOTP 校验参数:6 位验证码,30 秒步长,窗口 ±1 个时间片。

4.8 当前用户信息结构(GET /profile 响应 data

字段 类型 说明
id uint 用户 ID
user_name string 用户名
avatar string 头像 URL
email string 脱敏后的邮箱展示值,不返回明文
phone string 脱敏后的手机号展示值,不返回明文
role_name string 角色名称:超级管理员 / 管理员 / 普通用户

role_name 判定规则:

  • 包含 super_admin超级管理员
  • 仅包含 base普通用户
  • base 基础上有其他角色(且不包含 super_admin):管理员

4.9 WebAuthn 配置(system.admin.webauthn

字段 类型 说明
rp_id string Relying Party ID,Passkey 功能必填;通常填写前端域名或其父域名,不带协议、路径、端口
rp_display_name string Relying Party 展示名;为空时默认 Dudu Admin
rp_origins array 允许发起 WebAuthn 的前端 origin 列表,Passkey 功能必填;每项都必须是完整 origin
challenge_expire_in int Passkey 验证请求有效期,单位秒;默认 180
user_verification string 用户验证策略;支持 required / preferred / discouraged,默认 preferred

说明:

  • rp_idrp_origins 未配置,Passkey 注册/登录接口会返回 500
  • 当前实现使用 discoverable Passkey 登录,注册时会要求 resident key;因此 rp_id 会直接决定凭证作用域与后续子域共享边界。
  • challenge_expire_in 同时影响 Redis 中验证请求的 TTL,以及 WebAuthn 注册/登录超时时间。
  • challenge_expire_in <= 0 时会回退到 180user_verification 为空或不是合法值时会按 preferred 处理。
  • 示例配置可参考 bin/configs/dev.jsonbin/configs/local.json.defaultbin/configs/prod.json

rp_id

rp_id 表示 Passkey 所属的 Relying Party 域范围,是最重要的 WebAuthn 配置项之一。

  • 应填写域名或主机名,例如 localhost127.0.0.1admin.example.comexample.com
  • 不要填写 https://admin.example.comadmin.example.com:3000/login 这类带协议、端口或路径的值。
  • 当前页面若运行在 https://a.b.com,通常可选 a.b.comb.com;两者都可能合法,但作用域不同:
    • a.b.com:作用域更窄,只面向当前子域。
    • b.com:作用域更宽,适合明确需要多个子域共享 Passkey 的场景。
  • 一旦生产环境已签发 Passkey,再修改 rp_id,旧凭证通常需要重新注册。

rp_display_name

rp_display_name 是站点展示名,主要用于浏览器或系统弹窗里给用户显示“这是谁在请求 Passkey”。

  • 建议填写稳定的产品名,例如 Dudu AdminAcme Admin
  • 为空时后端会回退为 Dudu Admin
  • 该字段不是凭证存储提供方名称,不会决定用户看到的是 iCloud KeychainApple PasswordsGoogle Password Manager 还是 Bitwarden
  • iCloud KeychainBitwarden 这类信息通常表示凭证管理器或存储位置,由浏览器、系统和密码管理器决定,不是本项目自动推导出来的。

rp_origins

rp_origins 表示允许发起 WebAuthn 的前端来源列表;这里填的是前端页面 origin,不是后端 API 地址。

  • 每项都必须是完整 origin,包含协议、主机和可选端口,例如:
    • http://localhost:3000
    • http://127.0.0.1:3000
    • https://admin.example.com
  • 不要写成 /adminadmin.example.comhttps://admin.example.com/loginhttps://api.example.com 这类不匹配当前前端页面来源的值。
  • 如果管理端存在多个合法入口,需要将所有入口都加入数组。
  • rp_id 决定凭证归属范围,rp_origins 决定哪些页面允许发起 WebAuthn;两者必须同时正确。

challenge_expire_in

challenge_expire_in 表示一次 Passkey 验证请求从生成到失效的时间窗口,单位为秒。

  • 默认值为 180
  • 当前实现会把该值同时用于:
    • Redis 中 passkey challenge 的 TTL;
    • WebAuthn 注册超时;
    • WebAuthn 登录超时。
  • 建议取值:
    • 本地开发:180
    • 日常生产:120300
  • 值过小容易导致用户尚未完成系统弹窗就已过期;值过大则会增加请求被重复利用的时间窗口。

user_verification

user_verification 控制认证器是否要求用户在本地完成“本人验证”,例如指纹、人脸或设备 PIN。

  • required:必须完成本地验证,安全性最高,兼容性相对更严格。
  • preferred:尽量完成本地验证;当前项目默认值,也是大多数后台系统更平衡的选择。
  • discouraged:尽量不强制本地验证,通常不建议用于高敏感后台。
  • 如果配置为空或写成其他非法值,当前实现会按 preferred 处理,不会抛出单独的配置错误。

推荐配置示例

本地开发:

"webauthn": {
  "rp_id": "localhost",
  "rp_display_name": "Dudu Admin",
  "rp_origins": ["http://localhost:3000"],
  "challenge_expire_in": 180,
  "user_verification": "preferred"
}

单域生产:

"webauthn": {
  "rp_id": "admin.example.com",
  "rp_display_name": "Dudu Admin",
  "rp_origins": ["https://admin.example.com"],
  "challenge_expire_in": 180,
  "user_verification": "preferred"
}

多个子域共享 Passkey:

"webauthn": {
  "rp_id": "example.com",
  "rp_display_name": "Dudu Admin",
  "rp_origins": [
    "https://admin.example.com",
    "https://console.example.com"
  ],
  "challenge_expire_in": 180,
  "user_verification": "preferred"
}

最后一种写法只适合你明确要在多个子域共享同一套 Passkey 体系的场景;如果只是单后台域名,优先使用更窄的 rp_id

4.10 Passkey 日志脱敏约定

  • /passkey/register/finish/passkey/login/finish 不记录原始操作载荷。
  • 请求日志会对 challenge_idcredentialattestationassertionpublic_keysignatureuser_handle 等敏感字段做脱敏。

5. 登录与安全流程

5.1 标识密码登录(grant_type=password

  1. 校验 identifier/credentials 非空(否则 11000)。
  2. 校验 identifier 为邮箱或手机号(否则 11007)。
  3. 查询用户:identifier(email/phone) + status=1(不存在 11002)。
  4. 校验密码:使用 bcrypt 校验前端传入的 credentials(失败返回 11001)。
  5. 若用户已开启 TFA => 11028 并返回 safe_code(action=tfa)
  6. 其余情况生成 JWT 并返回 token + expires_in

5.2 TOTP 二次登录(grant_type=totp

  1. 参数映射:identifier=safe_codecredentials=totp_code
  2. 校验 safe_code 并要求 action=tfa(失败 11030)。
  3. 校验用户 TOTP(失败 11029)。
  4. 成功返回 JWT。

5.3 重置密码(PUT /password/reset

  1. 校验 safe_code/password 非空(11034 / 11032)。
  2. 解析 safe_codeaction=reset_password(否则 11030)。
  3. 更新密码:以 bcrypt 存储 password(当前接口口径下,password 为前端传入的 md5(明文密码))。

5.4 OAuth 登录(飞书/企业微信)

5.4.1 GET /oauth/url

  • 生成 16 位 state,缓存 180 秒。
  • type 构造重定向 URL:
    • feishu
    • wechatlogin_type=qrcode 时走企业微信扫码地址)

5.4.2 POST /tokengrant_type=feishu/wechat

  1. 校验 state 与 oauthType 匹配(否则 11041)。
  2. 通过对应 provider API 换取用户标识。
  3. (provider, provider_tenant, provider_subject) 查询 sys_user_identity
  4. 若已绑定用户,直接生成 JWT,并更新该 identity 的 last_login_at
  5. 若第三方未绑定用户,返回 11042 且携带 bind_ticket
  6. NeedBindOAuth 场景会同时返回第三方资料预览与可同步字段列表;前端可让用户勾选本次要同步的字段。

5.5 本地账号重认证(POST /reauth

第一阶段:密码校验

  1. 传入本地 identifier + password
  2. 使用密码验证本地账号所有权。
  3. 若用户未开启 TFA,直接返回短期 reauth_ticket
  4. 若用户已开启 TFA,返回 11028,并携带 safe_code(action=high_risk_reauth)

第二阶段:TOTP 校验

  1. 传入第一阶段返回的 safe_code 与当前 totp_code
  2. 校验 safe_code 且要求 action=high_risk_reauth(失败 11030)。
  3. 查询该 safe_code 对应用户,并校验用户仍处于启用状态。
  4. 校验用户 TOTP(失败 11029)。
  5. 成功后返回短期 reauth_ticket,仅用于绑定/解绑/删除 Passkey 等高风险操作。

5.5.1 已登录用户敏感操作统一二次验证

  1. 前端先调用 GET /reauth/methods 获取当前用户的可用验证方式、默认方式和 password_requires_totp 标记。
  2. 若当前用户已配置 Passkey,则默认方式为 passkey;用户仍可手动切换到密码链路。
  3. Passkey 链路:
    • 调用 POST /reauth/passkey/options 获取 challenge_id + options
    • 浏览器执行 navigator.credentials.get(options)
    • 调用 POST /reauth/passkey/finish 完成校验并换取 reauth_ticket
  4. 密码链路:
    • 调用 POST /reauth/password 提交当前密码
    • 若用户未开启 TFA,直接返回 reauth_ticket
    • 若用户已开启 TFA,返回 11028 + safe_code
    • 再调用 POST /reauth/totp 提交 safe_code + totp_code,换取 reauth_ticket
  5. 以下敏感操作统一要求先完成上述验证并携带 reauth_ticket
    • 修改密码
    • 修改登录标识
    • 管理第三方账号
    • 管理 Passkey
    • 管理双因素认证

5.6 第三方账号绑定确认(POST /oauth/bind/confirm

  1. 传入 bind_ticketreauth_ticket
  2. 校验两张 ticket 均有效,且 reauth_ticket.action=high_risk_reauth
  3. 在事务中校验目标第三方身份未被其他用户占用。
  4. 写入 sys_user_identity
  5. 若传入 sync_fields,同步对应资料到 sys_user
  6. 绑定成功后回查目标用户并直接签发 JWT,返回 token + expires_in
  7. 成功后消费两张 ticket。

完整流程:第三方登录绑定本地账号

  1. 前端先调用 GET /oauth/url 获取第三方授权地址,并跳转到第三方完成授权。
  2. 第三方回调后,前端携带 code + state 调用 POST /tokengrant_type=feishu/wechat)。
  3. 若该第三方身份已绑定本地账号,则 POST /token 直接返回 JWT,流程结束。
  4. 若该第三方身份尚未绑定本地账号,则 POST /token 返回 11042,并携带 bind_ticket + oauth_profile + syncable_fields
  5. 前端提示用户输入要绑定的本地账号和密码,并调用 POST /reauth 第一阶段。
  6. 若本地账号未开启 TFA,则 POST /reauth 直接返回 reauth_ticket
  7. 若本地账号已开启 TFA,则 POST /reauth 返回 11028 + safe_code;前端继续提示输入 TOTP,再调用 POST /reauth 第二阶段换取 reauth_ticket
  8. 前端携带 bind_ticket + reauth_ticket 调用 POST /oauth/bind/confirm;如用户勾选了资料同步项,再附带 sync_fields
  9. 绑定成功后,接口会直接返回 JWT,前端应立即建立登录态,无需再额外调用一次登录接口。
  10. 此后再次走同一第三方登录时,将直接命中已绑定用户并返回 JWT。

5.7 Passkey 登录

  1. 前端直接调用 POST /passkey/login/options,无需先提交账号。
  2. 服务端返回验证请求标识 challenge_idoptions
  3. 前端将 options 作为整个参数对象传给 navigator.credentials.get(options),获取浏览器返回的 assertion。
  4. 前端把 challenge_id + credential 提交到 POST /passkey/login/finish
  5. 服务端根据凭证中的 userHandlecredential id 定位用户,完成验证请求校验、WebAuthn 签名校验,并回写 sign_count + last_used_at,最终返回 token + expires_in

补充说明:

  • 登录阶段 response 字段应保持浏览器原样透传,服务端同时支持 camelCase 与 snake_case 键名。
  • POST /passkey/login/optionsPOST /passkey/login/finish 走管理端鉴权频控。
  • 当前项目不做历史兼容;切换到无账号 Passkey 登录后,开发阶段已注册的旧 Passkey 需要删除并重新注册。

5.8 已绑定账号查询与解绑

  • GET /oauth/accounts:返回当前登录用户已绑定的第三方账号列表,每条记录都包含 id,前端解绑时必须使用该标识。
  • POST /oauth/unbind:请求体必须传 identity_id + reauth_ticket;服务端只会解绑这条指定 identity,解绑前会校验账号至少保留一种可用登录方式。

5.9 Passkey 注册与自主管理

  1. 当前登录用户先完成“已登录用户敏感操作统一二次验证”,再调用 POST /passkey/register/options,并携带 reauth_ticket
  2. 服务端返回验证请求标识 challenge_idoptions,前端将 options 作为整个参数对象传给 navigator.credentials.create(options)
  3. 前端把 challenge_id + credential 提交到 POST /passkey/register/finish,成功后返回新建 PasskeyItem
  4. GET /passkeys 可查询当前用户已绑定的全部 Passkey。
  5. DELETE /passkey 需携带 id + reauth_ticket 删除单个 Passkey;若删除后将不再保留任何可用登录方式,返回 11049
  6. 若 credential 已存在,注册完成阶段返回 11053;若凭证验证失败则返回 11054

6. 接口明细(逐路由)

6.1 获取 OAuth 登录地址

  • Method:GET

  • Path/{apiPrefix}/internal/admin/auth/oauth/url

  • 鉴权:否

  • Query

    字段 类型 必填 说明
    type string feishu / wechat
    login_type string wechat 可传 qrcode
  • 响应data.url(string)

  • 错误码40011040500


6.2 换取登录 Token

  • Method:POST
  • Path/{apiPrefix}/internal/admin/auth/token
  • 鉴权:否
  • BodyAuthParam
  • 响应AccessToken
  • OAuth 未绑定时返回示例
    {
      "code": 11042,
      "msg": "NeedBindOAuth",
      "trace": {
        "id": "afeade2f5957-tcdtjo-gdmaj",
        "desc": ""
      },
      "data": {
        "bind_ticket": "BIND_TICKET",
        "oauth_profile": {
          "user_name": "张三",
          "avatar": "https://example.com/avatar.png"
        },
        "syncable_fields": ["user_name", "avatar"]
      }
    }
  • 错误码(按分支)
    • 通用:40011010
    • password:1100011001110021101511028
    • totp:110291103011002
    • OAuth:11041500

6.3 获取当前用户信息

  • Method:GET
  • Path/{apiPrefix}/internal/admin/auth/profile
  • 鉴权:是
  • 响应
    {
      "id": 1,
      "user_name": "admin",
      "avatar": "https://...",
      "email": "a***n@e*****e.com",
      "phone": "+86*******0000",
      "role_name": "管理员"
    }
  • 标识字段说明
    • emailphone 仅用于前端展示,响应中返回脱敏值,不返回数据库中的明文标识。
    • 前端如直接基于该响应初始化“修改登录标识”表单,可在未修改某字段时原样回传该脱敏值;服务端会识别为“该字段未变更”。
  • role_name 说明
    • 超级管理员:用户角色中包含 super_admin
    • 普通用户:用户角色仅包含 base
    • 管理员:在 base 基础上存在其他角色(且不包含 super_admin
  • 错误码:第 3 章鉴权错误码
  • 业务边界说明:按 service 逻辑,用户不存在时应为 11002;但当前 handler 会在 err == nil 时强制置 code=0,因此该场景可能返回 code=0, data=null(当前实现行为)。

6.4 重置密码(安全码)

  • Method:PUT

  • Path/{apiPrefix}/internal/admin/auth/password/reset

  • 鉴权:否

  • Body

    字段 类型 必填 说明
    safe_code string POST /token 返回的安全码
    password string 新密码,前端必须传 md5(明文密码)(服务端使用 bcrypt 存储)
  • 错误码40011032110341103011002500


6.5 修改密码

  • Method:PUT

  • Path/{apiPrefix}/internal/admin/auth/password

  • 鉴权:是

  • Body

    字段 类型 必填 说明
    reauth_ticket string 通过统一敏感操作验证流程换取的票据
    password string 新密码,前端必须传 md5(新明文密码)(服务端使用 bcrypt 存储)
  • 校验规则

    • 必须先完成统一敏感操作验证。
    • 有 Passkey 时默认优先使用 Passkey,也可手动切换到密码链路。
    • 无 Passkey 且已开启 TFA 时,前端需按“密码 -> TOTP”两阶段换取 reauth_ticket
  • 错误码40011032110461104711002500 + 第 3 章鉴权错误码


6.6 更新个人资料

  • Method:PUT

  • Path/{apiPrefix}/internal/admin/auth/profile

  • 鉴权:是

  • Body

    字段 类型 必填 说明
    user_name string 用户名(受保留词规则限制)
    avatar string 头像 URL
  • 保留词规则admin/root/administrator/管理员/超级管理员/seakee/super_admin/superAdmin(前缀或后缀命中均无效)

  • 错误码4001100711002500 + 第 3 章鉴权错误码


6.7 获取用户菜单

  • Method:GET
  • Path/{apiPrefix}/internal/admin/auth/menus
  • 鉴权:是
  • 响应data.items(见 4.6 菜单树结构)
  • 权限逻辑
    • super_admin:返回全部菜单树
    • 普通用户:按角色权限聚合菜单,并自动补齐父级菜单后返回树形结构
  • 错误码:当前控制器将 service 异常统一映射为 400,并附带错误信息;另含第 3 章鉴权错误码

6.8 修改登录标识

  • Method:PUT

  • Path/{apiPrefix}/internal/admin/auth/identifier

  • 鉴权:是

  • Body

    字段 类型 必填 说明
    reauth_ticket string 通过统一敏感操作验证流程换取的票据
    email string 新邮箱(需合法格式)
    phone string 新手机号(需合法格式)
  • 校验规则

    • 必须先完成统一敏感操作验证。
    • 缺失或非法 reauth_ticket 返回 11046 / 11047
    • emailphone 为空时表示不提交该标识;两者都为空返回 11014
    • 若前端直接回传 GET /profile 中对应字段的脱敏值,服务端会按“当前字段未修改”处理,并恢复为当前账号的原值后继续做唯一性和格式校验。
    • 只有用户实际输入的新邮箱/新手机号需要满足合法格式并通过唯一性校验。
  • 错误码400110071101411046110471100211013500 + 第 3 章鉴权错误码


6.9 本地账号重认证

  • Method:POST

  • Path/{apiPrefix}/internal/admin/auth/reauth

  • 鉴权:否

  • Body

    字段 类型 必填 说明
    identifier string 第一阶段必填 本地邮箱/手机号
    password string 第一阶段必填 md5(当前明文密码)
    safe_code string 第二阶段必填 第一阶段返回的安全码,要求 action=high_risk_reauth
    totp_code string 第二阶段必填 当前用户 TOTP 验证码
  • 调用方式

    • 第一阶段:只传 identifier + password
    • 第二阶段:只传 safe_code + totp_code
  • 第一阶段请求示例

    {
      "identifier": "admin@example.com",
      "password": "md5(当前明文密码)"
    }
  • 第一阶段需继续 TFA 时响应示例

    {
      "safe_code": "SAFE_CODE"
    }
  • 第二阶段请求示例

    {
      "safe_code": "SAFE_CODE",
      "totp_code": "123456"
    }
  • 成功响应

    {
      "reauth_ticket": "REAUTH_TICKET"
    }
  • 说明

    • 若第一阶段命中 11028,表示本地账号密码已通过,但还需要继续提交第二阶段 TOTP。
    • safe_code 为一次性凭证,读取校验后即消费,不能重复使用。
  • 错误码40011000110011100211028110301103311029500


6.9.1 查询敏感操作验证方式

  • Method:GET
  • Path/{apiPrefix}/internal/admin/auth/reauth/methods
  • 鉴权:是
  • 成功响应
    • default_methodpasskey / password
    • available_methods:当前可选方式列表
    • password_requires_totp:密码链路是否还需继续输入 TOTP
    • totp_enabled:当前用户是否已开启 TFA
    • passkey_count:当前用户已注册 Passkey 数量

6.9.2 密码验证敏感操作

  • Method:POST

  • Path/{apiPrefix}/internal/admin/auth/reauth/password

  • 鉴权:是

  • Body

    字段 类型 必填 说明
    password string md5(当前明文密码)
  • 行为说明

    • 若用户未开启 TFA,直接返回 reauth_ticket
    • 若用户已开启 TFA,返回 11028 + safe_code

6.9.3 TOTP 验证敏感操作

  • Method:POST

  • Path/{apiPrefix}/internal/admin/auth/reauth/totp

  • 鉴权:是

  • Body

    字段 类型 必填 说明
    safe_code string POST /reauth/password 返回的安全码
    totp_code string 当前用户 TOTP 验证码
  • 成功响应

    {
      "reauth_ticket": "REAUTH_TICKET"
    }

6.9.4 获取 Passkey 敏感操作验证请求

  • Method:POST
  • Path/{apiPrefix}/internal/admin/auth/reauth/passkey/options
  • 鉴权:是
  • 响应PasskeyOptionsResult

6.9.5 完成 Passkey 敏感操作验证

  • Method:POST

  • Path/{apiPrefix}/internal/admin/auth/reauth/passkey/finish

  • 鉴权:是

  • Body

    字段 类型 必填 说明
    challenge_id string POST /reauth/passkey/options 返回的验证请求标识
    credential object 浏览器返回的 PasskeyCredential
  • 成功响应

    {
      "reauth_ticket": "REAUTH_TICKET"
    }

6.10 确认绑定第三方

  • Method:POST

  • Path/{apiPrefix}/internal/admin/auth/oauth/bind/confirm

  • 鉴权:否

  • Body

    字段 类型 必填 说明
    bind_ticket string POST /token 返回的绑定票据
    reauth_ticket string POST /reauth 返回的重认证票据
    sync_fields array 用户勾选的同步字段列表;当前支持 user_nameavatar
  • 请求示例

    {
      "bind_ticket": "BIND_TICKET",
      "reauth_ticket": "REAUTH_TICKET",
      "sync_fields": ["user_name", "avatar"]
    }
  • 成功响应AccessToken

    {
      "code": 0,
      "msg": "ok",
      "trace": {
        "id": "afeade2f5957-tcdtjo-gdmaj",
        "desc": ""
      },
      "data": {
        "token": "JWT_TOKEN",
        "expires_in": 7200
      }
    }
  • 行为说明

    • 该接口是“第三方登录未绑定”链路的最后一步。
    • 绑定成功后会直接签发登录态,前端无需再额外调用 POST /token
    • 若本次勾选 sync_fields,返回的 JWT 会基于同步后的最新用户资料生成。
  • 错误码400110441104511046110471104311002500


6.11 查询已绑定第三方

  • Method:GET

  • Path/{apiPrefix}/internal/admin/auth/oauth/accounts

  • 鉴权:是

  • 响应data.list[]

    字段 类型 说明
    id uint identity 主键,解绑时必须使用
    provider string 第三方类型,如 feishu / wechat
    provider_tenant string 第三方租户标识
    display_name string 第三方侧展示名称
    avatar_url string 第三方侧头像地址
    bound_at string 绑定时间
    last_login_at string 最近一次通过该身份登录时间
  • 错误码11002500 + 第 3 章鉴权错误码


6.12 解绑第三方

  • Method:POST

  • Path/{apiPrefix}/internal/admin/auth/oauth/unbind

  • 鉴权:是

  • Body

    字段 类型 必填 说明
    identity_id uint GET /oauth/accounts 返回的 identity 主键
    reauth_ticket string POST /reauth 返回的重认证票据
  • 请求示例

    {
      "identity_id": 12,
      "reauth_ticket": "REAUTH_TICKET"
    }
  • 行为说明

    • 服务端按 identity_id 精确解绑,不再根据 provider/provider_tenant 批量删除。
    • 解绑会物理删除对应 sys_user_identity 记录,后续允许重新绑定同一个第三方身份。
    • 若该账号只剩最后一种可用登录方式,则返回 11049
  • 错误码4001104611047110481104911002500 + 第 3 章鉴权错误码


6.13 获取 Passkey 登录验证请求

  • Method:POST
  • Path/{apiPrefix}/internal/admin/auth/passkey/login/options
  • 鉴权:否
  • 响应PasskeyOptionsResult
  • 说明
    • data.options 为 WebAuthn 登录外层 options 对象,可直接作为 navigator.credentials.get(data.options) 的参数。
    • 无需传账号,服务端会生成 discoverable Passkey 登录所需的 options。
    • 该接口启用管理端登录频控。
  • 错误码40011056500

6.14 完成 Passkey 登录

  • Method:POST

  • Path/{apiPrefix}/internal/admin/auth/passkey/login/finish

  • 鉴权:否

  • Body

    字段 类型 必填 说明
    challenge_id string POST /passkey/login/options 返回的验证请求标识
    credential object 浏览器返回的 PasskeyCredential
  • 成功响应AccessToken

  • 错误码4001100211050110511105211054500


6.15 获取 Passkey 注册验证请求

  • Method:POST

  • Path/{apiPrefix}/internal/admin/auth/passkey/register/options

  • 鉴权:是

  • Body

    字段 类型 必填 说明
    reauth_ticket string 通过统一敏感操作验证流程换取的票据
    display_name string 自定义设备展示名;为空时后端按用户资料自动生成
  • 响应PasskeyOptionsResult

  • 说明

    • data.options 为 WebAuthn 注册外层 options 对象,可直接作为 navigator.credentials.create(data.options) 的参数。
    • 该接口会在生成 challenge 成功后消费 reauth_ticket
  • 错误码40011002110461104711055500 + 第 3 章鉴权错误码


6.16 完成 Passkey 注册

  • Method:POST

  • Path/{apiPrefix}/internal/admin/auth/passkey/register/finish

  • 鉴权:是

  • Body

    字段 类型 必填 说明
    challenge_id string POST /passkey/register/options 返回的验证请求标识
    credential object 浏览器返回的 PasskeyCredential
  • 成功响应PasskeyItem

  • 错误码400110021105011051110531105411055500 + 第 3 章鉴权错误码


6.17 查询当前用户 Passkey

  • Method:GET

  • Path/{apiPrefix}/internal/admin/auth/passkeys

  • 鉴权:是

  • 响应data.list[]

    字段 类型 说明
    id uint Passkey 主键
    display_name string 设备展示名
    aaguid string Authenticator AAGUID,可能为空
    transports array 浏览器上报的传输方式
    last_used_at string/null 最近使用时间
    created_at string 创建时间
  • 错误码11002500 + 第 3 章鉴权错误码


6.18 删除当前用户 Passkey

  • Method:DELETE

  • Path/{apiPrefix}/internal/admin/auth/passkey

  • 鉴权:是

  • Body

    字段 类型 必填 说明
    id uint 要删除的 Passkey 主键
    reauth_ticket string POST /reauth 返回的重认证票据
  • 行为说明

    • 按当前登录用户 + id 精确删除。
    • 删除前必须先完成一次高风险重认证,并提交 reauth_ticket
    • 若当前账号只剩最后一种可用登录方式,则返回 11049
    • 缺失或非法 id/reauth_ticket 会在 handler 层直接返回 400
  • 错误码40011046110471104911052500 + 第 3 章鉴权错误码


6.19 开启 TFA

  • Method:PUT

  • Path/{apiPrefix}/internal/admin/auth/tfa/enable

  • 鉴权:是

  • Body

    字段 类型 必填 说明
    reauth_ticket string 通过统一敏感操作验证流程换取的票据
    totp_code string 基于 totp_key 生成的验证码
    totp_key string /tfa/key 获取
  • 错误码4001103511033110291104611047500 + 第 3 章鉴权错误码


6.20 关闭 TFA

  • Method:PUT

  • Path/{apiPrefix}/internal/admin/auth/tfa/disable

  • 鉴权:是

  • Body

    字段 类型 必填 说明
    reauth_ticket string 通过统一敏感操作验证流程换取的票据
  • 错误码400110461104711002500 + 第 3 章鉴权错误码


6.21 获取 TOTP Key

  • Method:GET

  • Path/{apiPrefix}/internal/admin/auth/tfa/key

  • 鉴权:是

  • 响应

    字段 类型 说明
    totp_key string 新生成的 Base32 密钥(长度 32)
    qr_code string 对应二维码的 base64 data URL
  • 错误码11002(用户不存在) + 第 3 章鉴权错误码


6.22 获取 TFA 状态

  • Method:GET

  • Path/{apiPrefix}/internal/admin/auth/tfa/status

  • 鉴权:是

  • 响应

    字段 类型 说明
    enable bool 当前用户是否开启 TFA
  • 错误码11002(用户不存在) + 第 3 章鉴权错误码


7. 参数口径约定(前端必须遵守)

当前实现中,密码相关接口仍要求前端先传 md5(明文密码)。服务端会将该摘要使用 bcrypt 存储。

  1. POST /tokengrant_type=passwordcredentials 必须传 md5(明文密码)
  2. PUT /password/resetpassword 必须传 md5(新明文密码)
  3. 已登录用户的敏感安全操作统一走“先验证,后提交 reauth_ticket”:
    • 先调用 GET /reauth/methods 获取默认方式;
    • Passkey 链路:POST /reauth/passkey/options -> POST /reauth/passkey/finish
    • 密码链路:POST /reauth/password,必要时再调用 POST /reauth/totp
  4. PUT /password / PUT /identifier / PUT /tfa/enable / PUT /tfa/disable / POST /passkey/register/options / DELETE /passkey
    • 必须携带 reauth_ticket
    • PUT /passwordpassword 仍需传 md5(新明文密码)
  5. POST /reauth
    • 第一阶段传 identifier + password=md5(当前明文密码)
    • 若返回 11028,第二阶段必须改传 safe_code + totp_code
    • 不要在第二阶段重复传 identifier/password,统一按 safe_code 完成 TOTP 校验。
  6. POST /reauth/password
    • password 必须传 md5(当前明文密码)
    • 若返回 11028,继续改传 safe_code + totp_codePOST /reauth/totp
  7. POST /oauth/bind/confirm
    • 必须携带 bind_ticket + reauth_ticket
    • 若希望首次绑定时同步第三方资料,可传 sync_fields,当前支持 user_nameavatar
    • 成功后接口会直接返回 token + expires_in,前端应按登录成功处理。
  8. POST /passkey/login/options
    • 无需传账号;
    • 旧开发数据里的 Passkey 需要删除后重新注册,才能稳定支持无账号登录。
  9. POST /passkey/register/finish / POST /passkey/login/finish / POST /reauth/passkey/finish
    • credential 必须透传浏览器原始 WebAuthn 结果;
    • rawId/clientDataJSON/attestationObject/authenticatorData/signature/userHandle 支持 camelCase 与 snake_case 键名。
  10. POST /passkey/register/options
  • 必须先完成统一敏感操作验证,并携带 reauth_ticket
  • display_name 可选;
  • 若不传,后端会回退到 user_name 或当前登录标识,自动生成 xxx Passkey
  1. DELETE /passkey
  • 必须先完成统一敏感操作验证获取 reauth_ticket
  • 请求体必须携带 id + reauth_ticket

示例(密码链路完成敏感操作验证后修改密码):

{
  "reauth_ticket": "REAUTH_TICKET",
  "password": "md5(新明文密码)"
}

示例(完成统一验证后修改登录标识):

{
  "reauth_ticket": "REAUTH_TICKET",
  "email": "new_admin@example.com"
}