#概要
SpringBootで構築しているWebアプリケーションで、Controllerに対して共通処理を行いたい。
主に簡単な認証処理で、ログイン状態であればControllerでの処理、ログイン状態でなければログイン画面へ遷移させたい。
URLパスやパッケージベースでなく、Annotationで管理したい。
方法
HandlerInterceptor
を使うことで実装が可能になる。
HandlerInterceptor
をimplementしたクラスでは preHandle
postHandle
afterCompletion
を実装する必要がある。
それぞれ、
-
preHandle
→ Controllerにリクエストが行く前に呼ばれる -
postHandle
→ ビューのレンダリング前に呼ばれる。RestControllerでは呼ばれない。 -
afterCompletion
→ ビューのレンダリング後に呼ばれる。RestControllerでも呼ばれる。
となっている。
実装したクラスを作成し、@Configuration
を付与し、全てのパッケージにおいて有効にしておく。
/**
* login check
*/
@Log4j
public class SampleInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
// trueであれば通す
return true;
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
}
}
/**
* configuration for app
*/
@Configuration
public class WebApplicationConfiguration {
@Bean
public SampleInterceptor sampleInterceptor() {
return new SampleInterceptor();
}
@Bean
public MappedInterceptor interceptor() {
return new MappedInterceptor(new String[]{"/**"}, sampleInterceptor());
}
}
有効範囲をContorollerのAnnotationで制御する
preHandleでログインチェックを行うが、このままでは 引数の HttpServletRequest
からrequestURLを取り出し、特定のURLでは認証チェックを除外する、といった処理になるが煩雑なのでAnnotationを自作する。
仮に@NonAuthとする。method単位につけられるように、@Target(ElementType.METHOD)
としておく。
/**
* non require auth
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface NonAuth {
}
このAnnotationがついている場合には、認証チェックを走らせないようにする。
HandleIntercepter内で、実行されるContorollerClassのメソッドのAnnotationを取得するには、preHandleの引数の Object handler
をorg.springframework.web.method.HandlerMethod;
キャストしてからgetMethodを呼び出す。
その後、org.springframework.core.annotation.AnnotationUtils
で、当該Annotationが付与されてるかチェックしてあげればよい。
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
HandlerMethod hm = (HandlerMethod) handler;
Method method = hm.getMethod();
NonAuth annotation = AnnotationUtils.findAnnotation(method, NonAuth.class);
if (annotation != null) {
log.info(String.format("exclude login check %s", requestURI));
return true;
}
}
しかし、このままだと、静的ファイルへのアクセス時にも呼び出しが行われ、その時はHandlerMethod
へのキャスト時にClassCastException
が発生してしまうので、js/css
が含まれていたら除外している。
多分ここはもっとマシな方法がある。
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
String requestURI = request.getRequestURI();
if (isLoginCheckPath(requestURI) == false) {
return true;
}
HandlerMethod hm = (HandlerMethod) handler;
Method method = hm.getMethod();
NonAuth annotation = AnnotationUtils.findAnnotation(method, NonAuth.class);
if (annotation != null) {
log.info(String.format("exclude login check %s", requestURI));
return true;
}
private boolean isLoginCheckPath(String requestUri) {
return requestUri.contains("js/") == false && requestUri.contains("css/") == false;
}
}
↑上記はやはりイマイチだった。
静的リソースの場合は、handler
が org.springframework.web.servlet.resource.ResourceHttpRequestHandler
のインスタンスになるので、その場合も通してあげればよい。
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
//静的リソースの場合は認証不要
if (handler instanceof ResourceHttpRequestHandler) {
return true;
}
// @NonAuthがついてるメソッドは認証不要
HandlerMethod hm = (HandlerMethod) handler;
Method method = hm.getMethod();
NonAuth annotation = AnnotationUtils.findAnnotation(method, NonAuth.class);
if (annotation != null) {
log.info(String.format("exclude login check %s", requestURI));
return true;
}
//認証済みかチェックする処理
}