You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.05 18:07:01