Symfony 3 - CSRF 令牌仅在使用 AJAX 提交时无效

Ber*_*rdA 5 javascript php ajax jquery symfony

环顾四周后我找不到解决方案。仅当通过 Ajax 提交时,我才会收到以下错误。这意味着我事先已经以常规 Symfony 方式提交了表单,没有出现任何问题。

\n\n
\n

CSRF 令牌无效。请尝试重新提交表格。

\n
\n\n

什么有效:

\n\n
    \n
  • 常规表单提交,即不使用 Ajax
  • \n
  • 使用 Ajax,我在控制器端提交之前检查了 $_POST 是否已正确填充,包括 CSFR 令牌,如下所示:
  • \n
\n\n
\n

$form->submit($request->request->get($form->getName(\'user\')));

\n
\n\n

根据要求,请参阅下面的输出

\n\n
\n

var_dump($request->request->get($form->getName(\'user\')));

\n
\n\n
array(8) { \n["name"]=> string(9) "fafdffafa" \n["avatar"]=> string(9) "dfafffafa" \n["cityId"]=> string(1) "6" \n["phone"]=> string(14) "33434343434344" \n["email"]=> array(2) { \n      ["first"]=> string(22) "myemail@gmail.com" \n      ["second"]=> string(22) "myemail@gmail.com" } \n["plainPassword"]=> array(2) { \n      ["first"]=> string(8) "senha444" \n      ["second"]=> string(8) "senha444" } \n["blogSubs"]=> string(1) "1" \n["_token"]=> string(43) "hLhyoRxVYmJ_FWK0FqXmiiEYZMZ77fDAWvxCZMXCtxw" }\n
Run Code Online (Sandbox Code Playgroud)\n\n

只是为了确认,如果我只是注释掉下面的 JavaScript,提交将起作用并且实体将被持久化。

\n\n

这是相同的 var_dump,这次是在工作正常时。

\n\n
array(9) { \n["name"]=> string(12) "dfdfdfdfafaf" \n["avatar"]=> string(13) "dfdfdfdafdafa" \n["cityId"]=> string(1) "8" \n["phone"]=> string(16) "3343434343343343" \n["email"]=> array(2) { \n    ["first"]=> string(22) "myemail@gmail.com" \n    ["second"]=> string(22) "myemail@gmail.com" }\n["plainPassword"]=> array(2) { \n    ["first"]=> string(8) "senha444" \n    ["second"]=> string(8) "senha444" } \n["blogSubs"]=> string(1) "1" \n["save"]=> string(0) "" \n["_token"]=> string(43) "hLhyoRxVYmJ_FWK0FqXmiiEYZMZ77fDAWvxCZMXCtxw" }\n
Run Code Online (Sandbox Code Playgroud)\n\n

这是Symfony生成的提交按钮,但没有被js序列化捕获。

\n\n
<button type="submit" id="user_save" name="user[save]" class="btn-default btn">Cr\xc3\xa9er mon compte</button>\n
Run Code Online (Sandbox Code Playgroud)\n\n

表单(我跳过了 $builder 表单,因为它似乎没有必要)

\n\n

应用程序/资源/视图/common/register.html.twig

\n\n
{{ form_start(form, { \'attr\': { \'id\': \'signup_form\' }}) }}\n    <div class="contact input-group">\n        {{ form_widget(form.name) }}\n    </div>\n    <div class="contact input-group">\n        {{ form_widget(form.avatar) }}\n        <span class="input-group-addon" id="info_avatar">\n            <i class="fa fa-info"></i>\n        </span>       \n    </div>\n    <div class="contact input-group">\n        {{ form_widget(form.cityId) }}\n    </div>\n    <div class="contact input-group">\n        {{ form_widget(form.phone) }}           \n    </div>\n    <div class="contact input-group">\n        {{ form_widget(form.email) }}\n    </div>\n    <div class="contact input-group">\n        {{ form_widget(form.plainPassword) }}\n    </div>\n    <div class="contact">\n        {{ form_widget(form.blogSubs) }}\n    </div>\n    <div class="contact form-group ">\n        {{ form_widget(form.save) }}\n    </div>\n{{ form_end(form) }} \n
Run Code Online (Sandbox Code Playgroud)\n\n

同一文件上的 JavaScript:

\n\n
<script>\n    $(\'body\').on(\'submit\',\'#signup_form\',function(event) {\n        event.preventDefault();\n        var str = $("#signup_form").serialize();\n        $.ajax({\n            url: "/inscription",\n            type: "POST",\n            dataType:"json",\n            data: str,\n            success: function (data) {\n                            alert(data);\n            }   \n        });\n    });\n  </script>\n
Run Code Online (Sandbox Code Playgroud)\n\n

和控制器( getErrorMessages() 方法是在 SO 上找到的。)

\n\n

/src/UsedBundle/Controller/RegistrationController.php

\n\n
namespace UsedBundle\\Controller;\n\nuse UsedBundle\\Form\\UserType;\nuse UsedBundle\\Entity\\User;\nuse Sensio\\Bundle\\FrameworkExtraBundle\\Configuration\\Route;\nuse Symfony\\Bundle\\FrameworkBundle\\Controller\\Controller;\nuse Symfony\\Component\\HttpFoundation\\Request;\nuse Symfony\\Component\\Security\\Core\\Encoder\\UserPasswordEncoderInterface;\nuse Doctrine\\ORM\\EntityManagerInterface;\nuse Symfony\\Component\\HttpFoundation\\JsonResponse;\n\nclass RegistrationController extends Controller\n{\n/**\n * @Route("/inscription", name="inscription")\n */\npublic function registerAction(Request $request)\n{\n    $user = new User();\n    $form = $this->createForm(UserType::class, $user);\n\n    if ($request->isMethod(\'POST\')) {\n        var_dump($_POST);\n        $form->submit($request->request->get($form->getName(\'user\')));\n        if(!$form->isValid()){ \n            $errors = $this->getErrorMessages($form);\n            var_dump($errors);\n        }\n        if ($form->isSubmitted() && $form->isValid()) {\n            $password = $this->get(\'security.password_encoder\')\n                ->encodePassword($user, $user->getPlainPassword());\n            $user->setPassword($password);\n            $email = $user->getEmail();\n            $user->setUserKey( $email );\n            $user->setUserKeyTime();\n            $user->setDateReg();\n            $em = $this->getDoctrine()->getManager(\'used\');\n            $em->persist($user);\n            $em->flush();\n            return new JsonResponse(array(\'message\' => \'Success!\'));\n        }\n    }else{\n        return $this->render(\n            \'common/register.html.twig\',\n            array(\'form\' => $form->createView())\n        );           \n    }\n}\n\nprotected function getErrorMessages($form) \n{\n    $errors = array();\n    foreach ($form->getErrors() as $key => $error) {\n        $errors[] = $error->getMessage();\n    }\n\n    foreach ($form->all() as $child) {\n        if (!$child->isValid()) {\n            $errors[$child->getName()] = $this->getErrorMessages($child);\n        }\n    }\n\n    return $errors;\n} \n}\n
Run Code Online (Sandbox Code Playgroud)\n

lor*_*dos 0

正如评论中所讨论的,您的问题的原因与不同的环境有关。如果您使用带有app_dev.php前端控制器的标准 Symfony 项目,您的表单将使用环境的有效 csrf 令牌进行渲染dev。你的 JavaScript 代码

$.ajax({
    url: "/inscription",
    type: "POST",
    dataType:"json",
    data: str,
    success: function (data) {
                    alert(data);
    }   
});
Run Code Online (Sandbox Code Playgroud)

不知道 Symfony 环境,因此 url/location指向该prod环境,导致出现 CSRF 令牌无效的错误消息。

要解决此问题,您可以使前端代码了解 Symfony 路由,例如使用FOSJsRoutingBundle。或者您可以禁用环境的 CSRF 保护dev

# app/config/config_dev.yml
framework:
    csrf_protection: false
Run Code Online (Sandbox Code Playgroud)