くらげになりたい。

くらげのようにふわふわ生きたい日曜プログラマなブログ。趣味の備忘録です。

Spring Securityのカスタマイズ、どこでなにするのか?のおぼえがき

Spring Boot/Spring Securityを使って、認証部分のカスタマイズについていろいろ調べてみた、ときの備忘録
というか、やってみたこと、というか、おぼえがき

認証全体の流れ

リクエストが来たら

  1. SecurityFilter/AuthenticationFilterを通って
  2. AuthenticationManagerがAuthenticationProviderを呼び出し、
  3. AuthenticationProviderがUserDetailsSerivceを呼び出し、
  4. UserDetailsSerivceが、UserDetailsを返す。
  5. 認証が成功すると、AuthenticationFilterが、AuthenticationSuccessHandlerを呼び出す
  6. 認証が失敗すると、AuthenticationFilterが、AuthenticationFailureHandlerを呼び出す

多いし複雑。。さらに、認証が行われると認証イベントが発生し、それもハンドリングできる。

データベース認証をカスタマイズしたい(UserDetailsSerivce+UserDetails)

以下の2つを用意すればOK

  • UserDetailsを実装した資格情報を格納するクラス
  • UserDetailsSerivceを実装したDBからUserDetailsを取得するクラス

前の記事でも書いているので省略。

wannabe-jellyfish.hatenablog.com

認証処理の前にリクエストをフィルタしたり、追加情報を使いたい(AuthenticationFilter)

  • 認証処理に行く前に何かしたい場合は、AuthenticationFilterの実装をする
  • form認証の場合、デフォルトではUsernamePasswordAuthenticationFilterが使われる
  • UsernamePasswordAuthenticationFilterを拡張してゴニョゴニョするのがよい
public class MyUsernamePasswordAuthenticationFilter extends UsernamePasswordAuthenticationFilter {

    @Override
    public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {
     if (!request.getMethod().equals("POST")) {
   throw new AuthenticationServiceException("Authentication method not supported: " + request.getMethod());
  }
       String username = obtainUsername(request);
       String password = obtainPassword(request);
       // このあたりでゴニョゴニョする。
        
       UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(username, password);

        // Allow subclasses to set the "details" property
       setDetails(request, authRequest);
       
       return this.getAuthenticationManager().authenticate(authRequest);
    }
}
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        // Java Configではこんな感じ
        http.addFilterAt(new MyUsernamePasswordAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class);
    }

認証成功時の処理をカスタマイズしたい(AuthenticationSuccessHandler)

  • AuthenticationSuccessHandlerを実装したクラスを使う
  • デフォルトはForwardAuthenticationSuccessHandler?SavedRequestAwareAuthenticationSuccessHandler?のよう
  • ForwardAuthenticationSuccessHandlerの実装はこんな感じ。authenticationを見て、遷移先とかをゴニョゴニョできる
public class ForwardAuthenticationSuccessHandler implements AuthenticationSuccessHandler {

    private final String forwardUrl;

    /**
    * @param forwardUrl
    */
    public ForwardAuthenticationSuccessHandler(String forwardUrl) {
        Assert.isTrue(UrlUtils.isValidRedirectUrl(forwardUrl), "'" + forwardUrl + "' is not a valid forward URL");
        this.forwardUrl = forwardUrl;
    }

    public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
        request.getRequestDispatcher(forwardUrl).forward(request, response);
    }
}

認証失敗時の処理をカスタマイズしたい(AuthenticationFailureHandler)

  • AuthenticationSuccessHandlerを実装したクラスを使う
  • デフォルトはForwardAuthenticationFailureHandler?SimpleUrlAuthenticationFailureHandler?のよう
  • ForwardAuthenticationFailureHandlerの実装はこんな感じ。exceptionを見て、遷移先とかをゴニョゴニョできる
public class ForwardAuthenticationFailureHandler implements AuthenticationFailureHandler {

    private final String forwardUrl;

    /**
    * @param forwardUrl
    */
    public ForwardAuthenticationFailureHandler(String forwardUrl) {
        Assert.isTrue(UrlUtils.isValidRedirectUrl(forwardUrl), "'" + forwardUrl + "' is not a valid forward URL");
        this.forwardUrl = forwardUrl;
    }

    public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws IOException, ServletException {
        request.setAttribute(WebAttributes.AUTHENTICATION_EXCEPTION, exception);
        request.getRequestDispatcher(forwardUrl).forward(request, response);
    }
}

参考にしたサイト様