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

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令牌:
    1. 在Twig模板中获取令牌:
      <script>
          const csrfToken = '{{ csrf_token("new_lesson_plan") }}';
      </script>
      
    2. 将令牌加入请求的FormData:
      const formData = new FormData();
      formData.append('_token', csrfToken);
      // 追加其他表单字段
      
  • 在控制器中添加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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.03 12:05:56