hyapp-server/docs/auth-login-register-technical-design.md
2026-04-29 23:07:50 +08:00

28 KiB
Raw Blame History

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-servicegateway 只做外部 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_idsthird_party_identities 和首个 auth_sessions 会在同一事务中创建。birth 必须是 yyyy-mm-ddplatform 当前只接受 androidiosgateway 只接收 app_version,不会读取误拼字段。客户端不能提交注册 IP、UA 或 IP 国家,register_ipregister_user_agent 来自 gateway 请求上下文,country_by_ip 来自服务端/可信边缘层根据入口 IP 形成的国家快照。provider_subject 只允许由 user-service 校验 provider credential 后得到,不能信任客户端直传,也不能把客户端传入的 credential 原样当成 subject。

注册字段必须在 user-service 写库前完成校验,失败统一返回 INVALID_ARGUMENT,不能让 MySQL VARCHARDATE 约束错误泄漏成 gateway UPSTREAM_ERROR

Field Max Format
username 64 trimmed display name
gender 32 client enum/string snapshot
country 3 ISO 3166-1 alpha-2/alpha-3 canonical code, 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_iddisplay_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 device_id = 8;
  string device = 9;
  string avatar = 10;
  string birth = 11;
  string app_version = 12;
  string source = 13;
  string platform = 14;
  string language = 15;
  string os_version = 16;
  string build_number = 17;
  string timezone = 18;
  string install_channel = 19;
  string campaign = 20;
}

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 default 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/alpha-3 canonical country 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=prettycurrent_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 the 30-day cooldown window
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 身份依据。

Development Contract

当前项目处于开发阶段没有线上旧客户端或旧服务版本需要保留HTTP、protobuf 和数据库契约按当前代码直接收敛:

  • HTTP JSON 只接受当前文档字段,旧拼写和废弃字段不再读取。
  • protobuf 按当前字段编号生成,删除的旧字段不保留解析分支。
  • 新增 provider 必须通过配置 allowlist 控制,不能让客户端任意传 provider 后触发未知逻辑。
  • 新增 provider 必须同时新增专用 verifier、配置项和测试不能只把 provider 名加入静态 allowlist。
  • 三方登录创建用户必须使用事务,保证 users 注册资料、user_display_user_idsthird_party_identities 不出现半绑定。
  • 设置密码必须使用 user_id 主键幂等判断,不能引入独立账号唯一键。
  • 登录响应中的 display_user_id 表示当前有效短号,服务端权限、账务、房间和 IM 身份一律使用长 user_id

Test Matrix

必须覆盖:

  • 三方身份首次登录创建用户、生成 default_display_user_id,并返回当前 display_user_idis_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_idis_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_LIMITEDRedis 频控 backend 异常时 fail-closed不进入 user-service。

Open Decisions

这些点需要在实现前定死,不能靠代码猜:

  • 首批三方 provider 名单。
  • 每个 provider 的 credential 类型、client_id/app_id、issuer/audience、nonce 策略和公钥/JWKS 缓存策略。
  • 密码复杂度策略。
  • access token 有效期和 refresh token 有效期。
  • HS256 继续使用多久,以及迁移非对称签名的时间点。