SpringBootの特定のAnnotationが付与されたControllerのメソッドに対して事前処理を行う

#概要
SpringBootで構築しているWebアプリケーションで、Controllerに対して共通処理を行いたい。
主に簡単な認証処理で、ログイン状態であればControllerでの処理、ログイン状態でなければログイン画面へ遷移させたい。
URLパスやパッケージベースでなく、Annotationで管理したい。

## 方法
`HandlerInterceptor ` を使うことで実装が可能になる。

`HandlerInterceptor`をimplementしたクラスでは `preHandle ` `postHandle` ` afterCompletion` を実装する必要がある。
それぞれ、
- `preHandle` → Controllerにリクエストが行く前に呼ばれる
- `postHandle` →　ビューのレンダリング前に呼ばれる。RestControllerでは呼ばれない。
- `afterCompletion` → ビューのレンダリング後に呼ばれる。RestControllerでも呼ばれる。

となっている。

実装したクラスを作成し、`@Configuration` を付与し、全てのパッケージにおいて有効にしておく。

```java:Interceptor



/**
 * 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 {

    }
}


```



```java:Configuraiton


/**
 * 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)` としておく。

```java:Annotation

/**
 * 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が付与されてるかチェックしてあげればよい。


```java:Interceptor

    @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`が含まれていたら除外している。
多分ここはもっとマシな方法がある。

```java:Interceptor


    @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 `のインスタンスになるので、その場合も通してあげればよい。


```java:interceptor


    @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;
        }

　　　　//認証済みかチェックする処理
}
```