web-dev-qa-db-ja.com

SpringセキュリティでCSRF保護が有効になっているユニットテストコントローラー

最近、Spring Security3.2を使用するプロジェクトにCSRF保護を導入しました。

CSRFを有効にした後、csrfトークンがリクエストに存在しないため、一部の単体テストが失敗します。 '_csrf'パラメーターにダミー値を入れましたが、機能しませんでした。

とにかく、リクエストを送信する前にcsrfトークンを取得できますか(ユニットテスト時)?

18
uiroshan

あなたの答えuiroshanはcsrfトークンの目的を打ち破っています:あなたの構成ではそれは定数値になります(あなたの構成があなたのテストコンテキストでのみ使用され、あなたがそれを指定しなかった場合を除きます)。

この問題を解決する正しい(そしてより簡単な)方法は次のとおりです。

_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() staticメソッドは_spring-security-test_によって提供されます:

_<dependency>
    <groupId>org.springframework.security</groupId>
    <artifactId>spring-security-test</artifactId>
    <version>5.1.8.RELEASE / 5.2.2.RELEASE</version>
    <scope>test</scope>
</dependency>
_

ユニットテストにアクセスするには、次のインポートが必要です。

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

カスタム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");
    
1
uiroshan

@Thierryの回答に加えて、リアクティブスタックにも同様のソリューションがあります。

バックエンドをWebTestClientで呼び出す場合:

import org.springframework.security.test.web.reactive.server.SecurityMockServerConfigurers.csrf

        // ...
        webTestClient.mutateWith(csrf()).post()...
0
pixel