AngularJS + JavaEE7 でログイン・ログアウトと、認証の処理について実装してみたときのメモ。
動作確認は GlassFish 4.0 上で実施。
認証処理を入れた場所
次の2カ所で、ログインしているかどうかのチェックを入れている。
- REST のリクエストがあったとき
- ページ遷移のとき
REST のリクエストがあったときのチェック
Filter でチェック
Filter を作成して、そこでチェックを行っている。
package sample.angular.filter;
import java.io.IOException;
import javax.inject.Inject;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.annotation.WebFilter;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import sample.angular.rest.session.UserSession;
@WebFilter("/rest/*")
public class SessionFilter implements Filter {
@Inject
private UserSession userSession;
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
HttpServletRequest req = (HttpServletRequest)request;
HttpServletResponse res = (HttpServletResponse)response;
if (this.needsSessionCheck(req) && !this.userSession.isLogged()) {
res.sendError(HttpServletResponse.SC_UNAUTHORIZED);
} else {
chain.doFilter(request, response);
}
}
private boolean needsSessionCheck(HttpServletRequest req) {
return !req.getRequestURI().endsWith("/session");
}
@Override public void destroy() {/*no use*/}
@Override public void init(FilterConfig filterConfig) throws ServletException {/*no use*/}
}
認証チェックが必要なリクエスト(/rest/session 以外へのリクエスト)の場合、 UserSession の状態をチェックして認証済みか確認している。
認証済み出ない場合は 401 のエラーをクライアントに返している。
UserSession で認証情報を保持する
UserSession は CDI の SessionScoped で管理しているインスタンスで、内部にログイン済みかどうかのフラグを持っている。
package sample.angular.rest.session;
import java.io.Serializable;
import javax.enterprise.context.SessionScoped;
import javax.inject.Inject;
import javax.servlet.http.HttpSession;
@SessionScoped
public class UserSession implements Serializable {
private static final long serialVersionUID = 1L;
@Inject
private HttpSession session;
private boolean isLogged;
public void start() {
this.isLogged = true;
}
public boolean isLogged() {
return this.isLogged;
}
public void end() {
this.isLogged = false;
this.session.invalidate();
}
}
ログイン時に start() メソッドを実行し、ログアウトするときに end() メソッドを実行してセッションを破棄する。
サーバーから 401 が返されたときは、クライアントでページを強制遷移させる
angular
.module('mine')
.factory('SessionInterceptor', function($q, $location) {
return {
responseError: function(response) {
// サーバーからのレスポンスが 401 ならトップページに強制遷移する
if (response.status === 401) {
$location.path('/');
}
return $q.reject(response);
}
};
});
$http の処理にインターセプターをかませて、サーバーからのレスポンスコードが 401 の場合に、強制的にログイン画面に遷移するようにしている。
ページ遷移のときのチェック
ページが切り換わるときのイベントをフックして、都度サーバーに認証情報の問い合わせを行うようにしている。
angular
.module('mine', ['ngRoute'])
.config(function($httpProvider) {
$httpProvider.interceptors.push('SessionInterceptor');
})
.config(function($routeProvider) {
$routeProvider
.when('/', {
templateUrl: 'app/view/login.html',
controller: 'LoginController',
needsAuth: false
})
.when('/sample', {
templateUrl: 'app/view/sample.html',
controller: 'SampleController',
needsAuth: true
});
})
.run(function($rootScope, sessionService, $location) {
// ページが切り換わるごとに、認証が必要なページの場合はサーバーにセッションの問い合わせを行う
$rootScope.$on('$routeChangeSuccess', function(event, current, next) {
if (current.needsAuth) {
sessionService.inquire();
}
});
});
$routeScope.$on("$routeChangeSuccess", callback) でページ遷移のイベントをフックできる。
このタイミングで認証が必要なページかどうかを確認して、必要であればサーバーに確認のリクエストを送信する。
401 が返された場合は、先ほどのインターセプターによってログイン画面に強制遷移させられる。
認証が必要かどうかを判断するための値(needsAuth)は、 when() メソッドでルートを設定するときの引数で指定している。