在测试中获取 CSRF 令牌

Posted

技术标签:

【中文标题】在测试中获取 CSRF 令牌【英文标题】:Get the CSRF token in test 【发布时间】:2012-03-20 09:59:12 【问题描述】:

我正在编写功能测试,我需要发出 ajax 发布请求。 “CSRF 令牌无效。请尝试重新提交表单”。如何在功能测试中获取令牌?

$crawler = $this->client->request(
  'POST', 
  $url, 
  array(
      'element_add' => array(
          '_token' => '????',
          'name'   => 'bla',
      )

  ), 
  array(), 
  array('HTTP_X-Requested-With' => 'XMLHttpRequest')
);

【问题讨论】:

【参考方案1】:

CSRF 令牌生成器是普通的 symfony 2 服务。您可以自己获取服务并生成令牌。例如:

    $csrfToken = $client->getContainer()->get('form.csrf_provider')->generateCsrfToken('registration');
    $crawler = $client->request('POST', '/ajax/register', array(
        'fos_user_registration_form' => array(
            '_token' => $csrfToken,
            'username' => 'samplelogin',
            'email' => 'sample@fake.pl',
            'plainPassword' => array(
                'first' => 'somepass',
                'second' => 'somepass',
            ),
            'name' => 'sampleuser',
            'type' => 'DSWP',
        ),
    ));

generateCsrfToken 获取一个重要参数intention,该参数在测试和表单中应该相同,否则会失败。

【讨论】:

如何知道表单中使用的$intention参数是什么? 你可以使用任何你想要的 $intention,只要确保它与你用于检查令牌的相同 在 Symfony 3 中,->get('form.csrf_provider') 返回的服务已被弃用。请改用->get('security.csrf.token_manager') 方法是->get('security.csrf.token_manager')->getToken($intention)。如果您在测试中对用户进行任何身份验证,请确保首先生成令牌,以避免模拟会话存储在会话开始后尝试设置会话 ID。 如果您不确定意图,那么可能是'form'。但是,如果它没有在任何明显的地方设置(Symfony 魔法正在设置它),那么您可以临时编辑vendor/symfony/symfony/src/Symfony/Component/Security/Csrf/CsrfTokenManager.php 以在getToken() 方法中使用$tokenId 参数抛出异常,除非您的页面是多格式的或其他复杂,异常应该告诉你$intention参数。【参考方案2】:

经过长时间的搜索(我在 doc 和网上都没有找到关于如何检索 csrf 令牌的内容)我找到了一种方法:

$extract = $this->crawler->filter('input[name="element_add[_token]"]')
  ->extract(array('value'));
$csrf_token = $extract[0];

在发出请求之前从响应中提取令牌。

【讨论】:

这行得通,但它取决于测试的目的:如果它是一个行为测试,复制浏览器行为,那么它肯定是要走的路(无论如何它都是浏览器所做的);但如果是集成测试,检查控制器是否与 CSRF 框架正确集成,那么最好通过令牌管理器。【参考方案3】:

在 symfony 3 中,在您的WebTestCase 中,您需要获取 CSRF 令牌:

$csrfToken = $client->getContainer()->get('security.csrf.token_manager')->getToken($csrfTokenId);

要获得$csrfTokenId,最好的方法是to force it in the options 的FormType ():

class TaskType extends AbstractType

    // ...

    public function configureOptions(OptionsResolver $resolver)
    
        $resolver->setDefaults(array(
            'csrf_token_id'   => 'task_item',
        ));
    

    // ...

所以在这种情况下:$csrfTokenId = "task_item";。或者您可以尝试使用默认值,即表单的名称。

然后用它作为post参数:

$client->request(
  'POST',
  '/url',
  [
    'formName' => [
      'field' => 'value',
      'field2' => 'value2',
      '_token' => $csrfToken
    ]
  ]
);

【讨论】:

这是 symfony3 的当代答案,应该更积极地支持。它结合了来自已接受答案、其 cmets 的信息,并以记录的设置方式完成 $tokenId【参考方案4】:

以防万一有人偶然发现,在 symfony 5 中你可以通过这种方式获得令牌:

$client->getContainer()->get('security.csrf.token_manager')->getToken('token-id')->getValue();

其中 'token-id' 是您在表单类型的 configureOptions 方法中使用的 id,如下所示:

public function configureOptions(OptionsResolver $resolver)
    
        $resolver->setDefaults([
           
            "data_class"         => Foo::class,
            "csrf_protection"    => true,
            "csrf_field_name"    => "field_name", //form field name where token will be placed. If left empty, this will default to _token
            "csrf_token_id"      => "token-id", //This is the token id you must use to get the token value in your test
        ]);
    

然后你只需将令牌作为普通字段放入请求中。

【讨论】:

以上是关于在测试中获取 CSRF 令牌的主要内容,如果未能解决你的问题,请参考以下文章

在Jmeter中获取CSRF令牌以进行salesforce负载测试

如何在 Jmeter 中获取用于登录的 csrf 令牌

如何使用 ajax 在 LARAVEL 中获取新的 CSRF 令牌

将 CSRF 令牌添加到 RestTemplate 获取由表单登录保护的页面的请求

如何在 javaScript 中获取 CSRF 令牌值

在没有表格的情况下获取 Codeigniter CSRF 令牌?