28 KiB
Login And Registration Technical Design
本文档定义 v1 登录、三方注册登录、密码设置和 token 生命周期。它只描述当前架构事实和接口边界,不记录实现流水账。
Scope
v1 只有一个用户创建入口:
- 三方身份登录即注册。
provider + provider_subject不存在时创建用户、生成default_display_user_id、设置当前display_user_id、绑定三方身份并签发 token。
v1 的密码能力只有一个前置条件:
- 用户必须已经通过三方登录拿到 access token,之后才能设置密码。
v1 不支持:
- 账号密码注册。
- 用户名、邮箱、手机号作为独立登录账号。
- 未登录状态设置密码。
- 手机验证码登录。
- 匿名游客登录。
- 多账号主动合并。
- 客户端直接访问
user-service。 - gateway 保存密码、三方凭证或 refresh token。
Ownership
登录注册归属 user-service,gateway 只做外部 HTTP JSON 入口和 gRPC 协议转换。
| Capability | Owner | Rule |
|---|---|---|
| HTTP JSON API | gateway-service |
只暴露 /api/v1/auth/*,统一返回 envelope |
| 用户创建 | user-service |
只能由三方身份首次登录触发 |
| 展示短号 | user-service |
新用户创建时生成默认短号,后续登录用当前有效短号;临时靓号有效期内会覆盖默认短号 |
| 密码身份 | user-service |
已登录用户手动设置后才存在 |
| 三方身份绑定 | user-service |
provider + subject 唯一绑定用户 |
| 三方凭证校验 | user-service |
生产环境必须由 provider 专用 verifier 校验 credential 后得到 subject |
| access token 签发 | user-service |
登录成功后签发 |
| access token 校验 | gateway-service |
业务 API 和设置密码入口校验 |
| refresh token | user-service |
只保存 hash,支持轮换和失效 |
| 房间身份态 | room-service |
只消费已鉴权 actor_user_id,不处理登录 |
| 腾讯云 IM 身份态 | gateway-service |
用 access token 鉴权后签发腾讯云 IM UserSig,不处理注册 |
gateway-service 不读取用户表,不校验密码 hash,不调用三方平台做最终身份判断。三方 provider secret 只存在于 user-service verifier 配置或服务端密钥系统。
External HTTP API
所有接口使用 gateway 业务响应 envelope:
{
"code": "OK",
"message": "ok",
"request_id": "req_xxx",
"data": {}
}
request_id 来自 X-Request-ID 或由 gateway 生成,并透传到内部 gRPC meta。
Third Party Login Or Register
POST /api/v1/auth/third-party/login
Request:
{
"provider": "google",
"credential": "google_id_token_or_auth_code",
"username": "hy",
"gender": "male",
"country": "CN",
"invite_code": "INV-1",
"device_id": "device_xxx",
"device": "iPhone 15",
"os_version": "iOS 18.1",
"avatar": "https://cdn.example/avatar.png",
"birth": "2000-01-02",
"app_version": "1.2.3",
"build_number": "123",
"source": "campaign-a",
"install_channel": "app_store",
"campaign": "spring_launch",
"platform": "ios",
"language": "zh-CN",
"timezone": "Asia/Shanghai"
}
Response data:
{
"user_id": 10001,
"display_user_id": "100001",
"default_display_user_id": "100001",
"display_user_id_kind": "default",
"display_user_id_expires_at_ms": 0,
"session_id": "sess_xxx",
"access_token": "jwt_xxx",
"refresh_token": "refresh_xxx",
"expires_in_sec": 1800,
"token_type": "Bearer",
"is_new_user": true
}
三方接口不拆注册和登录。新用户不会默认生成密码;注册资料和来源快照写入 users,同时 user_display_user_ids、third_party_identities 和首个 auth_sessions 会在同一事务中创建。birth 必须是 yyyy-mm-dd;platform 当前只接受 android 或 ios;gateway 兼容客户端误拼的 app_verison,进入内部 RPC 时统一为 app_version。客户端不能提交注册 IP、UA 或 IP 国家,register_ip 和 register_user_agent 来自 gateway 请求上下文,country_by_ip 来自服务端/可信边缘层根据入口 IP 形成的国家快照。provider_subject 只允许由 user-service 校验 provider credential 后得到,不能信任客户端直传,也不能把客户端传入的 credential 原样当成 subject。
注册字段必须在 user-service 写库前完成校验,失败统一返回 INVALID_ARGUMENT,不能让 MySQL VARCHAR 或 DATE 约束错误泄漏成 gateway UPSTREAM_ERROR。
| Field | Max | Format |
|---|---|---|
username |
64 | trimmed display name |
gender |
32 | client enum/string snapshot |
country |
2 | ISO 3166-1 alpha-2, normalized uppercase |
invite_code |
64 | optional invite code |
register_ip |
64 | gateway observed IP only |
register_user_agent |
512 | gateway observed User-Agent only |
country_by_ip |
2 | trusted edge/server country code only |
device_id |
128 | required, first refresh session binding |
device |
128 | device model/description |
os_version |
64 | client OS version |
avatar |
512 | absolute http or https URL |
birth |
date | yyyy-mm-dd |
app_version |
64 | client version |
build_number |
64 | client build number |
source |
64 | registration source |
install_channel |
64 | install channel |
campaign |
128 | attribution campaign |
platform |
16 | android or ios |
language |
32 | BCP 47 language tag subset, e.g. en-US |
timezone |
64 | IANA timezone name, e.g. America/Los_Angeles |
display_user_id 永远表示当前有效展示号。用户没有临时靓号时,它等于 default_display_user_id;用户有 active 靓号时,它等于靓号。
Third Party Credential Verification
生产环境的三方登录必须使用 provider 专用 verifier。StaticThirdPartyVerifier 只能用于单元测试、本地隔离联调或明确的假 provider,不能配置到线上 provider allowlist。
校验规则:
provider必须命中 user-service 配置 allowlist;未知 provider 返回AUTH_FAILED。- verifier 必须校验 credential 的签名、过期时间、issuer、audience/client_id/app_id、nonce 或等价防重放字段。
- verifier 必须从 provider 验证后的 payload 或服务端换票结果中提取稳定
provider_subject;客户端不能直传provider_subject。 - provider 返回可选 union id 时,
provider_subject仍然是登录绑定主键,provider_union_id只做辅助字段。 - provider 配置缺失、密钥缺失、JWKS/公钥加载失败或 provider 响应不可判定时必须 fail-closed,返回
AUTH_FAILED或服务端配置错误,不允许降级为静态 verifier。 - verifier 不能把 credential、id token、auth code、provider access token 写入日志、审计表或
users。
上线前必须按首批 provider 分别写清 credential 类型:
| Provider | Credential | Required Checks |
|---|---|---|
google |
ID token or server auth code | signature/JWKS, issuer, audience, expiry, nonce when present |
apple |
identity token or authorization code | signature/JWKS, issuer, audience, expiry, nonce when present |
facebook |
access token or limited-login token | app id, token validity, expiry, subject from provider API or signed token |
tiktok |
auth code or access token | app/client key, expiry, subject from provider API |
如果首版仍使用本地静态 verifier 联调,配置名和注释必须明确标记为 local/mock,线上配置示例不能把真实 provider 指向静态 verifier。
Set Password
POST /api/v1/auth/password/set
Authorization: Bearer <access_token>
Request:
{
"password": "plain_password"
}
Response data:
{
"password_set": true
}
设置密码只接受已登录用户的 access token。客户端不能在 body 中自报 user_id 或 display_user_id。重复设置密码返回 PASSWORD_ALREADY_SET,后续如果要支持改密,必须新增独立改密接口和旧密码校验规则。
Update Profile
POST /api/v1/users/me/profile/update
Authorization: Bearer <access_token>
Request:
{
"username": "hy",
"avatar": "https://cdn.example/avatar.png",
"birth": "2000-01-02"
}
该接口只修改用户名、头像和生日。birth 非空时必须是 yyyy-mm-dd;国家不能混在这个接口中修改。
Change Country
POST /api/v1/users/me/country/change
Authorization: Bearer <access_token>
Request:
{
"country": "US"
}
国家修改由 user-service 单独写 user_country_change_logs。同一用户按滚动 30 天冷却窗口只能成功修改一次;提交相同国家不写新日志,也不消耗新的冷却窗口。
Password Login
POST /api/v1/auth/password/login
Request:
{
"display_user_id": "100001",
"password": "plain_password",
"device_id": "device_xxx"
}
Response data:
{
"user_id": 10001,
"display_user_id": "100001",
"default_display_user_id": "100001",
"display_user_id_kind": "default",
"display_user_id_expires_at_ms": 0,
"session_id": "sess_xxx",
"access_token": "jwt_xxx",
"refresh_token": "refresh_xxx",
"expires_in_sec": 1800,
"token_type": "Bearer"
}
短号不存在、用户未设置密码和密码错误都返回 AUTH_FAILED。这三个分支不能给客户端更细错误,避免登录入口变成用户枚举接口。
密码登录只接受当前有效 display_user_id。如果用户申请了临时靓号,默认短号在靓号有效期内不能用于密码登录;靓号过期后,默认短号恢复登录能力。
Refresh Token
POST /api/v1/auth/token/refresh
Request:
{
"refresh_token": "refresh_xxx",
"device_id": "device_xxx"
}
Response data:
{
"user_id": 10001,
"display_user_id": "100001",
"default_display_user_id": "100001",
"display_user_id_kind": "default",
"display_user_id_expires_at_ms": 0,
"session_id": "sess_xxx",
"access_token": "jwt_xxx",
"refresh_token": "refresh_new_xxx",
"expires_in_sec": 1800,
"token_type": "Bearer"
}
refresh token 必须轮换。旧 refresh token 使用成功后立即失效。
Logout
POST /api/v1/auth/logout
Request:
{
"refresh_token": "refresh_xxx",
"session_id": "sess_xxx"
}
Response data:
{
"revoked": true
}
Internal gRPC Contract
api/proto/user/v1/auth.proto 承载 auth RPC:
service AuthService {
rpc LoginPassword(LoginPasswordRequest) returns (AuthResponse);
rpc LoginThirdParty(LoginThirdPartyRequest) returns (AuthResponse);
rpc SetPassword(SetPasswordRequest) returns (SetPasswordResponse);
rpc RefreshToken(RefreshTokenRequest) returns (RefreshTokenResponse);
rpc Logout(LogoutRequest) returns (LogoutResponse);
}
关键消息:
message LoginPasswordRequest {
RequestMeta meta = 1;
string display_user_id = 2;
string password = 3;
}
message LoginThirdPartyRequest {
RequestMeta meta = 1;
string provider = 2;
string credential = 3;
string username = 4;
string gender = 5;
string country = 6;
string invite_code = 7;
string ip = 8 [deprecated = true];
string device_id = 9;
string device = 10;
string avatar = 11;
string birth = 12;
string app_version = 13;
string source = 14;
string platform = 15;
string language = 16;
string os_version = 17;
string build_number = 18;
string timezone = 19;
string install_channel = 20;
string campaign = 21;
}
message SetPasswordRequest {
RequestMeta meta = 1;
int64 user_id = 2;
string password = 3;
}
message AuthToken {
int64 user_id = 1;
string session_id = 2;
string access_token = 3;
string refresh_token = 4;
int64 expires_in_sec = 5;
string token_type = 6;
string display_user_id = 7;
string default_display_user_id = 8;
string display_user_id_kind = 9;
int64 display_user_id_expires_at_ms = 10;
}
SetPasswordRequest.user_id 必须来自 gateway 已校验 access token 后的上下文,不能来自客户端 JSON body。
Data Model
users
用户主记录保存用户全局身份、当前短号快照、注册资料和注册来源快照。三方 credential、密码明文、refresh token 原文都不能进入这张表。
| Column | Type | Rule |
|---|---|---|
user_id |
bigint | primary key |
default_display_user_id |
varchar | user's permanent fallback short id |
current_display_user_id |
varchar | current effective short id; pretty id can cover default id |
current_display_user_id_kind |
varchar | default or pretty |
current_display_user_id_expires_at_ms |
bigint | null for default id |
username |
varchar | registration display name snapshot |
gender |
varchar | client submitted gender value |
country |
varchar | ISO 3166-1 alpha-2 country or region code |
invite_code |
varchar | optional invite code |
register_ip |
varchar | gateway observed request ip snapshot |
register_user_agent |
varchar | gateway observed User-Agent snapshot |
country_by_ip |
varchar | server/edge inferred country from entry IP |
register_device_id |
varchar | registration device id and first session device binding |
register_device |
varchar | client device model or description |
os_version |
varchar | client OS version |
avatar |
varchar | absolute http/https avatar URL |
birth_date |
date | yyyy-mm-dd |
app_version |
varchar | registration client version |
build_number |
varchar | registration client build number |
source |
varchar | registration source channel |
install_channel |
varchar | install channel |
campaign |
varchar | attribution campaign |
platform |
varchar | android or ios |
language |
varchar | BCP 47 language tag subset |
timezone |
varchar | client IANA timezone |
status |
varchar | active, disabled, banned |
created_at_ms |
bigint | creation time |
updated_at_ms |
bigint | update time |
password_accounts
密码身份表。它表达“该用户已经设置过密码”,不表达独立账号体系。
| Column | Type | Rule |
|---|---|---|
user_id |
bigint | primary key |
password_hash |
varchar | never store plain password |
hash_alg |
varchar | e.g. bcrypt |
created_at_ms |
bigint | creation time |
updated_at_ms |
bigint | update time |
密码登录时先用 users.current_display_user_id 解析当前用户,再按 user_id 读取 password_accounts。这样用户修改默认短号或申请临时靓号后,密码登录自动跟随当前有效短号,不需要同步更新密码表。
如果 current_display_user_id_kind=pretty 且 current_display_user_id_expires_at_ms <= now,登录前必须先触发 user-service 的靓号懒过期流程,把当前短号恢复为默认短号后再继续解析。
third_party_identities
三方身份绑定表。
| Column | Type | Rule |
|---|---|---|
id |
bigint | primary key |
user_id |
bigint | owner user |
provider |
varchar | provider name |
provider_subject |
varchar | provider user id, required |
provider_union_id |
varchar | optional |
created_at_ms |
bigint | creation time |
updated_at_ms |
bigint | update time |
唯一约束:
unique(provider, provider_subject)
如果 provider 支持 union id,也不能只依赖 union id。provider_subject 是登录身份的稳定主键。
user_country_change_logs
用户国家修改日志表,作为冷却期判断和审计来源。
| Column | Type | Rule |
|---|---|---|
id |
bigint | primary key |
user_id |
bigint | changed user |
old_country |
varchar | nullable when previous country is empty |
new_country |
varchar | required |
request_id |
varchar | gateway trace id |
changed_at_ms |
bigint | change time |
冷却期检查必须和 users 行更新在同一事务中完成,不能只依赖 gateway 侧判断。
auth_sessions
refresh token 和会话状态表。
| Column | Type | Rule |
|---|---|---|
session_id |
varchar | primary key |
user_id |
bigint | session owner |
refresh_token_hash |
varchar | unique |
device_id |
varchar | client device |
expires_at_ms |
bigint | refresh expiry |
revoked_at_ms |
bigint | null means active |
created_at_ms |
bigint | creation time |
updated_at_ms |
bigint | update time |
refresh token 原文只返回给客户端一次,服务端只保存 hash。
login_audit
登录审计表,不参与主链路判断。
| Column | Type | Rule |
|---|---|---|
id |
bigint | primary key |
request_id |
varchar | trace id |
user_id |
bigint | nullable on failed login |
login_type |
varchar | password, third_party, refresh, set_password |
provider |
varchar | nullable |
result |
varchar | success, failed |
failure_code |
varchar | nullable |
client_ip |
varchar | gateway observed ip |
user_agent |
varchar | gateway observed ua |
created_at_ms |
bigint | event time |
审计失败不能影响登录主链路,但不能记录明文密码、三方 credential 或 refresh token 原文。
Core Flows
Third Party Login Or Register
sequenceDiagram
participant C as Client
participant G as gateway-service
participant U as user-service
participant P as Provider
C->>G: POST /api/v1/auth/third-party/login
G->>U: LoginThirdParty(provider, credential, registration_profile)
U->>P: verify credential
P-->>U: provider_subject
U->>U: find identity
alt identity exists
U->>U: check user status
U->>U: create session
else identity missing
U->>U: create user/profile + default_display_user_id + third_party identity + session
end
U-->>G: AuthToken + is_new_user
G-->>C: envelope(data=AuthToken)
三方 credential 校验必须在 user-service 内完成。gateway 不保存 provider app secret。
生产路径中,U->>P: verify credential 必须是真实 provider 校验或服务端换票。静态 verifier 把 credential 当 subject 的实现只允许在本地测试替身中使用,不能用于线上注册入口。
Set Password
sequenceDiagram
participant C as Client
participant G as gateway-service
participant U as user-service
C->>G: POST /api/v1/auth/password/set + access token
G->>G: verify access token
G->>U: SetPassword(user_id from token, password)
U->>U: check user status
U->>U: hash password
U->>U: insert password_accounts by user_id
U-->>G: password_set=true
G-->>C: envelope(data={password_set:true})
password_accounts.user_id 是主键。重复设置说明用户已经有密码身份,返回 PASSWORD_ALREADY_SET。
Password Login
sequenceDiagram
participant C as Client
participant G as gateway-service
participant U as user-service
C->>G: POST /api/v1/auth/password/login
G->>U: LoginPassword(display_user_id, password)
U->>U: expire pretty display_user_id if needed
U->>U: resolve current display_user_id
U->>U: lookup password by user_id
U->>U: verify password hash
U->>U: check user status
U->>U: create session
U-->>G: AuthToken
G-->>C: envelope(data=AuthToken)
display_user_id 是登录名,不是另一个账号体系。默认短号变更后旧短号不能继续登录;临时靓号有效期内默认短号不能登录;临时靓号过期后旧靓号不能继续登录。
Refresh Token
sequenceDiagram
participant C as Client
participant G as gateway-service
participant U as user-service
C->>G: POST /api/v1/auth/token/refresh
G->>U: RefreshToken
U->>U: hash refresh token
U->>U: find active session
U->>U: revoke old refresh token
U->>U: create replacement refresh token
U->>U: expire pretty display_user_id if needed
U->>U: reload user current display_user_id
U-->>G: new AuthToken
G-->>C: envelope(data=AuthToken)
Refresh 返回当前 display_user_id,避免客户端持有旧短号。临时靓号已经过期时,Refresh 必须返回默认短号。
Token Strategy
v1 使用:
access_token: JWT,短有效期,默认 30 分钟。refresh_token: opaque random token,默认 30 天,只保存 hash。session_id: 服务端会话 ID,用于 logout、审计和多端管理。
JWT claims:
{
"sub": "10001",
"user_id": 10001,
"display_user_id": "100001",
"default_display_user_id": "100001",
"display_user_id_kind": "default",
"display_user_id_expires_at_ms": 0,
"sid": "sess_xxx",
"iat": 1777000000,
"exp": 1777001800,
"typ": "access"
}
JWT 内的短号字段只是签发时的展示快照,不是身份判断依据。业务鉴权只能用 user_id。如果临时靓号在 access token 有效期内过期,客户端需要通过 Refresh 或用户身份接口拿到恢复后的默认短号。
首版继续沿用本地 HS256 配置。中长期建议迁移到非对称签名:
user-service持私钥签发。gateway-service持公钥校验。- 公钥通过配置或 JWKS 缓存分发。
腾讯云 IM 不直接校验业务 JWT。客户端先用 access token 调 /api/v1/im/usersig,再用返回的 UserSig 登录腾讯云 IM。
Error Codes
gateway HTTP envelope 的 code 不使用数字。
| Code | HTTP | Meaning |
|---|---|---|
OK |
200 | success |
INVALID_JSON |
400 | request body is invalid JSON |
INVALID_ARGUMENT |
400 | field format or required field invalid |
RATE_LIMITED |
429 | public auth rate limit exceeded |
AUTH_FAILED |
401 | display_user_id missing, password wrong, no password set, or provider rejected |
UNAUTHORIZED |
401 | missing or invalid access token |
PASSWORD_ALREADY_SET |
409 | user already has password identity |
COUNTRY_CHANGE_COOLDOWN |
409 | country change is within rolling 30-day cooldown |
USER_DISABLED |
403 | user is disabled or banned |
SESSION_EXPIRED |
401 | refresh token expired |
SESSION_REVOKED |
401 | refresh token revoked |
UPSTREAM_ERROR |
502 | user-service unavailable or returned unexpected error |
INTERNAL_ERROR |
500 | gateway internal failure |
密码登录时,短号不存在、未设置密码和密码错误都映射为 AUTH_FAILED。
Public Auth Rate Limit
gateway 对三方注册登录、密码登录、refresh 和 logout 做 Redis 固定窗口频控。当前频控发生在 JSON 解码后、调用 user-service 前;命中频控时返回 HTTP 429 和 RATE_LIMITED,请求不会进入 user-service。
| Dimension | Applies To | Purpose |
|---|---|---|
ip |
third-party, password, refresh, logout | 限制单入口 IP 对 public auth 的总请求量 |
device_id |
third-party, password, refresh | 限制单设备安装批量注册或刷新 |
provider + ip |
third-party | 限制单 IP 对同一三方 provider 批量建号 |
display_user_id + ip |
password | 限制单 IP 对同一短号喷射尝试 |
display_user_id |
password | 限制同一短号被跨 IP 喷射尝试 |
session_id + ip |
logout | 限制 logout 重放 |
Redis backend 使用 Lua 脚本一次性检查并递增同一请求命中的所有维度,避免部分 key 已递增但后续维度超限的非原子状态。公网可控的 provider、device_id、display_user_id、session_id 和入口 IP 在 Redis key 中只保留固定长度摘要,避免超长输入污染 key 空间。auth_rate_limit.enabled=true 时 gateway 启动必须连通 Redis;运行期 Redis 调用失败会 fail-closed 返回 HTTP 502 和 UPSTREAM_ERROR,避免入口在依赖故障时无保护放量。内存 backend 只用于单元测试或显式关闭 Redis 频控后的本地构造。
Security Rules
- 明文密码只允许存在于 gateway 入参对象和 user-service hash 前的内存短路径中,不能写日志。
password_hash必须使用强 hash,不能使用 SHA、MD5 或可逆加密。- refresh token 原文不能入库,只能入库 hash。
- 三方 provider secret 只属于
user-service配置或服务端密钥系统。 - 生产三方 verifier 必须校验 provider credential 的真实性和用途,不能接受客户端自报 subject,不能把静态 verifier 配到真实 provider。
- 登录失败日志不能包含明文密码、三方 credential、refresh token 原文。
- gateway 统一过滤内部错误,客户端只拿稳定错误码和
request_id。 - access token 校验失败不访问
user-service,避免每个业务请求都打用户服务。 - 设置密码必须从 access token 取
user_id,不接受客户端 body 指定用户。 - access token 中的
display_user_id不能作为权限、账务、房间或 IM 身份依据。
Compatibility Rules
当前项目仍在架构阶段,没有外部调用方,本次契约直接删除了旧账号注册接口。后续一旦进入联调或灰度,必须恢复兼容演进纪律:
- HTTP JSON 字段只能兼容扩展,不能重命名或删除已发布字段。
- protobuf 字段只能追加,不能复用字段编号。
- 新增 provider 必须通过配置 allowlist 控制,不能让客户端任意传 provider 后触发未知逻辑。
- 新增 provider 必须同时新增专用 verifier、配置项和测试;不能只把 provider 名加入静态 allowlist。
- 三方登录创建用户必须使用事务,保证
users注册资料、user_display_user_ids和third_party_identities不出现半绑定。 - 设置密码必须使用
user_id主键幂等判断,不能引入独立账号唯一键。 - 登录响应可以追加默认短号、当前短号来源和靓号过期时间;已发布后不能改变
display_user_id表示当前有效短号的语义。
Test Matrix
必须覆盖:
- 三方身份首次登录创建用户、生成
default_display_user_id,并返回当前display_user_id和is_new_user=true。 - 三方 verifier 拒绝未知 provider、伪造签名、错误 audience、过期 credential、nonce 不匹配和 provider 配置缺失。
- 真实 provider 配置不能回退到
StaticThirdPartyVerifier;静态 verifier 只在测试或 local/mock 配置中出现。 - 三方注册资料写入
users;任意写入users的注册字段超过 schema 长度,或birth/platform/country/language/timezone/avatar格式非法时返回INVALID_ARGUMENT。 - 用户资料接口只修改
username/avatar/birth,非法生日返回INVALID_ARGUMENT。 - 用户国家接口写
user_country_change_logs,冷却期内第二次修改返回COUNTRY_CHANGE_COOLDOWN。 - 三方身份再次登录返回同一个
user_id和is_new_user=false。 - 三方新用户未设置密码前,短号密码登录返回
AUTH_FAILED。 - 已登录用户设置密码成功。
- 重复设置密码返回
PASSWORD_ALREADY_SET。 - 设置密码后可使用当前
display_user_id + password登录。 - 短号不存在和密码错误都返回
AUTH_FAILED。 - 用户有 active 靓号时,密码登录必须使用靓号,默认短号返回
AUTH_FAILED。 - 靓号过期后,密码登录必须恢复使用默认短号,旧靓号返回
AUTH_FAILED。 - Refresh 在靓号过期后返回默认短号和
display_user_id_kind=default。 - 被禁用用户密码登录返回
USER_DISABLED。 - refresh token 成功轮换,旧 token 再次使用失败。
- logout 后 refresh token 失败。
- gateway auth API 响应始终使用
{code,message,request_id,data}。 - gateway 在
SetPassword中只从 access token 传递user_id。 - public auth 频控命中时返回
RATE_LIMITED,Redis 频控 backend 异常时 fail-closed,不进入 user-service。
Open Decisions
这些点需要在实现前定死,不能靠代码猜:
- 首批三方 provider 名单。
- 每个 provider 的 credential 类型、client_id/app_id、issuer/audience、nonce 策略和公钥/JWKS 缓存策略。
- 密码复杂度策略。
- access token 有效期和 refresh token 有效期。
- HS256 继续使用多久,以及迁移非对称签名的时间点。