From 289f4488f94361990e77a5313a33abf49c57baa7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=BB=84=E8=87=AA=E8=BE=BE?= <980324341@qq.com> Date: Sat, 5 Sep 2026 14:49:52 +0800 Subject: [PATCH] =?UTF-8?q?fix(auth/logout):=20=E7=A6=81=E7=94=A8=E5=86=85?= =?UTF-8?q?=E7=BD=AE=20LogoutFilter=EF=BC=8C=E7=99=BB=E5=87=BA=E6=94=B9?= =?UTF-8?q?=E8=B5=B0=20auth=20=E6=A8=A1=E5=9D=97=E6=B8=85=E4=BC=9A?= =?UTF-8?q?=E8=AF=9D=20cookie?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit POST /logout 原先被 Spring Security 内置 LogoutFilter 截走(204),只清空 SecurityContext,从不执行 LoginController.logout,导致 aiimage_token cookie 残留、退出登录后仍保持登录态。SecurityConfig 显式 logout.disable() 后请求 落到 POST /logout → 200 + Set-Cookie aiimage_token=;(Max-Age=0) 清除。 补充真实登出 E2E(live):登出返回 200、下发清除 cookie、无 cookie 后 401。 注:此前的 500/CNFE 系运行进程 jar 被覆盖为 thin jar 所致,重建 fat jar 并重启即恢复;本提交只改逻辑。11/11 live 用例通过。 --- .../tests/live/task-40-live.test.ts | 27 +++++++++++++++++++ .../nanri/aiimage/config/SecurityConfig.java | 2 ++ 2 files changed, 29 insertions(+) diff --git a/admin-frontend-vue/tests/live/task-40-live.test.ts b/admin-frontend-vue/tests/live/task-40-live.test.ts index 55fcf8e5..8a6ba2f6 100644 --- a/admin-frontend-vue/tests/live/task-40-live.test.ts +++ b/admin-frontend-vue/tests/live/task-40-live.test.ts @@ -130,3 +130,30 @@ test('test_task_040_live_smoke_dependency_failure_returns_actionable_message', { const user = parseCurrentUser(await (await authedFetch('/api/admin/current-user')).json()) assert.ok(roleKind(user.role) !== null) }) + +test('test_task_040_live_smoke_logout_clears_cookie', { skip: !HAS_CREDS }, async () => { + // 真实登出 E2E:POST /logout 下发清除 aiimage_token 的 Set-Cookie;清除后同源请求回到 401。 + const login = await fetch(`${BASE}/login`, { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ username: USER, password: PASS, deviceId: `${DEVICE}-e2e-logout` }), + }) + const loginBody = (await login.json()) as { success?: boolean; data?: { token?: string } } + assert.equal(loginBody.success, true) + const token = loginBody.data?.token || '' + assert.ok(token) + const cookie = `aiimage_token=${token}` + const out = await fetch(`${BASE}/logout`, { + method: 'POST', + headers: { Cookie: cookie, 'X-Requested-With': 'XMLHttpRequest' }, + }) + assert.equal(out.status, 200, '登出应返回 200 而非内置 LogoutFilter 的 204/500') + assert.equal(((await out.json()) as { success?: boolean }).success, true) + const clearCookies = typeof out.headers.getSetCookie === 'function' ? out.headers.getSetCookie() : [] + const clearHeader = clearCookies[0] || out.headers.get('set-cookie') || '' + assert.match(clearHeader, /aiimage_token=;/, '登出应清除 aiimage_token cookie') + assert.match(clearHeader, /Max-Age=0|Expires=Thu, 01 Jan 1970/) + const after = (await (await fetch(`${BASE}/api/admin/current-user`)).json()) as { success?: boolean; code?: number } + assert.equal(after.success, false) + assert.equal(after.code, 401) +}) diff --git a/backend-java/src/main/java/com/nanri/aiimage/config/SecurityConfig.java b/backend-java/src/main/java/com/nanri/aiimage/config/SecurityConfig.java index 53bf199e..4ce93389 100644 --- a/backend-java/src/main/java/com/nanri/aiimage/config/SecurityConfig.java +++ b/backend-java/src/main/java/com/nanri/aiimage/config/SecurityConfig.java @@ -19,6 +19,8 @@ public class SecurityConfig { return http .cors(Customizer.withDefaults()) .csrf(csrf -> csrf.disable()) + // 登出走 auth 模块 POST /logout(清 aiimage_token cookie),禁用内置 LogoutFilter + .logout(logout -> logout.disable()) .authorizeHttpRequests(auth -> auth.anyRequest().permitAll()) .httpBasic(Customizer.withDefaults()) .build();