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
相关产品推荐
相关产品推荐

