24 KiB
Login Region Risk Architecture
本文档定义登录地区、语言和时区限制的当前推荐方案。方案按当前产品取舍设计:不引入 pending_risk 会话,不让各玩法服务分别判断风险状态;用户在未知 IP 首次登录时可以进入 App,后台异步 IP 风控命中不支持国家后撤销 session 并踢下线,后续同一 IP 再登录会在入口直接拒绝。
Scope
| Capability | Owner | Rule |
|---|---|---|
| 真实入口 IP 提取 | gateway-service |
从可信上游 header 或 RemoteAddr 提取,不信任前端 JSON 里的 IP |
| 语言和时区快速判断 | gateway-service |
只做入口级弱信号拦截,命中明显不支持配置时直接拒绝登录 |
| IP 风控缓存读取 | gateway-service |
登录前读取 Redis IP 决策缓存,blocked 直接拒绝,allowed/unknown/miss 放行并触发复核 |
| 登录、session 和 token | user-service |
仍是 session owner;登录成功后创建 auth_sessions 并签发 token |
| 异步 IP 风控任务 | cron-service 调度,user-service 执行 |
按优先级顺序查询 IP Geo 源;当前一个不可用时才降级到下一个,写持久审计和 Redis 缓存 |
| session 撤销 | user-service |
命中不支持国家后撤销 session,写撤销原因和审计 |
| 登录态接口统一拦截 | gateway-service |
JWT 校验后统一检查 session 是否已撤销,避免玩法服务散落判断 |
| 房间/IM 踢下线 | gateway-service 编排,room-service/腾讯云 IM 执行 |
只能走 Room Cell 命令或腾讯云 IM 服务端接口,不能直接改 room Redis |
| 后台登录日志 | hyapp-admin-server |
只在用户管理一级菜单下新增二级菜单“登录日志”,不单独做风控策略后台 |
Non-Goals
| Non-goal | Reason |
|---|---|
不做 pending_risk session |
当前阶段要降低接入成本,避免设计白名单接口和受限权限集 |
| 不在玩法服务判断地区风险 | 房间、礼物、钱包、VIP、任务都不应该各自写 risk_status 判断,容易漏 |
| 不把 IP Geo 查询接口放进同步登录路径 | 外部接口慢、超时或不稳定会拖慢登录 |
| 不信任前端传过来的 IP | 客户端字段可伪造,只能作为调试或兼容字段记录 |
| 不靠“清客户端 token”完成踢下线 | JWT 已签发后客户端仍可能继续使用,必须服务端撤销 session 并由 gateway 拦截 |
| 不把 Redis 当唯一事实来源 | Redis 只做快查缓存;审计、任务、明文 IP 和撤销原因必须能在 MySQL 追溯 |
| 不做独立风控后台 | 当前后台只提供登录日志查看,不提供策略编辑、provider 管理或人工改判 |
Core Decision
采用“入口快拦 + 登录后异步复核 + session 统一撤销”的模型:
flowchart LR
C["Client"] --> G["gateway-service"]
G --> LangTZ["language/timezone fast guard"]
G --> Cache["Redis IP decision cache"]
Cache -->|blocked| Deny["reject login"]
Cache -->|allowed/miss/unknown| U["user-service Auth"]
U --> Session[("auth_sessions")]
U --> Resp["token response"]
U --> Job[("login_ip_risk_jobs / outbox")]
Job --> RiskBatch["cron triggered IP risk batch"]
RiskBatch --> IP1["IP Geo A"]
IP1 -. "unavailable/timeout" .-> IP2["IP Geo B"]
IP2 -. "unavailable/timeout" .-> IP3["IP Geo C"]
IP3 -. "unavailable/timeout" .-> IP4["IP Geo D"]
RiskBatch --> Decision[("risk decision + Redis cache")]
RiskBatch -->|blocked| Revoke["revoke session"]
Revoke --> Kick["IM/Room kick"]
这个方案接受一个明确边界:未知 IP 第一次可能短暂进入 App。通过 Redis 缓存和 session 撤销,同一个 IP 后续不能反复完整登录。
Login Flow
Password Login
sequenceDiagram
participant App
participant Gateway
participant User as user-service
participant Redis
participant Risk as cron-service/user-service batch
App->>Gateway: POST /api/v1/auth/account/login
Gateway->>Gateway: extract trusted client_ip
Gateway->>Gateway: check language/timezone config
Gateway->>Redis: GET ip decision
alt ip blocked
Gateway-->>App: AUTH_LOGIN_BLOCKED
else allowed/miss/unknown
Gateway->>User: LoginPassword(meta.client_ip, language, timezone)
User->>User: verify password and create session
User->>User: enqueue login_ip_risk_job
User-->>Gateway: token(session_id)
Gateway-->>App: token
Risk->>Risk: async resolve IP by provider fallback chain
alt unsupported country
Risk->>User: RevokeSession(session_id, reason)
Risk->>Redis: SET ip decision = blocked
Risk->>Gateway: trigger kick orchestration
else allowed country
Risk->>Redis: SET ip decision = allowed
end
end
Third Party Login
三方登录和密码登录使用同一套入口风控,只是 user-service 在创建或复用用户 session 后写风险任务。三方首次注册时,客户端提交的 country 仍然只是用户选择国家;IP 国家由服务端 IP Geo 结果决定,不能用 country 替代风控国家。
Gateway Fast Guard
gateway 只做三件事:
- 提取可信入口 IP。
- 对
language和timezone做简单配置判断。 - 查询 Redis IP 决策缓存,命中
blocked时拒绝登录。
Trusted IP
可信 IP 来源顺序:
trusted edge header -> X-Forwarded-For first public IP -> RemoteAddr
前端提交的 ip 字段不参与判断。旧客户端如果还传 IP,只能写入审计扩展字段,不能作为风控依据。
Language And Timezone
语言和时区是弱信号,因为客户端可以修改。它们只能做入口快速拦截:
login_risk:
blocked_languages:
- zh-CN
blocked_language_primary_tags:
- zh
blocked_timezones:
- Asia/Shanghai
blocked_timezone_prefixes:
- Asia/
规则:
language按 BCP 47 轻量格式校验,例如en-US、zh-CN。timezone必须是 IANA 名称,例如America/Los_Angeles。- 命中阻断配置时,gateway 返回
AUTH_LOGIN_BLOCKED。 - 未传、格式异常或不可信时,不直接推断国家;只记录审计。
IP Decision Cache
Redis key 建议不要保存明文 IP:
login_risk:ip:{app_code}:{sha256(ip)}
Value:
{
"decision": "blocked",
"country": "CN",
"source": "geo_provider_chain",
"checked_at_ms": 1710000000000,
"expires_at_ms": 1710604800000
}
决策值:
| Decision | Gateway Action |
|---|---|
blocked |
登录前直接拒绝 |
allowed |
放行登录,可低频复核 |
unknown |
放行登录,必须异步复核 |
| miss | 放行登录,必须异步复核 |
TTL 建议:
| Decision | TTL |
|---|---|
blocked |
7 到 30 天 |
allowed |
1 到 7 天 |
unknown |
10 到 60 分钟 |
可用性策略:Redis 不可用时放行登录,只记录错误和告警;不能因为 Redis 故障阻断登录。
Async IP Risk Check
异步 IP 风控 batch 按优先级顺序调用 IP Geo 源,不阻塞登录响应。一次任务只使用一个可用 provider 的结果;只有当前 provider 超时、不可用或返回不可解析结果时,才请求下一个 provider。
Job
风险任务需要包含:
| Field | Rule |
|---|---|
job_id |
幂等任务 ID |
app_code |
多 App 隔离 |
session_id |
命中后撤销的 session |
user_id |
审计和踢下线目标 |
client_ip |
明文入口 IP,后台登录日志直接展示 |
client_ip_hash |
缓存 key、索引和日志关联字段,避免普通日志输出明文 IP |
channel |
登录渠道:account/google/facebook/twitter/tiktok |
platform |
客户端平台:android/ios |
language |
登录时客户端语言 |
timezone |
登录时客户端时区 |
request_id |
串联登录日志 |
status |
pending/running/completed/failed/skipped |
created_at_ms |
任务创建时间 |
updated_at_ms |
任务更新时间 |
任务可以落 MySQL 表 login_ip_risk_jobs,也可以由 user outbox 派生。首版如果没有统一 MQ,优先用 MySQL 任务表 + cron-service 调度的 batch claim,避免 Redis list 丢失导致永远不复核。MySQL 写任务失败时不影响登录结果,但必须记录错误和告警。
Provider Fallback Chain
batch 行为:
- 先查 Redis 是否已有新鲜决策;已有
blocked/allowed可以跳过外部查询。 - miss 时按配置顺序调用第一个 provider。
- 如果 provider 在 timeout 内返回有效国家码,立即停止查询后续 provider。
- 如果 provider 超时、不可用、限流、返回空国家或返回不可解析结果,记录失败原因并降级到下一个 provider。
- 所有 provider 都失败时,输出
unknown,写短 TTL 缓存并等待后续任务重试;如果缓存也不可用,只记录日志和告警。 - 有效国家码命中不支持国家时输出
blocked;有效国家码未命中不支持国家时输出allowed。 - 写 MySQL 决策记录和 Redis 缓存。
- 如果命中不支持国家,撤销当前 session 并触发踢下线。
该模型没有“结果冲突”问题,因为同一个任务不会同时采信多个 provider。provider 顺序就是信任优先级:
| Case | Decision |
|---|---|
| 第一个可用 provider 返回不支持国家 | blocked |
| 第一个可用 provider 返回支持国家 | allowed |
| provider 超时、不可用或返回无效结果 | 请求下一个 provider |
| 所有 provider 都失败 | unknown,短 TTL,后续重试 |
不支持国家列表必须来自配置,不应硬编码在风控逻辑里;当前后台不提供策略编辑能力。
Session Revocation
命中不支持国家时,撤销 session 是核心动作。
auth_sessions.revoked_at_ms = now
auth_sessions.revoked_reason = GEO_POLICY_BLOCKED
当前表只有 revoked_at_ms,实现时建议补充:
| Field | Reason |
|---|---|
revoked_reason |
区分用户 logout、refresh 轮换、后台封禁、IP 风控撤销 |
revoked_request_id |
串联风险任务和审计 |
revoked_by |
risk_worker、user_logout、admin |
撤销成功后写 Redis denylist:
auth:revoked_session:{app_code}:{session_id} = GEO_POLICY_BLOCKED
TTL = access token remaining ttl + safety window
Gateway Session Guard
如果 access token 是 JWT,只校验签名不够。用户已经拿到 access token 后,即使 auth_sessions 被撤销,JWT 在过期前仍然能访问接口。
因此 gateway 必须增加统一 session guard:
Authorization JWT valid
+ sid exists
+ auth:revoked_session:{app_code}:{sid} not exists
首版可以先只查 Redis denylist,不对每个请求同步查 MySQL:
- 登录成功时不需要写 active cache。
- 风控撤销 session 时写 denylist。
- logout、refresh 轮换、后台封禁同样写 denylist。
- access token TTL 到期后 denylist 自然失效。
后续如果需要更强一致性,再增加 ValidateSession RPC 或 auth:active_session cache。
所有 App 登录态接口都走 gateway middleware,因此玩法服务不需要感知风险状态:
flowchart TD
Req["Business Request"] --> JWT["Verify JWT"]
JWT --> SID["Read sid claim"]
SID --> Deny["Check Redis revoked_session"]
Deny -->|revoked| Fail["SESSION_REVOKED"]
Deny -->|active| Handler["Business handler"]
Kick Offline
踢下线分三层,按能做到的程度逐步接入:
| Layer | Action | Required |
|---|---|---|
| API | gateway session guard 返回 SESSION_REVOKED |
必须 |
| IM | 调腾讯云 IM 服务端接口踢下线或发系统消息通知客户端清 token | 建议 |
| Room | 如果用户在房间,通过 room-service Room Cell 命令踢出或触发离房 | 建议 |
不能直接删除 room-service Redis presence 或麦位缓存。房间状态 owner 是 Room Cell,风险踢人必须走 room-service 的命令入口;如果首版没有踢人命令,至少依靠 session guard 阻止后续 HTTP 操作,并让房间 heartbeat/断线补偿释放状态。
Error Codes
建议新增稳定业务码:
| Code | HTTP | gRPC | Meaning |
|---|---|---|---|
AUTH_LOGIN_BLOCKED |
403 | PermissionDenied |
登录入口命中地区、语言、时区或 IP 风控策略 |
SESSION_REVOKED |
401 | Unauthenticated |
session 已撤销,客户端必须清登录态 |
客户端行为:
AUTH_LOGIN_BLOCKED:停留登录页,展示地区不支持类提示。SESSION_REVOKED:清本地 token,退出房间/IM,回登录页。- 普通 5xx 或网络失败不能清 token。
Audit And Observability
必须记录:
| Event | Fields |
|---|---|
login_fast_guard_blocked |
request_id/app_code/client_ip_hash/language/timezone/block_reason |
login_ip_risk_job_created |
request_id/app_code/session_id/user_id/client_ip/client_ip_hash/channel/platform |
login_ip_geo_provider_attempt |
job_id/provider/order/country/status/duration_ms/error_code |
login_ip_risk_decision |
job_id/session_id/user_id/decision/country/confidence |
login_session_revoked_by_risk |
job_id/session_id/user_id/reason/country |
kick_offline_result |
session_id/user_id/im_result/room_result |
敏感规则:
- 不记录明文 access token、refresh token、三方 credential。
- 明文 IP 可以落 MySQL 业务表,供后台登录日志查看;普通服务日志仍只记录
client_ip_hash,避免日志系统大范围暴露明文 IP。 - provider 返回原始报文不进日志,只记录本次尝试的顺序、标准化国家码、耗时和错误码。
Data Model
login_ip_risk_jobs
CREATE TABLE login_ip_risk_jobs (
app_code VARCHAR(32) NOT NULL,
job_id VARCHAR(96) NOT NULL,
session_id VARCHAR(96) NOT NULL,
user_id BIGINT NOT NULL,
client_ip VARCHAR(64) NOT NULL,
client_ip_hash VARCHAR(128) NOT NULL,
channel VARCHAR(32) NOT NULL,
platform VARCHAR(16) NOT NULL,
language VARCHAR(32) NOT NULL DEFAULT '',
timezone VARCHAR(64) NOT NULL DEFAULT '',
request_id VARCHAR(96) NOT NULL DEFAULT '',
status VARCHAR(32) NOT NULL,
decision VARCHAR(32) NOT NULL DEFAULT '',
country_code VARCHAR(3) NOT NULL DEFAULT '',
failure_reason VARCHAR(128) NOT NULL DEFAULT '',
locked_by VARCHAR(128) NULL,
locked_until_ms BIGINT NULL,
attempt_count INT NOT NULL DEFAULT 0,
created_at_ms BIGINT NOT NULL,
updated_at_ms BIGINT NOT NULL,
PRIMARY KEY (app_code, job_id),
KEY idx_login_ip_risk_jobs_status (app_code, status, updated_at_ms),
KEY idx_login_ip_risk_jobs_session (app_code, session_id),
KEY idx_login_ip_risk_jobs_user (app_code, user_id, created_at_ms),
KEY idx_login_ip_risk_jobs_ip (app_code, client_ip_hash, updated_at_ms)
);
login_ip_risk_decisions
CREATE TABLE login_ip_risk_decisions (
app_code VARCHAR(32) NOT NULL,
decision_id VARCHAR(96) NOT NULL,
client_ip VARCHAR(64) NOT NULL,
client_ip_hash VARCHAR(128) NOT NULL,
decision VARCHAR(32) NOT NULL,
country_code VARCHAR(3) NOT NULL DEFAULT '',
confidence INT NOT NULL DEFAULT 0,
provider_attempts_json JSON NOT NULL,
decided_at_ms BIGINT NOT NULL,
expires_at_ms BIGINT NOT NULL,
created_at_ms BIGINT NOT NULL,
PRIMARY KEY (app_code, decision_id),
KEY idx_login_ip_risk_decisions_ip (app_code, client_ip_hash, decided_at_ms),
KEY idx_login_ip_risk_decisions_country (app_code, country_code, decided_at_ms),
KEY idx_login_ip_risk_decisions_expires (app_code, expires_at_ms)
);
login_audit Additions
现有 login_audit 已记录登录类型、provider、结果和失败原因。为了支撑后台登录日志,需要把登录入口展示字段补齐:
ALTER TABLE login_audit
ADD COLUMN channel VARCHAR(32) NOT NULL DEFAULT '',
ADD COLUMN platform VARCHAR(16) NOT NULL DEFAULT '',
ADD COLUMN client_ip VARCHAR(64) NOT NULL DEFAULT '',
ADD COLUMN ip_country_code VARCHAR(3) NOT NULL DEFAULT '',
ADD COLUMN blocked TINYINT(1) NOT NULL DEFAULT 0,
ADD COLUMN block_reason VARCHAR(64) NOT NULL DEFAULT '',
ADD KEY idx_login_audit_user_created (app_code, user_id, created_at_ms),
ADD KEY idx_login_audit_blocked (app_code, blocked, created_at_ms),
ADD KEY idx_login_audit_ip_country (app_code, ip_country_code, created_at_ms);
字段口径:
| Field | Rule |
|---|---|
channel |
account/google/facebook/twitter/tiktok,三方 provider 必须归一成这几个展示值 |
platform |
android/ios,来自客户端请求体或可信 header |
client_ip |
明文入口 IP,来自 gateway 提取结果 |
ip_country_code |
IP Geo 解析出的国家码,未解析时为空 |
blocked |
当前登录是否被语言、时区、IP cache 或异步 IP 风控阻断 |
block_reason |
language/timezone/ip_cache/ip_async 等稳定原因 |
auth_sessions Additions
ALTER TABLE auth_sessions
ADD COLUMN revoked_reason VARCHAR(64) NOT NULL DEFAULT '',
ADD COLUMN revoked_request_id VARCHAR(96) NOT NULL DEFAULT '',
ADD COLUMN revoked_by VARCHAR(64) NOT NULL DEFAULT '';
Admin Login Logs
后台管理当前只做查询和展示,不做风控配置编辑。入口位置:
用户管理
└── 登录日志
List Page
列表按登录事件展示,默认按 created_at_ms DESC 分页。
展示字段:
| Column | Source |
|---|---|
| 用户 ID | login_audit.user_id |
| 短号 | join users.current_display_user_id |
| 昵称 | join users.username |
| 国家/区域 | join users.country/region_id,只作用户资料快照展示 |
| 登录 IP | login_audit.client_ip |
| IP 所在国家 | login_audit.ip_country_code |
| 渠道 | login_audit.channel,值为 account/google/facebook/twitter/tiktok |
| 平台 | login_audit.platform,值为 android/ios |
| 结果 | success/failed/blocked/revoked |
| 屏蔽原因 | login_audit.block_reason |
| 登录时间 | login_audit.created_at_ms |
交互规则:
blocked=true或failure_code=AUTH_LOGIN_BLOCKED的表格行背景显示红色。- 如果异步 IP 风控在登录成功后撤销 session,该次登录日志也要更新为屏蔽态,行背景显示红色。
- 点击任意用户行,进入该用户历史登录日志侧栏或详情页。
- 列表只读,不提供解除屏蔽、修改策略、重试 provider 或手动踢下线按钮。
筛选条件:
| Filter | Rule |
|---|---|
| 用户 ID / 短号 | 精确搜索,短号需要先解析成 user_id |
| IP | 精确搜索明文 IP |
| IP 国家 | 按 ip_country_code |
| 渠道 | account/google/facebook/twitter/tiktok |
| 平台 | android/ios |
| 是否屏蔽 | all/blocked/not_blocked |
| 时间范围 | 按 created_at_ms |
User History
点击用户行后展示该用户历史登录日志:
| Field | Rule |
|---|---|
| 用户基础信息 | user_id、短号、昵称、头像、当前国家、当前区域 |
| 最近登录列表 | 按时间倒序展示该用户所有登录事件 |
| 风控结果 | 展示 IP 国家、是否屏蔽、屏蔽原因、撤销 session 时间 |
| 渠道和平台 | 展示每次登录的 channel/platform |
用户历史页只读。若后续需要手动封禁、解封或策略配置,必须走独立后台能力和审计,不混在登录日志里。
Configuration
首版配置放 gateway、user-service 和 cron-service 的 YAML。当前后台只做登录日志查看,不管理不支持国家、provider 顺序或语言/时区策略:
login_risk:
enabled: true
fast_guard:
blocked_languages: []
blocked_language_primary_tags: []
blocked_timezones: []
blocked_timezone_prefixes: []
ip:
unsupported_countries:
- CN
blocked_ttl_sec: 2592000
allowed_ttl_sec: 604800
unknown_ttl_sec: 1800
provider_timeout_ms: 800
providers:
- primary_geo
- fallback_geo_a
- fallback_geo_b
- fallback_geo_c
# services/cron-service/configs/config*.yaml
tasks:
login_ip_risk:
enabled: true
interval: 5s
timeout: 3s
lock_ttl: 30s
batch_size: 100
配置原则:
- gateway 快速判断和 user-service IP 风控策略的不支持国家列表必须同源,避免 gateway 放行但后台风控用另一套规则。
- provider 顺序必须显式配置,顺序代表信任优先级;不要在代码里随机切换。
- 线上配置不要写 provider secret;provider key 走密钥系统或环境变量。
- 多 App 时按
app_code覆盖策略。
Failure Policy
| Failure | Policy |
|---|---|
| Redis 不可用 | 放行登录;记录错误和告警,不读写 IP decision cache;session denylist 失效属于踢下线实时性下降,不能阻断登录 |
| MySQL 写登录日志或风险任务失败 | 放行登录;记录 error 和告警,后续依赖下次登录或缓存命中补偿 |
| 所有 IP provider 都失败 | 放行登录;能写缓存则写 unknown 短 TTL,不能写缓存也不阻塞 |
| 前置 provider 不可用 | 自动降级到下一个 provider;如果全部不可用则放行 |
| session 已经 logout | 后台风控撤销幂等成功,不重复踢 |
| 用户已换新 session | 只撤销命中的 session_id;如果 IP 已 blocked,后续登录会被 gateway 拒绝 |
| 踢 IM 失败 | session 已撤销仍然生效;记录补偿任务或等待客户端下一次 API 返回 SESSION_REVOKED |
| 房间踢出失败 | 不直接改 Redis;记录补偿,依赖 Room Cell/heartbeat 兜底 |
Implementation Sequence
- 增加
AUTH_LOGIN_BLOCKED错误码,并确认 gateway 对SESSION_REVOKED的客户端语义。 - gateway 登录入口增加 language/timezone fast guard 和 IP decision cache 读取。
- 登录请求把
client_ip/language/timezone透传到 user-service;密码登录也需要补这两个字段。 - user-service 登录成功后创建
login_ip_risk_jobs,任务至少包含session_id/user_id/client_ip/client_ip_hash/channel/platform/request_id。 - 扩展
login_audit,保存明文 IP、IP 国家、渠道、平台、是否屏蔽和屏蔽原因。 - 实现
UserCronService.ProcessLoginIPRiskBatch:MySQL claim 任务,按优先级顺序调用 provider fallback chain,写决策和 Redis cache。 - user-service 增加按
session_id撤销 session 的内部能力,并写撤销原因。 - gateway 增加统一 session denylist middleware,JWT 校验后查
auth:revoked_session:{app_code}:{sid}。 - 风控撤销 session 时写 Redis denylist,并把对应登录审计更新为屏蔽态。
- 接入腾讯云 IM 踢下线或系统消息通知。
- 增加 room-service 踢人命令或复用现有 Room Cell 管理命令,不直接操作 room Redis。
- admin 在用户管理下新增二级菜单“登录日志”,支持列表、筛选、红色屏蔽行和用户历史登录日志。
- 增加日志、指标和告警:provider 超时率、unknown 比例、blocked 数、撤销数、踢下线失败数。
Test Cases
| Case | Expected |
|---|---|
| language 命中阻断 | gateway 直接返回 AUTH_LOGIN_BLOCKED,不调用 user-service 登录 |
| timezone 命中阻断 | gateway 直接返回 AUTH_LOGIN_BLOCKED |
| IP cache blocked | gateway 直接返回 AUTH_LOGIN_BLOCKED |
| IP cache miss | 登录成功,创建风险任务 |
| 后台风控判定 allowed | 写 allowed cache,不撤销 session |
| 后台风控判定 blocked | 撤销 session,写 blocked cache,写 denylist,触发踢下线 |
| blocked 后同 IP 再登录 | gateway 登录前拒绝 |
| 已撤销 session 调业务接口 | gateway middleware 返回 SESSION_REVOKED |
| provider 全失败 | 放行,能写缓存则写 unknown cache,不踢下线 |
| 第一个 provider 不可用、第二个 provider 返回 blocked country | 写 blocked cache,撤销 session |
| 登录日志列表含 blocked 行 | 该行背景为红色 |
| 点击用户登录日志行 | 展示该用户历史登录日志 |
| refresh 使用已撤销 session | user-service 返回 SESSION_REVOKED |
Open Decisions
| Decision | Recommendation |
|---|---|
| Redis/MySQL/IP 查询不可用时是否阻断登录 | 当前方案统一 fail-open:全部放行,只记录错误和告警 |
| 第一个可用 provider 命中不支持国家是否踢 | 当前方案直接踢;provider 顺序代表信任优先级 |
| 明文 IP 是否落库 | 明文 IP 落 MySQL,普通服务日志仍只打 hash |
| 是否做后台管理页面 | 只做用户管理下的“登录日志”二级菜单,不做策略编辑后台 |
Final Rule
当前阶段最重要的工程约束是:地区风险只在 gateway 入口和 user-service session 层处理,玩法服务不感知风险状态。只要 gateway 统一检查 session denylist,后台异步命中后撤销 session 就能对房间、钱包、礼物、VIP、消息等所有 HTTP 业务统一生效。