如何让SvelteKit的CSRF令牌匹配Django后端的CSRF令牌?
Django + SvelteKit + Allauth Headless CSRF令牌不匹配问题
后端基于Django开发,前端使用Svelte + SvelteKit,采用Allauth Headless实现认证流程。移除settings.py中的django.middleware.csrf.CsrfViewMiddleware后,注册功能可正常运行,确认问题仅由CSRF令牌不匹配导致,与CORS无关(已确保ALLOWED_HOSTS添加了localhost:5173)。
问题现象
- Django后端和Svelte前端打印的CSRF令牌值完全一致,但浏览器控制台中请求头携带的
csrftoken值却不同 - Django控制台持续报错:
Forbidden (CSRF token from the 'X-Csrftoken' HTTP header incorrect.)
当前配置详情
1. Django CSRF令牌获取视图(views.py)
from django.http import JsonResponse from django.middleware.csrf import get_token def get_csrf_token(request): token = get_token(request) print(f"CSRF Token: {token}") response = JsonResponse({'csrfToken': token}) response.set_cookie('csrftoken', token) return response
2. Django URL路由配置
... from .views import get_csrf_token ... urlpatterns = [ ... path('csrf-token/', get_csrf_token, name='csrf_token'), ... ]
3. Svelte注册表单(+page.svelte)
<script lang="ts"> import type { ActionData } from './$types'; export let form: ActionData; </script> <div class="container my-4"> <div class="row justify-content-center"> <div class="col-md-6"> <h2 class="text-center logo-font mb-4 text-pink mt-5">Sign Up</h2> <hr class="w-25 mx-auto"> <form action="?/signup" method="POST" class="signup-form"> <div class="form-group"> <label for="username" class="--bs-pink1">Username</label> <input type="username" name="username" id="username" class="form-control" placeholder="Username" value={form?.username?? ''} required> <small class="text-secondary">*Choose your username carefully, as it cannot be changed later on.</small> </div> <div class="form-group"> <label for="email" class="text-pink">Email</label> <input type="email" name="email" id="email" class="form-control mb-3" placeholder="Email" value={form?.email ?? ''} required> </div> <div class="form-group"> <label for="password1" class="text-pink">Password</label> <input type="password" name="password1" id="password1" class="form-control mb-3" placeholder="Password" required> </div> <div class="form-group"> <label for="password2" class="text-pink">Confirm password</label> <input type="password" name="password2" id="password2" class="form-control mb-3" placeholder="Confirm password" required> </div> {#if form?.error} <div class="alert alert-danger mt-3"> <ul class="list-unstyled m-0"> <li>{form.error}</li> </ul> </div> {/if} <div class="form-actions text-center"> <button class="btn btn-lg btn-primary btn-block mt-4 -bs-purple" type="submit" style="width: 80%">Confirm</button> </div> </form> </div> </div> </div>
4. Allauth请求封装(参考React SPA示例)
import { getCsrfToken } from './csrfCookie'; ... const BASE_URL = `http://localhost:8000/_allauth/${CLIENT}/v1`; const ACCEPT_JSON = { accept: 'application/json' }; ... export const URLs = Object.freeze({ ... SIGNUP: BASE_URL + '/auth/signup', ... }); async function request(method, path, data = {}, headers = {}, event = null) { const options = { method, headers: { ...ACCEPT_JSON, ...headers }, credentials: 'include' }; if (typeof data !== 'undefined') { options.body = JSON.stringify(data); options.headers['Content-Type'] = 'application/json'; } const fetchFn = event ? event.fetch : fetch; const resp = await fetchFn(path, options); const msg = await resp.json(); return msg; } ... export async function signUp(data, event = null) { const csrfToken = await getCsrfToken(); const headers = csrfToken ? { 'X-CSRFToken': csrfToken } : {}; // 此处打印的令牌与Django端输出完全一致 console.log(`headers in signUp func: ${JSON.stringify(headers)}`); return await request('POST', URLs.SIGNUP, data, headers, event); }
5. Svelte端CSRF令牌获取逻辑(csrfCookie.ts)
export async function getCsrfToken() { const server_url = 'http://localhost:8000'; const response = await fetch(server_url + '/csrf-token/', { credentials: 'include' }); const data = await response.json(); const csrfToken = data.csrfToken; console.log('CSRF token response:', data); console.log('CSRF token for request:', csrfToken); return csrfToken; }
6. SvelteKit服务器端动作(+page.server.ts)
// +page.server.ts import { fail, redirect } from '@sveltejs/kit'; import type { Actions } from './$types'; import { signUp } from '$lib/allauth'; export const actions: Actions = { signup: async ({ request, fetch }) => { const data = await request.formData(); const username = data.get('username') as string; const email = data.get('email') as string; const password1 = data.get('password1') as string; const password2 = data.get('password2') as string; if (password1 !== password2) { return fail(400, { error: 'Passwords do not match' }); } try { const response = await signUp({ username, email, password: password1 }, { fetch }); console.log('Full response:', response); if (response.status === 200) { return redirect(303, '/account'); } else if (response.status === 400) { return fail(400, { error: response.errors[0].message }); } else { return fail(500, { error: 'An error occurred' }); } } catch (error) { console.error('Signup error:', error); return fail(500, { error: 'An error occurred' }); } } };
已尝试的解决方案及结果
- 禁用SvelteKit CSRF保护(修改
svelte.config.js):
import adapter from '@sveltejs/adapter-node'; import { vitePreprocess } from '@sveltejs/vite-plugin-svelte'; /** @type {import('@sveltejs/kit').Config} */ const config = { kit: { csrf: { checkOrigin: false }, adapter: adapter({ out: 'build', preprocess: [vitePreprocess()] }) } }; export default config;
结果:报错变为Forbidden (CSRF cookie not set.)
- 在表单中添加隐藏CSRF输入框:即使禁用SvelteKit CSRF保护,令牌仍会被自动替换,问题未解决。
求各位帮忙定位并解决这个CSRF令牌不匹配的问题!
内容的提问来源于stack exchange,提问作者St3ph3n92
相关产品推荐
相关产品推荐

