From e78f382ee4242d33830a5f29eff2aecb6da1f6b0 Mon Sep 17 00:00:00 2001 From: smail Date: Sun, 6 Sep 2026 19:17:03 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E5=BC=80=E5=8F=91=E4=BB=A3?= =?UTF-8?q?=E7=90=86=E7=99=BB=E5=BD=95=E4=BC=9A=E8=AF=9D=E6=A0=A1=E9=AA=8C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/listen/http_server/https_server.go | 11 ++++++++++- backend/listen/http_server/https_server_test.go | 9 +++++++++ frontend/vite.config.js | 14 ++++++++++++++ 3 files changed, 33 insertions(+), 1 deletion(-) diff --git a/backend/listen/http_server/https_server.go b/backend/listen/http_server/https_server.go index 8367fc9..a7c4069 100644 --- a/backend/listen/http_server/https_server.go +++ b/backend/listen/http_server/https_server.go @@ -118,7 +118,7 @@ func contextIterceptor(h controllers.HandlerFunc) http.HandlerFunc { return } - if stateChangingMethod(r.Method) && hasSessionCookie(r) && (!sameOriginRequest(r) || !csrf.Valid(r)) { + if stateChangingMethod(r.Method) && !csrfExemptPath(r.URL.Path) && hasSessionCookie(r) && (!sameOriginRequest(r) || !csrf.Valid(r)) { response.NewErrorResponse(response.ParamsError, "请求来源校验失败", "").FPrint(w) return } @@ -262,6 +262,15 @@ func stateChangingMethod(method string) bool { return method == http.MethodPost || method == http.MethodPut || method == http.MethodPatch || method == http.MethodDelete } +func csrfExemptPath(path string) bool { + switch normalizedAPIPath(path) { + case "/api/user/info": + return true + default: + return false + } +} + func hasSessionCookie(r *http.Request) bool { if session.Instance == nil { return false diff --git a/backend/listen/http_server/https_server_test.go b/backend/listen/http_server/https_server_test.go index 35c1eef..6c092ea 100644 --- a/backend/listen/http_server/https_server_test.go +++ b/backend/listen/http_server/https_server_test.go @@ -30,6 +30,15 @@ func TestRouterRejectsGetForPostOnlyReadStateEndpoints(t *testing.T) { } } +func TestCSRFExemptsReadOnlyUserInfoEndpoint(t *testing.T) { + if !csrfExemptPath("/api/v1/user/info") { + t.Fatal("user info endpoint must be exempt from CSRF token validation") + } + if csrfExemptPath("/api/v1/user/edit") { + t.Fatal("user edit endpoint must keep CSRF token validation") + } +} + func configureProductionOriginTest(t *testing.T) { t.Helper() oldConfig := config.Instance diff --git a/frontend/vite.config.js b/frontend/vite.config.js index 814c164..b6eb9cf 100644 --- a/frontend/vite.config.js +++ b/frontend/vite.config.js @@ -12,6 +12,19 @@ export default defineConfig(({ mode }) => { const backendUrl = (env.VITE_DEV_BACKEND_URL || 'https://127.0.0.1:443').replace(/\/$/, '') const backendOrigin = (env.VITE_DEV_BACKEND_ORIGIN || 'https://xuans.com').replace(/\/$/, '') + const rewriteDevelopmentCookies = (proxyResponse) => { + const setCookies = proxyResponse.headers['set-cookie'] + if (!Array.isArray(setCookies)) { + return + } + + proxyResponse.headers['set-cookie'] = setCookies.map((cookie) => ( + cookie + .replace(/;\s*Secure/gi, '') + .replace(/;\s*Domain=[^;]*/gi, '') + )) + } + return { plugins: [ vue(), @@ -42,6 +55,7 @@ export default defineConfig(({ mode }) => { proxyRequest.setHeader('x-forwarded-proto', originURL.protocol.replace(':', '')) proxyRequest.removeHeader('referer') }) + proxy.on('proxyRes', rewriteDevelopmentCookies) } }, "/attachments": {