[우아한테크코스 4기] 220715 F12 개발일지

Jihoon Oh·2022년 7월 16일
0

우아한테크코스 4기

목록 보기
23/43
post-thumbnail

오늘 진행한 일

인가 로직 구현

인증 로직이 완성되었으니 인가 로직을 구현하는데 집중했다. 인가 로직은 이미 레벨 2 장바구니 미션에서 인터셉터와 리졸버를 통해 구현하는 것을 배웠기 때문에 해당 부분을 코드에 적용하느라 어렵지 않았다. 하지만 인터셉터를 특정 URL이 아닌 특정 메서드에 적용시키는 방법을 적용시키느라 생각해 볼 부분이 있었다.

어노테이션으로 인터셉터 작동을 추가하기

레벨 2에서 배웠던 인터셉터를 추가하는 방식은 특정 URL에 대해 인터셉터가 작동하도록 하는 것이었다.

@Override
public void addInterceptors(final InterceptorRegistry registry) {
    registry.addInterceptor(loginInterceptor)
            .addPathPatterns("/api/**");
}

WebMvcConfigurer를 구현한 구현체를 만들고 addInterceptors 메서드를 오버라이딩해줄 때 addInterceptor로 넣는 구현체에 addPathPatterns로 인터셉터가 작동할 URL 패턴을 지정해 주는 방식이다. 하지만 이렇게 하면 소소한 문제가 있는데,

  1. 특정 헤더를 검사(인증/인가 처럼)해야 하는 인터셉터일 경우 preflight 요청에 대한 처리를 해주어야 함
  2. 같은 URL이면서 GET에서는 인터셉터가 작동하지 않고, POST에서는 작동해야 하는 등 HTTP 메서드에 따라 인터셉터의 동작 유무를 판단해야 할 때 분기문이 들어감

과 같은 문제들이 있다. 해결 방법이 없는 것은 아니지만 귀찮다. 때문에 컨트롤러의 지정한 메서드에서만 인터셉터가 작동할 수 있도록 할 수 있는 방법을 고민했었는데, 모라고라팀의 코드를 구경하다가 어노테이션을 기반으로 인터셉터의 작동을 설정할 수 있는 방법을 배우게 되었다.

@Component
public class AuthInterceptor implements HandlerInterceptor {

    private final JwtProvider jwtProvider;

    public AuthInterceptor(final JwtProvider jwtProvider) {
        this.jwtProvider = jwtProvider;
    }

    @Override
    public boolean preHandle(final HttpServletRequest request, final HttpServletResponse response, final Object handler)
            throws Exception {
        if (!(handler instanceof HandlerMethod)) {
            return true;
        }
        HandlerMethod handlerMethod = (HandlerMethod) handler;
        Auth auth = handlerMethod.getMethodAnnotation(Auth.class);
        if (Objects.isNull(auth)) {
            return true;
        }
        final String authorizationHeader = request.getHeader(HttpHeaders.AUTHORIZATION);
        if (!jwtProvider.validateToken(authorizationHeader)) {
            throw new UnAuthorizedException();
        }
        return true;
    }
}

인터셉터의 preHandle 메서드로는 handler가 매개변수로 넘어오게 되는데, 이 handlerHandlerMethod 타입이라면 형변환한 뒤 getMethodAnnotation으로 메서드에 붙은 어노테이션을 확인할 수 있다. 그래서 로그인이 필요하다는 것을 나타낸 @Auth 어노테이션을 만들고 해당 어노테이션이 붙어있으면 Authorization 헤더를 검증하도록 인터셉터를 작성했다. 이렇게 하면 특정 URL에 인터셉터를 넣어줄 필요 없이, 필요한 컨트롤러 메서드마다 @Auth 어노테이션을 붙여서 처리할 수 있다.

오늘 발생한 이슈

HandlerMethodArgumentResolver를 스프링 빈으로 만들 것인가

로그인 기능을 구현하고 나서 해야 할 일은 당연히 로그인한 회원의 정보를 가져오는 일이다. 그러려면 HTTP request에서 필요한 정보를 컨트롤러의 파라미터에 바인딩하는 리졸버가 필요하다.

기존에 이미 Pageable 객체를 바인딩하는 CustomPageableArgumentResolver 라는 리졸버를 만들어서 쓰고 있었는데, 이번에 로그인 이후 토큰에 들어있는 payload를 컨트롤러에 바인딩하기 위한 AuthArgumentResolve라는 리졸버를 새로 만들게 되었다.

그런데 기존에 존재하던 CustomPageableArgumentResolverWebConfig안에서 new 키워드로 만들어서 등록하고 있는데, AuthArgumentResolver는 스프링 빈으로 만들어준 다른 객체를 사용하느라 @Component를 붙여서 이미 빈으로 만들어 준 상태였다. 그래서 이 두 리졸버들을 스프링 빈으로 만들 것인지, 그냥 WebConfigaddArgumentResolvers 메서드 안에서 만들어서 쓸 것인지에 대해 고민했다.

만약 빈으로 등록하는 것 만으로 리졸버로 등록된다면 가장 좋았겠지만, 아쉽게도 그렇게 되지는 않았다. 여기에 대해서 네오가 WebMvcConfigurer를 오버라이딩한 WebConfig를 만들지 않았다면 우리가 기대한대로 됐을거라는 이야기를 해 주셨는데, 아직 왜 그런건지는 정확히 모르겠다.

어쨌든 계속 어떤 방법이 좋을지 논의가 오고 갔는데, 결국 핵심은 여러 개의 인터셉터와 리졸버가 있을 때 귀찮지 않으면서 각각의 객체마다 통일성 있는 방법으로 등록하자 였다. 그 관점에서 생각하다 보니 갑자기 좋은 생각이 떠올랐다. 전부다 빈으로 만든 뒤에 리스트로 주입 받으면 되는거였다!

스프링 빈은 인터페이스 타입으로 주입받을 때, 해당 인터페이스의 구현체가 여러개라면 필드 이름으로 구분하거나 @Primary, @Qualifier 등으로 구분해서 주입받아야 한다. 하지만 리스트 타입으로 주입받으면 그런 것에 상관 없이 구현체를 전부 주입받을 수 있다. 그래서 인터셉터와 리졸버들을 인터페이스 리스트인 List<HandlerInterceptor>, List<HandlerMethodArgumentResolver>로 의존성 주입받은 뒤 받아온 리스트 자체를 등록해주었다.

@Configuration
public class WebConfig implementsWebMvcConfigurer{

    private static final StringCORS_ALLOWED_METHODS= "GET,POST,HEAD,PUT,PATCH,DELETE,TRACE,OPTIONS";

    private finalList<HandlerInterceptor> interceptors;
    private finalList<HandlerMethodArgumentResolver> resolvers;

    public WebConfig(List<HandlerInterceptor>interceptors,List<HandlerMethodArgumentResolver>resolvers) {
this.interceptors =interceptors;
this.resolvers =resolvers;
    }

    @Override
    public void addInterceptors(final InterceptorRegistryregistry) {
        interceptors.forEach(registry::addInterceptor);
    }

    @Override
    public void addCorsMappings(final CorsRegistryregistry) {
registry.addMapping("/api/**")
                .allowedMethods(CORS_ALLOWED_METHODS.split(","))
                .exposedHeaders(HttpHeaders.LOCATION);
    }

    @Override
    public void addArgumentResolvers(finalList<HandlerMethodArgumentResolver>resolvers) {
resolvers.addAll(this.resolvers);
    }
}

내일 목표

주말은 휴식이다.

profile
Backend Developeer

0개의 댓글