Symfony Form 与 CSRF 保护详解
CSRF 基础概念
CSRF(Cross-Site Request Forgery) 跨站请求伪造攻击,攻击者诱导用户点击链接或访问恶意页面,利用用户已登录的身份发起非预期的请求。

Symfony Form 的 CSRF 保护机制
Symfony 表单组件默认启用 CSRF 保护,自动为每个表单生成唯一的令牌。
自动启用方式
// 默认配置下,所有表单自动启用 CSRF
$form = $this->createFormBuilder($task)
->add('name', TextType::class)
->add('save', SubmitType::class)
->getForm();
CSRF 配置与定制
全局配置(config/packages/framework.yaml)
framework:
form:
csrf_protection:
enabled: true
field_name: '_token' # 字段名称
token_id: null # 令牌ID(默认使用表单类名)
表单类级别配置
class TaskType extends AbstractType
{
public function configureOptions(OptionsResolver $resolver): void
{
$resolver->setDefaults([
'csrf_protection' => true,
'csrf_field_name' => '_token',
'csrf_token_id' => 'task_item', // 自定义令牌ID
]);
}
}
CSRF 令牌工作机制
// 1. 生成令牌(在表单渲染时)
$csrfToken = $csrfTokenManager->getToken('task_item');
// 2. 验证令牌(在表单提交时)
if ($csrfTokenManager->isTokenValid(new CsrfToken('task_item', $submittedToken))) {
// 令牌有效,处理表单
}
AJAX 请求中的 CSRF 处理
前端实现
// 方式1:获取页面中的CSRF令牌
const csrfToken = document.querySelector('input[name="_token"]').value;
// 方式2:通过API获取令牌
async function getCsrfToken() {
const response = await fetch('/api/csrf-token');
const data = await response.json();
return data.token;
}
// AJAX请求示例
fetch('/api/submit-form', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-CSRF-TOKEN': csrfToken // 或 'X-CSRF-Token': csrfToken
},
body: JSON.stringify(formData)
});
后端令牌端点
// src/Controller/SecurityController.php
#[Route('/api/csrf-token', name: 'api_csrf_token')]
public function getCsrfToken(CsrfTokenManagerInterface $csrfTokenManager): JsonResponse
{
$token = $csrfTokenManager->getToken('custom_token_id');
return $this->json([
'token' => $token->getValue()
]);
}
自定义 CSRF 验证
手动验证 CSRF 令牌
use Symfony\Component\Security\Csrf\CsrfToken;
use Symfony\Component\Security\Csrf\CsrfTokenManagerInterface;
class UserController extends AbstractController
{
#[Route('/profile/update', name: 'profile_update')]
public function update(
Request $request,
CsrfTokenManagerInterface $csrfTokenManager
): Response {
if ($request->isMethod('POST')) {
$submittedToken = $request->request->get('_token');
$token = new CsrfToken('profile_update', $submittedToken);
if (!$csrfTokenManager->isTokenValid($token)) {
$this->addFlash('error', 'Invalid CSRF token');
return $this->redirectToRoute('profile');
}
// 处理表单数据
}
// 生成令牌
$csrfToken = $csrfTokenManager->getToken('profile_update');
return $this->render('profile/update.html.twig', [
'csrf_token' => $csrfToken->getValue()
]);
}
}
常见问题与解决方案
问题1:CSRF 令牌过期
// 解决方案:延长令牌有效期
// config/packages/security.yaml
security:
firewalls:
main:
form_login:
csrf_token_generator: security.csrf.token_manager
csrf_parameter: _csrf_token
csrf_token_id: authenticate
问题2:API 请求无需 CSRF
// 解决方案:禁用特定路由的 CSRF
class ApiFormType extends AbstractType
{
public function configureOptions(OptionsResolver $resolver): void
{
$resolver->setDefaults([
'csrf_protection' => false, // API 端点禁用 CSRF
]);
}
}
问题3:多表单同页面
// 解决方案:为每个表单设置不同的令牌ID
class Form1Type extends AbstractType
{
public function configureOptions(OptionsResolver $resolver): void
{
$resolver->setDefaults([
'csrf_token_id' => 'form1_token',
]);
}
}
class Form2Type extends AbstractType
{
public function configureOptions(OptionsResolver $resolver): void
{
$resolver->setDefaults([
'csrf_token_id' => 'form2_token',
]);
}
}
最佳实践总结
- 始终启用 CSRF 保护(默认启用)
- 使用 HTTPS 防止令牌被中间人窃取
- 为不同表单使用不同令牌ID
- API 端点使用 Token 认证而非 CSRF
- 不要将 CSRF 令牌存储在 Cookie 中
- 定期轮换 CSRF 令牌
- 同页面多个表单时,确保令牌唯一性
- AJAX 请求正确传递 CSRF 令牌
安全增强建议
// 1. 自定义令牌生成器
class CustomCsrfTokenGenerator implements CsrfTokenGeneratorInterface
{
public function generateCsrfToken(string $tokenId): string
{
return bin2hex(random_bytes(32));
}
}
// 2. 令牌存储到会话
// config/packages/framework.yaml
framework:
session:
handler_id: null # 使用默认会话存储
cookie_secure: auto
cookie_httponly: true
cookie_samesite: lax
通过合理配置和使用 Symfony 的 CSRF 保护机制,可以有效防止跨站请求伪造攻击,确保应用的安全性。