Symfony 6.4更新后含换行/HTML的textarea表单提交报403错误
Symfony 6.4升级后提交含换行/HTML的textarea触发403/503错误的解决方法
问题概述
升级至Symfony 6.4后出现两类异常:
- 提交包含换行(
\n)或HTML代码的textarea内容时,触发403 Forbidden错误,仅单行无格式内容可正常提交; - 非AJAX方式提交表单时,第二次提交会出现503 Service Unavailable错误,需清除缓存才能恢复提交功能。
触发403的AJAX控制器代码
#[Route(path: '/ajax/save/new', name: 'new_lesson_plan')] public function newLessonPlan( LessonPlanRepository $lessonPlanRepo, Request $request, SessionRepository $sessionRepo, Mailer $mailer, SendSms $sendSms, ShortenUrl $shortenUrl, Environment $twig ): Response { $new_stuff = $request->request->get('new_stuff'); $finger_exercises = $request->request->get('finger_exercises'); $previous_pieces = $request->request->get('previous_pieces'); $current_piece = $request->request->get('current_piece'); $techniques_reinforced = $request->request->get('techniques_reinforced'); $extend_to_learn = $request->request->get('extend_to_learn'); $print_out = $request->request->get('print_out'); $assignment_for_student = $request->request->get('assignment_for_student'); $modify = $request->request->get('modify'); $sess_id = $request->request->get('session_id'); $session = $sessionRepo->findOneById($sess_id); $activity_string = ""; $activity_string .= "<h3>Warm-up</h3>"; $activity_string .= "<p>" . nl2br($finger_exercises) . "</p>"; $activity_string .= "<h3>Repertoire</h3>"; $activity_string .= "<p>" . nl2br($previous_pieces) . "</p>"; $activity_string .= "<h3>Creative</h3>"; $activity_string .= "<p> Learn: " . nl2br($new_stuff) . "</p>"; $activity_string .= "<h3>Current piece: " . $current_piece . "</h3>"; $activity_string .= "<p> This piece will reinforce the following: ". $techniques_reinforced .". I intend to do " . $extend_to_learn . "</p>"; $activity_string .= "<h3>Print outs</h3>"; $activity_string .= "<p>" . $print_out . "</p>"; $activity_string .= "<h3>Assignment</h3>"; $activity_string .= "<p>" . nl2br($assignment_for_student) . "</p>"; if($modify == 'false') { $lessonPlan = new LessonPlan(); $lp = 'Lesson Plan'; } else { $lessonPlan = $lessonPlanRepo->findOneBySession($session); $lp = 'Assessed Lesson Plan'; } $lessonPlan->setAddedOn($session->getBeginAt()); $lessonPlan->setSession($session); $lessonPlan->setStudent($session->getStudent()); $lessonPlan->setAddedOn(new \Datetime()); $lessonPlan->setWork($new_stuff); $lessonPlan->setActivities($activity_string); $entityManager = $this->managerRegistry->getManager(); $entityManager->persist($lessonPlan); $entityManager->flush(); $student = $lessonPlan->getStudent(); $teacher = $session->getTeacher(); $html = $twig->render('pdf/student_lesson_plan.html.twig', [ 'session' => $session, 'lesson_plan' => $lessonPlan, ]); $uigName = explode("-", (string) $session->getUserInstrumentGrade())[0]; $urlShort = $shortenUrl...; $sendSms->quickSend...; $mailer->sendEmailWithAttachment(...); $this->addFlash( 'success', 'The lesson plan was sent successfully.' ); return new JsonResponse($sess_id); }
非AJAX表单控制器代码
#[Route(path: '/new/for/{session_id}', name: 'lesson_plan_new', methods: ['GET', 'POST'])] public function new(Request $request, SessionRepository $sessionRepo, UserInstrumentGradeRepository $uigRepo, LessonPlanRepository $lessonPlanRepo, $session_id): Response { $this->denyAccessUnlessGranted('IS_AUTHENTICATED_REMEMBERED'); $session = $sessionRepo->findOneById($session_id); $lessonPlan = new LessonPlan(); $uig = $session->getUserInstrumentGrade(); $sessions = $uig->getSessions(); $lessonPlan->setAddedOn($session->getBeginAt()); $form = $this->createForm(LessonPlanType::class, $lessonPlan); $form->handleRequest($request); if ($form->isSubmitted() && $form->isValid()) { $lessonPlan->setSession($session); $lessonPlan->setStudent($session->getStudent()); $lessonPlan->setAddedOn(new \Datetime()); $entityManager = $this->managerRegistry->getManager(); $entityManager->persist($lessonPlan); $entityManager->flush(); return $this->redirectToRoute('lesson_plan_index', ['student_id' => $lessonPlan->getStudent()->getId()], Response::HTTP_SEE_OTHER); } $lessonPlanForThisSession = $lessonPlanRepo->findOneBySession($session); return $this->render('lesson_plan/new.html.twig', [ 'session' => $session, 'sessions' => $sessions, 'lessonPlanForThisSession' => $lessonPlanForThisSession, 'lesson_plan' => $lessonPlan, 'form' => $form, ]); }
解决方案
1. 修复403 Forbidden错误(AJAX请求)
核心原因:CSRF令牌缺失或验证失败
Symfony 6.4强化了CSRF保护机制,未携带有效CSRF令牌的AJAX请求会被拦截,尤其是包含HTML/换行的内容更容易触发恶意请求判定。
解决步骤:
- 在前端AJAX请求中传递CSRF令牌:
- 在Twig模板中获取令牌:
<script> const csrfToken = '{{ csrf_token("new_lesson_plan") }}'; </script> - 将令牌加入请求的FormData:
const formData = new FormData(); formData.append('_token', csrfToken); // 追加其他表单字段
- 在Twig模板中获取令牌:
- 在控制器中添加CSRF验证逻辑:
use Symfony\Component\Security\Csrf\CsrfTokenManagerInterface; use Symfony\Component\HttpFoundation\Response; public function newLessonPlan(..., CsrfTokenManagerInterface $csrfTokenManager): Response { $token = $request->request->get('_token'); if (!$csrfTokenManager->isTokenValid(new CsrfToken('new_lesson_plan', $token))) { return new JsonResponse(['error' => '无效的CSRF令牌'], Response::HTTP_FORBIDDEN); } // 后续业务逻辑 }
2. 修复503 Service Unavailable错误(非AJAX表单)
核心原因:缓存未正确更新
Symfony 6.4优化了缓存机制,表单类型或实体元数据的缓存未及时刷新,导致第二次提交时出现缓存冲突。
解决步骤:
- 开发环境:确保
.env中设置APP_ENV=dev且APP_DEBUG=true,自动禁用严格缓存;或手动清除缓存:php bin/console cache:clear - 生产环境:部署时自动执行缓存清除命令,检查
config/packages/framework.yaml的缓存配置,确保缓存目录权限正确(Web服务器可读写):framework: cache: app: cache.adapter.filesystem default_memcached_provider: 'memcached://localhost:11211' - 优化表单类型配置:若表单存在动态生成内容,可禁用表单缓存:
use Symfony\Component\Form\AbstractType; use Symfony\Component\OptionsResolver\OptionsResolver; class LessonPlanType extends AbstractType { public function configureOptions(OptionsResolver $resolver): void { $resolver->setDefaults([ 'data_class' => LessonPlan::class, 'csrf_protection' => true, 'csrf_token_id' => 'lesson_plan', 'cache_dir' => null, // 禁用表单缓存 ]); } }
额外检查项
- 确认实体字段类型:
LessonPlan的activities字段需设置为text或longtext类型,确保能存储HTML内容:use Doctrine\ORM\Mapping as ORM; #[ORM\Entity(repositoryClass: LessonPlanRepository::class)] class LessonPlan { #[ORM\Column(type: 'text')] private ?string $activities = null; // 其他字段定义 } - 查看服务器日志:通过
var/log/dev.log或生产环境日志,获取错误的具体触发原因,比如CSRF验证失败详情、缓存目录权限错误等。
内容的提问来源于stack exchange,提问作者Josiah
相关产品推荐
相关产品推荐

