forked from xiaohei/taiji-AI-PAD
fix(mcp-server): device-code 一次性作废失效修复(consume 集群 Redis 删除未生效)
初版 consume_device_code 用裸 redis_client.delete() 且吞异常,在集群 Azure Redis 上删除未生效,导致一个 device_code 换发 token 后仍能在每次 >interval 的轮询继续换发新 token —— 违反 RFC 8628 一次性语义与验收「换一次后再用→拒绝」。 初测二次轮询都在 slow_down 窗口内(<5s)被限流响应遮住,未暴露;>5s 公网 真实轮询复测才暴露。 修复:consume 改用已验证可靠的 _set_keepttl 置 status=consumed(token 端点 签发前硬检查 consumed → expired_token),并 best-effort 删除 device_code + device_user_code 两个 key。即使集群删除失败,状态位硬拦截。 复测(公网 APIM 真实路径,间隔 >5s):首 poll 签发 → 二/三次 poll 均 expired_token,不再重复签发。 镜像 device-code-fix2-20260722-arm64 @sha256:716c2e2d 已部署生产 3/3 Running。 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -159,12 +159,28 @@ async def mark_poll(device_code: str, state: dict) -> bool:
|
||||
return await _set_keepttl(_DC_PREFIX + device_code, json.dumps(state))
|
||||
|
||||
|
||||
async def consume_device_code(device_code: str) -> None:
|
||||
"""token 换发后作废该 device_code(一次性)。"""
|
||||
state = get_state()
|
||||
if not state.redis_client:
|
||||
async def consume_device_code(device_code: str, state: Optional[dict] = None) -> None:
|
||||
"""token 换发后作废该 device_code(一次性)。
|
||||
|
||||
先用**已验证可靠**的 _set_keepttl 把状态置 consumed(token 端点在签发前会
|
||||
检查 status==consumed → expired_token,这是硬拦截),再 best-effort 删除 key。
|
||||
裸 delete 在集群 Redis 上可能抛 MOVED 被吞掉,故不能只依赖删除。
|
||||
"""
|
||||
# 1) 置 consumed(权威拦截;保留原 TTL,不续命)
|
||||
if state is not None:
|
||||
st = dict(state)
|
||||
st["status"] = "consumed"
|
||||
ok = await _set_keepttl(_DC_PREFIX + device_code, json.dumps(st))
|
||||
if not ok:
|
||||
logger.error("device_auth_consume_mark_failed", key_prefix=(_DC_PREFIX + device_code)[:24])
|
||||
# 2) best-effort 物理删除(清 Redis,同时清 user_code 反查)
|
||||
rs = get_state()
|
||||
if not rs.redis_client:
|
||||
return
|
||||
try:
|
||||
await state.redis_client.delete(_DC_PREFIX + device_code)
|
||||
uc = (state or {}).get("user_code") if state else None
|
||||
await rs.redis_client.delete(_DC_PREFIX + device_code)
|
||||
if uc:
|
||||
await rs.redis_client.delete(_UC_PREFIX + uc)
|
||||
except Exception as e: # noqa: BLE001
|
||||
logger.warning("device_auth_consume_failed", error=str(e))
|
||||
logger.warning("device_auth_consume_delete_failed", error=str(e))
|
||||
|
||||
@@ -1613,7 +1613,7 @@ async def device_token(payload: _DeviceTokenRequest, db: AsyncSession = Depends(
|
||||
}
|
||||
access_token = create_access_token(data=token_data)
|
||||
refresh_token = create_refresh_token(data=token_data)
|
||||
await consume_device_code(dc) # 一次性作废
|
||||
await consume_device_code(dc, state) # 一次性作废(置 consumed + 删 key)
|
||||
logger.info("device_token_issued", user_id=str(user.id), client=state.get("client"))
|
||||
return SuccessResponse(data={
|
||||
"token": access_token,
|
||||
|
||||
Reference in New Issue
Block a user