Symfony+VueJS CSRF令牌验证失败:“无效CSRF令牌”错误求助
Symfony 6.3 + VueJS 3.2 CSRF令牌验证失败排查方案
环境
- 后端:Symfony 6.3
- 前端:VueJS 3.2
- 数据传输方式:POST请求使用FormData
问题描述
前端提交注册表单时,已携带从Symfony获取的CSRF令牌,但后端验证始终返回“无效CSRF令牌”错误,且确认前后端传输的令牌值完全一致。
后端实现代码
namespace App\Controller; use App\Entity\User; use App\Form\UserRegistrationType; use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; use Symfony\Component\HttpFoundation\JsonResponse; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response; use Symfony\Component\PasswordHasher\Hasher\UserPasswordHasherInterface; use Symfony\Component\Routing\Annotation\Route; use Symfony\Component\Security\Csrf\CsrfTokenManagerInterface; class RegistrationController extends AbstractController { private CsrfTokenManagerInterface $csrfTokenManager; public function __construct(CsrfTokenManagerInterface $csrfTokenManager){ $this->csrfTokenManager = $csrfTokenManager; } #[Route('/api/get-csrf-token', name: 'get-csrf-token', methods: ['GET'])] public function getCsrfToken(): JsonResponse { $csrfToken = $this->csrfTokenManager->getToken('csrf_token')->getValue(); return $this->json(['csrf_token' => $csrfToken]); } #[Route('/api/register', name: 'app_register', methods: ['POST'])] public function register(Request $request, UserPasswordHasherInterface $passwordHasher): Response { $content = $request->request->all(); $csrfToken = $request->get('_token', ''); // return $this->json(['message' =>$csrfToken]); if (!$this->isCsrfTokenValid('csrf_token', $csrfToken)) { return $this->json(['errors' => 'ERROR: The CSRF TEST token is invalid. Please try to resubmit the form.'], Response::HTTP_BAD_REQUEST); } $user = new User(); $form = $this->createForm(UserRegistrationType::class, $user); $form->submit($content); if ($form->isSubmitted() && $form->isValid()) { // Hashing the password $hashedPassword = $passwordHasher->hashPassword($user, $user->getPassword()); $user->setPassword($hashedPassword); // Save the User entity in the database $entityManager = $this->getDoctrine()->getManager(); $entityManager->persist($user); $entityManager->flush(); // Return a success response return $this->json(['message' => 'User successfully registered.'], Response::HTTP_CREATED); } // Handle form errors return $this->json(['errors' => (string) $form->getErrors(true, false)], Response::HTTP_BAD_REQUEST); } }
前端实现代码
<template> <div class="form-container"> <form @submit.prevent="submitForm" class="form-register"> <div class="form-group"> <label for="email">Email</label> <input v-model="user.email" type="email" id="email" placeholder="Email" class="form-control" required > </div> <div class="form-group"> <label for="password">Password</label> <input v-model="user.password" type="password" id="password" placeholder="Password" class="form-control" required> </div> <input type="hidden" name="_token" :value="csrfToken"> <button type="submit" class="btn-submit">Register</button> </form> </div> </template> <script> import axios from 'axios'; export default { data() { return { user: { email: '', password: '', }, errors: [], csrfToken: '', }; }, created() { axios.get('/get-csrf-token').then((response) => { this.csrfToken = response.data.csrf_token; console.log('Token Get:', this.csrfToken); }); }, methods: { async submitForm() { try { let formData = new FormData(); Object.keys(this.user).forEach(key => formData.append(key, this.user[key])); formData.append('_token', this.csrfToken); const config = { headers: { 'Content-Type': 'multipart/form-data' } }; const response = await axios.post('/register', formData, config); console.log(response.data); } catch (error) { if (error.response) { console.error('Error:', error.response.data); } else { console.error('Error:', error.message); } } } }, }; </script>
已尝试操作
- 确认令牌生成与验证时使用的CSRF令牌ID(
csrf_token)一致 - 验证令牌已正确加入FormData并随POST请求发送
- 检查请求与响应头差异
- 在受控环境中用纯Symfony表单测试功能正常
排查思路与解决方案
1. 会话一致性检查
Symfony的CSRF令牌依赖会话存储,若前端获取令牌和提交请求时未使用同一个会话,会导致验证失败:
- 前端axios需配置
withCredentials: true,确保携带会话Cookie:// 在请求配置中添加 const config = { headers: { 'Content-Type': 'multipart/form-data' }, withCredentials: true }; - Symfony端确认
framework.yaml中CSRF保护开启:framework: csrf_protection: enabled - 跨域场景下,需配置CORS允许携带凭证,同时会话Cookie设置
SameSite: None和Secure: true(HTTPS环境)。
2. 表单CSRF验证冲突
若UserRegistrationType中默认开启了CSRF验证,会使用与手动验证不同的令牌ID,导致双重验证失败:
- 方案一:关闭表单的CSRF验证(已手动验证),修改
UserRegistrationType:public function configureOptions(OptionsResolver $resolver) { $resolver->setDefaults([ 'data_class' => User::class, 'csrf_protection' => false, ]); } - 方案二:统一令牌ID,让表单与手动验证使用同一个ID:
public function configureOptions(OptionsResolver $resolver) { $resolver->setDefaults([ 'data_class' => User::class, 'csrf_protection' => true, 'csrf_token_id' => 'csrf_token', // 与手动生成的ID一致 ]); }
3. 请求路径匹配验证
前端请求路径与后端路由存在差异:后端路由为/api/register和/api/get-csrf-token,但前端请求的是/register和/get-csrf-token。若未配置axios的baseURL为/api,需修正前端请求路径:
// 修改获取令牌的请求 axios.get('/api/get-csrf-token').then(...) // 修改注册请求 axios.post('/api/register', formData, config)
4. 令牌存储一致性排查
在后端register方法中添加日志,对比会话中的令牌与请求令牌是否一致:
// 在register方法开头添加 $sessionToken = $this->csrfTokenManager->getToken('csrf_token')->getValue(); error_log("Session CSRF Token: {$sessionToken}, Request Token: {$csrfToken}");
若两者不一致,说明会话未正确维持,需检查Cookie的传输与存储配置。
5. CSRF令牌生命周期验证
Symfony的CSRF令牌默认在会话有效期内有效,若会话过期或被重置,令牌会失效。可尝试在获取令牌后立即提交请求,排除令牌过期问题;同时检查服务器端会话超时时间配置。
内容的提问来源于stack exchange,提问作者alexdoobre
相关产品推荐
相关产品推荐

