Spring Framework Advent Calendar 2011 part.7 - Spring Security で認証成功時に条件によって遷移先を変える
Spring Framework Advent Calendar の 7 日目です。今日はふたたび Spring Security の話です。小出しにしているような感もありますが、気にせずいきましょう。
以前 Spring Security でアカウントロック機構を実現する - 倖せの迷う森 にて、Spring Security では認証の成功や失敗に対応するイベントハンドラがあり、それらのイベントに対応する Listener を書いて Bean 登録しておけば自動的にそれが呼び出される、という話をしました。
さて、あなたが現在作成中の Web システムにこのような認証に関する要件が与えられたとします。
- 認証はIDとパスワードで行う
- パスワードには有効期限がある
- 有効期限を過ぎたパスワードでログインした場合、パスワード強制変更画面に飛ばす
以前紹介した ApplicationListener を用いる手法では、ログイン後に遷移するURLを条件によって変更することはできませんので、このような要件を解決することはできませんでした。ApplicationListener は、認証イベントに対応して実行される「裏方」のようなもので、HTTP に関する操作(リダイレクトとか)というのは想定されていません。
ああ、認証成功時に遷移するURLに対して手を加えられるような方法があれば...そう思いますよね。まさにその部分を担当している Spring Security のインターフェースがあります。そう、AuthenticationSuccessHandler です。この Handler は、名前の通り認証成功時に実行される、遷移先のURLをコントロールするためのハンドラです。インターフェース宣言されているメソッドのシグニチャが
public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws ServletException, IOException
であることからも分かる通り、HttpRequest や HttpResponse を操作するためにあるインターフェースであることが分かると思います。このインターフェースを適切に実装すれば、上記要件を満たすことができそうですね。
それでは実装例を見てみましょう。
import java.io.IOException; import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.springframework.security.core.Authentication; import org.springframework.security.web.RedirectStrategy; import org.springframework.security.web.authentication.SavedRequestAwareAuthenticationSuccessHandler; public class PasswordExpiredCheckAuthenticationSuccessHandler extends SavedRequestAwareAuthenticationSuccessHandler { private String redirectUrl; public void setRedirectUrl(String redirectUrl) { this.redirectUrl = redirectUrl; } @Override public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws ServletException, IOException { String userId = authentication.getName(); if (isPasswordExpired(userId)) { RedirectStrategy redirectStrategy = getRedirectStrategy(); redirectStrategy.sendRedirect(request, response, redirectUrl); return; } super.onAuthenticationSuccess(request, response, authentication); } private boolean isPasswordExpired(String userId) { // implementation... } }
今回は AuthenticationSuccessHandler の実装のひとつである SavedRequestAwareAuthenticationSuccessHandler をさらに拡張する方式を採用しています。SavedRequestAwareAuthenticationSuccessHandler は、認証のために Spring Security が割り込んだ際の元のリクエストを、認証成功時に復元するというハンドラです*1。
この拡張された AuthenticationSuccessHandler を利用するには、Spring Security の Bean 定義ファイルに以下のように書きます。
... <sec:http auto-config="true"> ... <sec:form-login authentication-success-handler-ref="passwordExpiredCheckAuthenticationSuccessHandler" /> ... </sec:http> <bean id="passwordExpiredCheckAuthenticationSuccessHandler" class="org.example.app.PasswordExpiredCheckAuthenticationSuccessHandler"> <property name="redirectUrl" value="/expired" /> <property name="alwaysUseDefaultTargetUrl" value="true" /> <property name="defaultTargetUrl" value="/" /> </bean> ...
このように、新しく作成したハンドラを Bean 登録した上で、それを
以前紹介した ApplicationListener を用いる手法と今回紹介した手法の使い分けとしては、
- 認証後の遷移先を制御する目的なら AuthenticationSuccessHandler
- 認証成功にトリガーして、裏で何かする (ロギングなど) なら ApplicationListener
このように意識しておけばよいでしょう。
*1:元リクエストは通常、Session スコープに保持されています。