【问题标题】:Unit testing controllers with CSRF protection enabled in Spring security在 Spring 安全中启用 CSRF 保护的单元测试控制器
【发布时间】:2014-10-25 15:43:30
【问题描述】:

最近我们为使用 spring security 3.2 的项目引入了 CSRF 保护。

启用 CSRF 后,一些单元测试失败,因为请求中不存在 csrf 令牌。我在“_csrf”参数中放入了一些虚拟值,但它不起作用。

在发送请求之前(单元测试时)我是否可以获取 csrf 令牌?

【问题讨论】:

    标签: spring-mvc junit spring-security csrf spring-mvc-test


    【解决方案1】:

    我找到了解决此问题的方法,方法是创建自定义 CsrfTokenRepository 实现。这将始终生成一个常量令牌(如“test_csrf_token”)。所以我们可以将该令牌作为请求参数(因为它不会改变)与其他表单参数一起发送。以下是我解决问题所遵循的步骤。

    1. 创建一个实现 CsrfTokenRepository 接口的类。使用一些常量令牌值实现生成令牌。

      public CsrfToken generateToken(HttpServletRequest request) {
         return new DefaultCsrfToken(headerName, parameterName, "test_csrf_token");
      }
      
      @Override
      public void saveToken(CsrfToken token, HttpServletRequest request, HttpServletResponse response) {
          if (token == null) {
              HttpSession session = request.getSession(false);
              if (session != null) {
                  session.removeAttribute(sessionAttributeName);
              }
          } else {
              HttpSession session = request.getSession();
              session.setAttribute(sessionAttributeName, token);
          }
       }
      
       @Override
       public CsrfToken loadToken(HttpServletRequest request) {
          HttpSession session = request.getSession(false);
          if (session == null) {
             return null;
          }
          return (CsrfToken) session.getAttribute(sessionAttributeName);
       }
      
    2. 在您的安全配置中添加对 csrf 标记的引用。

      <http>
         <csrf token-repository-ref="customCsrfTokenRepository" />
         ....
      </http>
      
      <beans:bean id="customCsrfTokenRepository" class="com.portal.controller.security.TestCsrfTokenRepository"></beans:bean>
      
    3. 通过添加 csrf 请求参数来修改您的测试用例。

      request.addParameter("_csrf", "test_csrf_token");
      

    【讨论】:

      【解决方案2】:

      解决这个问题的方法是:

      import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.*;
      
      ...
      
      @Test
      public void testLogin() throws Exception {
          this.mockMvc.perform(post("/login")
                  .param("username", "...")
                  .param("password", "...")
                  .with(csrf()))
              .andExpect(status().isFound())
              .andExpect(header().string("Location", "redirect-url-on-success-login"));
      }
      

      重要的部分是:.with(csrf()),它将向查询中添加预期的_csrf 参数。

      csrf() 静态方法由spring-security-test 提供:

      <dependency>
          <groupId>org.springframework.security</groupId>
          <artifactId>spring-security-test</artifactId>
          <version>5.3.5.RELEASE / 5.4.1</version>
          <scope>test</scope>
      </dependency>
      

      您的单元测试将需要以下导入才能访问它:

       import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.*;
      

      【讨论】:

      • @WannyMiarelli :这个答案不依赖于正文内容类型。也许您的安全配置对 json 内容类型请求有自定义行为?
      • 是的,我知道 - 但我正在搜索使用带有 X-CSRF-TOKEN 标头的 CSRF,它就像附加 .asHeader() 函数一样简单
      • 在浪费了这么多时间之后,我找到了这个答案。 csrf() 函数是关键。点赞1个还不够,这个答案我要给100个赞
      【解决方案3】:

      除了@Thierry 的回答之外,还有类似的反应堆栈解决方案。

      使用WebTestClient 调用您的后端时:

      import org.springframework.security.test.web.reactive.server.SecurityMockServerConfigurers.csrf
      
              // ...
              webTestClient.mutateWith(csrf()).post()...
      

      【讨论】:

        猜你喜欢
        • 2019-06-22
        • 2013-11-19
        • 2021-12-24
        • 2021-02-06
        • 2016-10-29
        • 2016-05-28
        • 2020-07-07
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多