관리 메뉴

bright jazz music

blog 27 : Interceptor 사용해 보기 본문

Projects/blog

blog 27 : Interceptor 사용해 보기

bright jazz music 2023. 2. 9. 12:34

blog 26 포스팅에서 사용했던 get parameter, 또는 헤더 인증의 경우,

간단하게 값을 확인해서 if문으로 비교하여 api를 허용/거부처리 해줄 수 있었다.

 

0. 문제

그러나 이 방법의 경우 메소드가 추가될 때마다 동일한 작업을 반복하여 작성해야 한다는 단점이 있다. 매번 값을 꺼내고 확인하고, 인증값이 변경될 경우 모든 컨트롤러 메서드마다 변경해줘야 한다. 한 군데에서 한 번의 작업으로 전역적이고 반복적인 작업을 처리할 수 있는 방법이 필요하다.

 

1. 테스트 할 controller 생성하기

인터셉터는 요청이 컨트롤러 레이어에 진입하기 전에 공통된 작업을 해 준다. 인터셉터를 생성하기 전에 인터셉터를 테스트 할 핸들러를 생성하자.

 

//postController.java
@GetMapping("/test")
public String test(){ //인터셉터 테스트용
    return "hello";
}

 

2. Interceptor 생성하고 로그 남겨보기

 

//AuthInterceptor.java

package com.endofma.blog.config;

import lombok.extern.slf4j.Slf4j;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

@Slf4j
public class AuthInterceptor implements HandlerInterceptor {
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        //prehandle : handler 가기 전에 작업
        log.info(">> preHandle");
        return true;
        //만약 return false인 경우 컨트롤러까지 가지 않고 여기서 끝난다.
    }

    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
        //postHandle : view반환 직후
        log.info(">> postHandle");
    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        //afterCompletion : view반환 이후 모든 처리가 끝난 후
        log.info(">> afterCompletion");
    }
}

 

 

이후 재시작. 그러나 재시작해도 로그는 찍히지 않는다. WebMvcConfigurer에 생성한 인터셉터를 등록하지 않았기 때문이다.

 

3. WebMvcConfigurer를 구현한 클래스 생성 

내 경우는 이미 존재했던 WebConfig파일을 WebMvcConfig로 이름만 변경해 주었다.

 

WebMvcConfigurer를 구현하게 한다. 그리고 클래스에 @Configuration 어노테이션을 붙여준다.

//WebMvcConfig.java

package com.endofma.blog.config;

import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.CorsRegistry;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@Configuration
public class WebMvcConfig implements WebMvcConfigurer {

    //인터셉터 추가
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(new AuthInterceptor()); //생성한 인터셉터 등록
//                .addPathPatterns() 적용할 패턴
//                .excludePathPatterns() 제외할 패턴
    }

    //서버에서 CORS문제를 해결하는 방법
    //frontend에서 Cors문제를 해결하는 실습을 위해 주석처리
//
//    @Override
//    public void addCorsMappings(CorsRegistry registry) {
//        registry.addMapping("/**").allowedOrigins("http://localhost:5173");
//    }
}

.addPathPatterns()는 해당 인터셉터에 적용한 작업을 적용할 패턴을 적어준다.

.excludePathPattern()은 제외할 패턴을 적어준다.

 

지금 처럼 패턴을 적용해 주지 않고 등록만 해줄 경우 모든 핸들러에 인터셉터의 작업을 적용한다.

 

 

새로 고침
로그 찍힘

 

4. preHandle() 변경 : 인증 로직 추가

//AuthInterceptor.java

package com.endofma.blog.config;

import lombok.extern.slf4j.Slf4j;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

@Slf4j
public class AuthInterceptor implements HandlerInterceptor {
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        //prehandle : handler 가기 전에 작업
        log.info(">> preHandle");

        //accessToken값이 존재하고 catnails라면 true를 반환하여 접근 가능처리. 아니면 false 반환하여 거부
        String accessToken = request.getParameter("accessToken");
        if (accessToken != null && accessToken.equals("catnails")) {
            return true;
        }
        return false;
//        return accessToken.equals("catnails"); 이렇게 간소화 가능

    }

    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
        //postHandle : handler 진입 이후
        log.info(">> postHandle");
    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        //afterCompletion : view까지 처리가 끝난 후에 처리
        log.info(">> afterCompletion");
    }
}

 

parameter인 accessToken에 catnails와 일치하는 값을 넣어주면 접근 가능

 

 

parameter가 없기 때문에 접근 불가

 

parameter인 accessToken 값이 다르기 때문에 (catnails가 아니기때문에 접근 불가)

 

 

 

5. 인증 실패 및 인증이 필요하다는 예외를 json으로 반환해 주기

인증되지 않았다는 상태 코드 (401 unauthorized) 를 json으로 내려주기

 

5.1 Unauthorized 클래스 생성 후 내용 작성

 

Unauthorized 클래스 생성

 

Unauthorized.java 내용 작성

package com.endofma.blog.exception;

/**
 * status > 401
 */
public class Unauthorized extends BlogException {
    private static final String MESSAGE ="인증이 필요합니다.";

    public Unauthorized() {
        super(MESSAGE);
    }

    @Override
    public int getStatusCode() {
        return 401;
    }

}

 

참고

//BlogException.java

package com.endofma.blog.exception;

import lombok.Getter;

import java.util.HashMap;
import java.util.Map;

@Getter
public abstract class BlogException extends RuntimeException{

    //validation값 추가 위해 작성
    public final Map<String, String> validation = new HashMap<>();

    public BlogException(String message){
        super(message);
    }

    public BlogException(String message, Throwable cause){
        super(message, cause);
    }

    //추상메서드: 반드시 구현 필요
    public abstract int getStatusCode();

    public void addValidation(String fieldName, String message){
        this.validation.put(fieldName, message);

    }

}

 

 

5.2 AuthInterceptor 변경. Unathorized 객체를 false 대신 반환하도록 코드 수정

//AuthInterceptor.java

package com.endofma.blog.config;

import com.endofma.blog.exception.Unauthorized;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

@Slf4j
public class AuthInterceptor implements HandlerInterceptor {
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        //prehandle : handler 가기 전에 작업
        log.info(">> preHandle");

        //accessToken값이 존재하고 catnails라면 true를 반환하여 접근 가능처리. 아니면 false 반환하여 거부
        String accessToken = request.getParameter("accessToken");
        if (accessToken != null && accessToken.equals("catnails")) {
            return true;
        }
        throw new Unauthorized(); // 제이슨 반환
//        return false;
//        return accessToken.equals("catnails"); 이렇게 간소화 가능
    }

    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
        //postHandle : handler 진입 이후
        log.info(">> postHandle");
    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        //afterCompletion : view까지 처리가 끝난 후에 처리
        log.info(">> afterCompletion");
    }
}

 

 

 

 

6. 특정 API path를 인증에서 제외하기

.excludePathPattern() 적용

//PostController.java

@GetMapping("/foo")
public String foo(){
    return "foo";
}

이 API path를 인터셉터에서의 작업에서 제외하고 싶은 경우 WebMvcConfigurer를 수정해 준다.

.excludePathPatterns()에 제외할 경로를 추가해 준다.

 

//WebMvcConfig.java

package com.endofma.blog.config;

import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.CorsRegistry;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@Configuration
public class WebMvcConfig implements WebMvcConfigurer {

    //인터셉터 추가
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(new AuthInterceptor()) //생성한 인터셉터 등록
//                .addPathPatterns() //인증 적용할 패턴
                .excludePathPatterns("/foo"); //인증 제외할 패턴
    }

}

 

인터셉터에서 제외되었다.

이 설정은 설정한 패턴을아예 인터셉터의 작동에서 제외한다. 따라서 preHandle()의 ">>preHandle" 로그조차 남지 않는다. 인터셉터가 작동하지 않았기 때문이다. 

 

 

 

 

이렇듯 컨트롤러 라우터마다 if문으로 처리하던 로직을 인터셉터에 적용해 줌으로써,  컨트롤러에 접근하기 전에 처리하도록 할 수 있다.

 

Comments