前回の続きです。

前回は、簡単にログイン画面を実装して、Spring Security のログイン機能を試してみましたが、今回は、ログイン機能に加えて、ログアウト処理についても検証してみたいと思います。

 

 

前回記事で、次のような疑問もありました。

  • ログイン後にプログラマブルにリダイレクト先を指定できないのか?
  • ログインに失敗した場合に独自の処理を追加したい場合は?
  • ログアウト処理は?

今回は、このあたりについても検証する予定です。

 

Spring Security Login 高度な設定

 

successHandler()

ログイン認証が成功したあと、様々な条件によって違うページにリダイレクトしたい場合、successHandler( handler ) を指定します。

 

Java Config の例

@Bean AuthenticationSuccessHandler customSuccessHandler() {
    // カスタムで作成したハンドラを返す
    return new CustomSuccessHandler();
}

// ...
@Override
protected void configure(HttpSecurity http) throws Exception {
    http
        // ...
        .formLogin()
        .loginPage("/loginForm")
        .usernameParameter("userName")
        .passwordParameter("password")
        .loginProcessingUrl("/login")
        .successHandler(customSuccessHandler()) // この部分を追加
        .failureUrl("/loginForm?error=true");
}

 

カスタム AuthenticationSuccessHandler の実装例

独自の LoginSuccessHandler を実装する場合、Spring Security が提供している AuthenticationSuccessHandler インターフェースを実装したクラスを作成します。

下のコードは、ユーザーの役割 (ロール) に応じてログイン後のリダイレクト先を決定します。

public class CustomSuccessHandler implements AuthenticationSuccessHandler {

    protected static final Logger log = LoggerFactory.getLogger(LoggingSuccessHandler.class);
    private RedirectStrategy redirectStrategy = new DefaultRedirectStrategy();

    @Override
    public void onAuthenticationSuccess(
            HttpServletRequest request, 
            HttpServletResponse response,
            Authentication authentication) throws IOException, ServletException {
        handle(request, response, authentication);
        clearAuthenticationAttributes(request);

    }

    protected void handle(
            HttpServletRequest request,
            HttpServletResponse response,
            Authentication authentication)
            throws IOException {
        String targetUrl = determineTargetUrl(authentication);
        if (response.isCommitted()) {
            log.debug("レスポンスがすでにコミットされています。次のURLへリダイレクトできません。 :" + targetUrl);
            return;
        }
        redirectStrategy.sendRedirect(request, response, targetUrl);
    }

    /* ユーザーの認証情報を元にリダイレクト先のURLを返します */
    protected String determineTargetUrl(Authentication authentication) {
        boolean isUser = false;
        boolean isAdmin = false;
        Collection<? extends GrantedAuthority> authorities = authentication.getAuthorities();
        for (GrantedAuthority grantedAuthority : authorities) {
            if (grantedAuthority.getAuthority().equals("ROLE_USER")) {
                isUser = true;
                break;
            } else if (grantedAuthority.getAuthority().equals("ROLE_ADMIN")) {
                isAdmin = true;
                break;
            }
        }

        if (isUser) {
            return "/user";
        } else if (isAdmin) {
            return "/admin";
        } else {
            throw new IllegalStateException();
        }
    }

    protected void clearAuthenticationAttributes(HttpServletRequest request) {
        HttpSession session = request.getSession(false);
        if (session == null) {
            return;
        }
        session.removeAttribute(WebAttributes.AUTHENTICATION_EXCEPTION);
    }

    public RedirectStrategy getRedirectStrategy() {
        return redirectStrategy;
    }

    public void setRedirectStrategy(RedirectStrategy redirectStrategy) {
        this.redirectStrategy = redirectStrategy;
    }
}

 

failureHandler()

前回では、ログイン認証が失敗したときに、failureUrl( url ) で認証失敗時のURLにリダイレクトさせていますが、より複雑な処理を行いたい場合は、AuthenticationFailureHandler インターフェースを実装したクラスを作成します。

 

Java Config の例

@Bean
public AuthenticationFailureHandler customAuthenticationFailureHandler() {
    // カスタムのハンドラを返す
    return new CustomAuthenticationFailureHandler();
}

// ...
@Override
protected void configure(HttpSecurity http) throws Exception {
    http
        // ...
        .formLogin()
        .loginPage("/loginForm")
        .usernameParameter("userName")
        .passwordParameter("password")
        .loginProcessingUrl("/login")
        .successHandler(customSuccessHandler())
        .failureHandler(customAuthenticationFailureHandler()); // ハンドラを指定
}

 

カスタム AuthenticationFailureHandler の実装例

以下は実装の例です。

public class CustomAuthenticationFailureHandler 
  implements AuthenticationFailureHandler {

    private ObjectMapper objectMapper = new ObjectMapper();

    @Override
    public void onAuthenticationFailure(
      HttpServletRequest request,
      HttpServletResponse response,
      AuthenticationException exception) 
      throws IOException, ServletException {

        response.setStatus(HttpStatus.UNAUTHORIZED.value());
        Map<String, Object> data = new HashMap<>();
        data.put(
          "timestamp", 
          Calendar.getInstance().getTime());
        data.put(
          "exception", 
          exception.getMessage());

        response.getOutputStream()
          .println(objectMapper.writeValueAsString(data));
    }
}

 

この例では、HTTP ステータスコード 401 (Unauthorized) とともに、エラーの情報とタイムスタンプをレスポンスで返します。

AuthenticationFailureHandler の他にも Spring には、ユーザーの用途によってすぐに使えるコンポーネントがあります。

  • DelegatingAuthenticationFailureHandler
    AuthenticationException のサブクラスを、違う AuthenticationFailureHandler に委任します。これは要するに、AuthenticationException のインスタンスごとに違った振る舞いを生成することができます。
  • ExceptionMappingAuthenticationFailureHandler
    AuthenticationException のフルクラス名によって、指定されたURLにリダイレクトします。
  • ForwardAuthenticationFailureHandler
    AuthenticationException のタイプに関係なくユーザーを指定されたURLにフォワードします。
  • SimpleUrlAuthenticationFailureHandler
    デフォルトで使用されるコンポーネントで、ユーザーを failureUrl が指定されていればリダイレクトします。でなければ、単順に 401 レスポンスを返します。

 

Spring Security Logout 一般的な設定

logout()

Java Config

ログアウトの基本的な設定は、logout() メソッドを使用することです。/logout という URL にアクセスすることでログアウトできるように構成されます。

@Configuration
@EnableWebSecurity
public class SecSecurityConfig extends WebSedurityConfigurerAdapter {
    @Override
    protected void comfigure(final HttpSecurity http) throws Exception {
        http
            // ...
            .logout()
            //...
    }

    //...
}

ちなみにデフォルトでは、ログアウトしたときにログインページに遷移するようになっています。1

 

Spring Security Logout 高度な設定

 

logoutSuccessUrl()

ログアウト処理が成功した後、Spring Security はユーザーを指定したページにリダイレクトします。

デフォルトは、/login?logout (正確には、loginPage( url ) で指定したURLになるので、この記事では loginForm?logout) になります。logoutSuccessUrl( url ) を使用すると、任意のページにリダイレクトできます。

// ...
.logout()
.logoutSuccessUrl("/afterlogout.html")

 

logoutUrl()

ログアウトをする際のトリガーとなる URL を指定します。デフォルトは /logout ですが、これを変更することができます。

下の例であれば、/logoutAction にアクセスするとログアウト処理が実行されます。

.logout()
.logoutUrl("/logoutAction")

 

invalidateHttpSession() と deleteCookies()

invalidateHttpSession( boolean ) は、ログアウト後に HttpSession を無効にするかどうかを指定します。true であれば、ログアウト時にセッション情報が削除されます。ログアウト後に HttpSession を無効にしたくない場合は、false を指定します。

deleteCookies( cookieName... ) は、ログアウト時に指定された名前 (複数指定可) の Cookie を削除します。

Java Config の例

.logout()
.logoutUrl("/logout")
.invalidateHttpSession(true)
.deleteCookies("JSESSIONID")

 

logoutSuccessHandler()

LogoutSuccessHandler を置きかえて、ログアウト処理が成功したときにより柔軟な処理を行うことができます。

 

Java Config の例

@Bean
public LogoutSuccessHandler logoutSuccessHandler() {
    // ハンドラを返す
    return new CustomLogoutSuccessHandler();
}

@Override
protected void comfigure(final HttpSecurity http) throws Exception {
    http
        // ...
        .logout()
        .logoutSuccessHandler(logoutSuccessHandler());
        //...
}

 

カスタム LogoutSuccessHandler の実装

以下がカスタム (何の意味もないただログを残すだけ) の LogoutSuccessHandler の実装になります。

public class CustomLogoutSuccessHandler extends SimpleUrlLogoutSuccessHandler implements LogoutSuccessHandler {

    private static final Logger log = LoggerFactory.getLogger(CustomLogoutSuccessHandler.class);

    @Override
    public void onLogoutSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication)
            throws IOException, ServletException {
        if (authentication != null && authentication.getDetails() != null) {
            log.info("ログアウトするよ: " + authentication.getDetails().toString());
        } else {
            log.info("ログアウト");
        }

        super.onLogoutSuccess(request, response, authentication);
    }

}

logoutSuccessHandler( handler ) を指定すると、仮に logoutSuccessUrl( url ) が指定されていたとしても機能しません。そのために、ハンドラ内でリダイレクトさせる必要があることに注意してください。

上の例では、親クラスである SimpleUrlLogoutSuccessHandleronLogoutSuccess() を呼び出すことでリダイレクトさせています。この場合、"/" にリダイレクトされます。

下の例のように、HttpServletResponse を利用してリダイレクトさせる方法もあります。

public class CustomLogoutSuccessHandler implements LogoutSuccessHandler{
    @Override
    public void onLogoutSuccess(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse,
                                Authentication authentication) throws IOException, ServletException {
        if (authentication != null && authentication.getDetails() != null) {
            log.info("ログアウトするよ: " + authentication.getDetails().toString());
        } else {
            log.info("ログアウト");
        }

        httpServletResponse.setStatus(HttpServletResponse.SC_OK);
        //redirect to login
        httpServletResponse.sendRedirect("/login");
    }
}

 

まとめ

前回と今回で、Spring Security を使ってユーザーのログイン認証処理とログアウトにかんする処理の例を上げました。

次回は、ユーザーモデルの実装と、DBの連携、ユーザー登録画面について検証してみたいと思います。

 

 


  1. 実際には、/loginForm?logout に遷移。 


 

 

zaturendo

中小企業社内SE。

0件のコメント

コメントを残す

アバタープレースホルダー

メールアドレスが公開されることはありません。 が付いている欄は必須項目です