feat(A1/A3+C8): 客户端令牌链路打通(按人鉴权就绪)+ 快照 JSON 写入节流

A1/A3 客户端令牌链路(服务端守卫已能按 JWT 鉴权,缺口在客户端不带身份)
- 前端:新增 user-token-bridge(纯逻辑,值变化才推送)+ user-token-sync(启动安装,
  pywebviewready/storage/60s 轮询补推),main.ts 接入;桥接口补 set_user_token
- 客户端:新增 crawler_core/user_token.py(持有 + 对自家 Java 端点注入
  Authorization: Bearer <jwt>,Session.request 包装,第三方域名不注入、已有头不覆盖、
  登出清空、SHUFUAI_DISABLE_USER_TOKEN_HOOK 可关);main.py 暴露桥方法并在启动安装钩子
- 服务端:守卫契约测试补 5 个用户态前缀用例(开关关闭放行 / 打开后匿名 401 /
  用户令牌通过 / 内部令牌仍放行 / /api/ziniao 第二层)
- 翻开关的前置条件(客户端铺开后置 aiimage.security.user-tool-guard-enabled=true)写入报告

C8 快照 JSON 写入节流
- 读端改以 biz_task_result_item 行为准、JSON 仅兜底(历史任务 JSON 仍是唯一副本时可用)
- 整档 JSON 改 30s 节流写,终态路径 force 立即写;新增 2 个契约测试钉住语义

验证:mvn test 2888 全绿;npm test 741 全绿;user_token 钩子自测(注入/归一/第三方跳过/不覆盖/登出)通过
This commit is contained in:
2026-09-14 10:20:15 +08:00
parent 7643094f1d
commit c55c4a140b
10 changed files with 423 additions and 9 deletions
@@ -0,0 +1,51 @@
import { test } from 'node:test'
import assert from 'node:assert/strict'
import { resetUserTokenBridgeStateForTest, syncUserTokenToBridge } from '../src/shared/auth/user-token-bridge.ts'
import type { TokenPushTarget } from '../src/shared/auth/user-token-bridge.ts'
function collector() {
const pushed: string[] = []
return {
target: { push: (value: string) => pushed.push(value) } as TokenPushTarget,
pushed,
}
}
test('令牌变化时推送,重复值不重复推送', () => {
resetUserTokenBridgeStateForTest()
const { target, pushed } = collector()
assert.equal(syncUserTokenToBridge('abc', target), true)
assert.equal(syncUserTokenToBridge('abc', target), false, '同值不重复推送')
assert.equal(syncUserTokenToBridge(' def ', target), true, '去空白后不同则推送')
assert.deepEqual(pushed, ['abc', 'def'])
})
test('登出推空串以清空 Python 侧令牌', () => {
resetUserTokenBridgeStateForTest()
const { target, pushed } = collector()
syncUserTokenToBridge('abc', target)
assert.equal(syncUserTokenToBridge('', target), true)
assert.deepEqual(pushed, ['abc', ''])
})
test('没有可用的桥时返回 false 且不记录状态(后续有桥仍会推送)', () => {
resetUserTokenBridgeStateForTest()
const { target, pushed } = collector()
assert.equal(syncUserTokenToBridge('abc', null), false, '无桥不推送')
assert.equal(syncUserTokenToBridge('abc', target), true, '之后有桥仍推送')
assert.deepEqual(pushed, ['abc'])
})
test('推送目标抛异常时返回 false', () => {
resetUserTokenBridgeStateForTest()
const throwing: TokenPushTarget = {
push: () => {
throw new Error('bridge down')
},
}
assert.equal(syncUserTokenToBridge('abc', throwing), false)
})