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

Symfony中X-CSRF-TOKEN验证失败问题排查

问题:Symfony测试中CSRF令牌验证失败(Invalid CSRF token 2)

在Symfony项目中,编写测试代码获取CSRF令牌并发送请求获取用户数据时触发Invalid CSRF token 2错误,但相同请求在Postman中可正常运行。相关代码见下文。

问题根源分析

  • 会话不共享:测试代码中getCSRF()方法和testGetUsers()方法分别创建了独立的HttpClient实例,两个实例之间不会共享Cookie。而Symfony的CSRF令牌与用户会话(通过Cookie识别)绑定,获取令牌时的会话和验证时的会话不一致,导致令牌无效。
  • Postman正常的原因:Postman会自动维护同一个会话的Cookie,获取令牌和发送请求使用的是同一个会话,因此验证通过。

解决方案

1. 复用同一个HttpClient实例(修改基类)

在测试基类中初始化全局共享的HttpClient实例,确保获取CSRF令牌和业务请求使用同一个会话:

namespace App\Tests;

use ApiPlatform\Symfony\Bundle\Test\ApiTestCase;
use Symfony\Component\HttpClient\HttpClient;
use Symfony\Contracts\HttpClient\HttpClientInterface;
use Symfony\Contracts\HttpClient\ResponseInterface;

class SuperApiTestCase extends ApiTestCase {
    private ?HttpClientInterface $client = null;
    
    public static function getBaseUrl(): ?string {
        return "https://localhost:8000";
    }

    public static function getShortSessionKey(): ?string {
        return "JWT_KEY";
    }

    protected function setUp(): void {
        parent::setUp();
        // 初始化共享的HttpClient实例,统一SSL配置
        $this->client = HttpClient::create([
            'verify_peer' => false,
            'verify_host' => false,
        ]);
    }

    public function sendRequest(string $url, string $methodType = 'GET'): ?ResponseInterface {
        $url = $this->getBaseUrl() . $url;

        return $this->client->request($methodType, $url, [
            'headers' => [
                'Authorization' => 'Bearer ' . $this->getShortSessionKey(),
                'Accept' => 'application/json',
            ],
        ]);
    }

    public function getCSRF(): ?string {
        $response = $this->sendRequest("/api/v1/config/csrf");
        $csrfData = $response->toArray();
        return $csrfData['csrf_token'];
    }

    // 提供给子类使用的共享客户端
    protected function getClient(): HttpClientInterface {
        return $this->client;
    }
}

2. 修改用户控制器测试代码

使用基类提供的共享HttpClient实例,不再创建新实例:

namespace App\Tests\Controller;

use App\Tests\SuperApiTestCase;
use Symfony\Component\HttpClient\Exception\ClientException;

class UserControllerTest extends SuperApiTestCase {
    
    public function testGetUsers() {
        $csrfToken = $this->getCSRF();
        $client = $this->getClient();

        try {
            $response = $client->request('GET', 'https://localhost:8000/api/v1/user/', [
                'headers' => [
                    'Authorization' => 'Bearer ' . $this->getShortSessionKey(),
                    'Content-Type' =>  'application/json',
                    'X-CSRF-TOKEN' => $csrfToken,
                ],
            ]);
            
            $statusCode = $response->getStatusCode();
            $data = $response->toArray();
        
            echo "Status Code: $statusCode\n";
            print_r($data);
        
        } catch (ClientException $e) {
            echo 'HTTP status code: ' . $e->getResponse()->getStatusCode() . PHP_EOL;
            echo 'Error: ' . $e->getMessage() . PHP_EOL;
            echo 'Response: ' . $e->getResponse()->getContent(false) . PHP_EOL;
        }
    }
}

3. 额外验证点

  • 确认/api/v1/config/csrf接口生成令牌时使用的ID与监听器中的csrf_token_id一致(当前代码已匹配,无需修改)。
  • 确保两次请求使用的JWT令牌对应同一个用户,避免因身份切换导致会话不一致。

内容的提问来源于stack exchange,提问作者Mustafa Poya

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.19 09:12:39