Spring Securityで、セッションタイムアウト時のAjaxリクエストに対応する
例えば、jQueryで
$("#div").load("content_fragment.jsp");
のようにコンテンツをロードする処理を想定する。
こういったAjaxによる画面更新では、ロード先のコンテンツがログインを必要とするもので、さらに「ログインしていない場合はログインページにリダイレクト」のような作りになっている場合、セッションが無効な状態(親ページを表示した後セッションタイムアウトしたとか)でロードするとログインページが画面に埋め込まれるような間抜けなことになってしまう。
これの対策としては、jQueryであればHTTPステータスコード200番台か304以外はエラーとして扱われ*1要素の更新が行われないので、Webアプリ側で400番台とかのエラーコードを返せば良い。この場合でも load の第3引数のコールバックは呼び出されるので、そこでエラーであれば通知を出すなりログインページに飛ばすなりすれば良さそうだ。
$("#div").load("content_fragment.jsp", null, function(text, status, res) { if (res.status == 401) { alert("セッションタイムアウトみたいです…"); window.location.replace("login.jsp"); } });
で、Spring Securityでフォーム認証を行っている場合、このように「ログインしていない場合、Ajaxリクエストならリダイレクトではなくエラーのレスポンスを返す」という動作はどうすれば実現できるかというと、デフォルトで使用されるEntryPointである AuthenticationProcessingFilterEntryPoint を拡張すればとりあえず可能らしい。
package example.webapp.security; import java.io.IOException; import javax.servlet.ServletException; import javax.servlet.ServletRequest; import javax.servlet.ServletResponse; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.springframework.security.AuthenticationException; import org.springframework.security.ui.webapp.AuthenticationProcessingFilterEntryPoint; public class AjaxAwareAuthenticationProcessingFilterEntryPoint extends AuthenticationProcessingFilterEntryPoint { @Override public void commence(ServletRequest request, ServletResponse response, AuthenticationException authException) throws IOException, ServletException { // jQueryでのAjaxリクエストにはこのヘッダがつく(他は知らない) if ("XMLHttpRequest".equals(((HttpServletRequest)request).getHeader("X-Requested-With"))) { // HTTP的に正しいかはともかく、なんとなく近そうなステータス(401)を返す ((HttpServletResponse)response).setStatus(HttpServletResponse.SC_UNAUTHORIZED); return; } super.commence(request, response, authException); } }
で、applicationContext-security.xml とかそういうファイルで上のを使うように指示する。
<http entry-point-ref="entryPoint" auto-config="true"> <intercept-url pattern="/login.jsp" filters="none" /> <intercept-url pattern="/**" access="ROLE_USER" /> <form-login always-use-default-target="true" authentication-failure-url="/login.jsp?failure=true" default-target-url="/home.jsp" login-page="/login.jsp" /> <logout logout-success-url="/logout.jsp" /> </http> <beans:bean id="entryPoint" class="example.webapp.security.AjaxAwareAuthenticationProcessingFilterEntryPoint"> <beans:property name="loginFormUrl" value="/login.jsp" /> </beans:bean>