관리 메뉴

bright jazz music

blog 28 : ArgumentResolver 사용해보기 본문

Projects/blog

blog 28 : ArgumentResolver 사용해보기

bright jazz music 2023. 2. 10. 07:52

AurgumentResolver:

 

preHandle에서 http 요청의 헤더를 검사한다.

request parameter에 key값이 "accessToken"인 값이 있으면 해당 값을 요청에 setAttribute 해준다.

이 때 key 값은 임의로 정해주어도 된다. 여기서는 "userName"을 key로, accessToken을 value로 넣어줬다.

여기서는 string 값을 넣어줬지만 object를 넘길 수도 있다.

 

request.setAttribute("userName", accessToken); <==멀티스레드 환경에서의 동작을 알아볼 필요가 있다.

 

 

 

 

 

//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값이 존재하고 ""이 아니라면 true를 반환하여 접근 가능처리. 아니면 false 반환하여 거부
        String accessToken = request.getParameter("accessToken");
        if (accessToken != null && !accessToken.equals("")) {
            request.setAttribute("userName", accessToken);
            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");
    }
}

 

//postController.java
@GetMapping("/foo")
public String foo(@RequestAttribute("userName") String userName ){
    log.info(">>> {}", userName);
    return "foo";
}

 

 

2. ArgumentResolver 생성하여 개선해보기

위와 같은 방식을 사용할 수도 있지만

ArgumentResolver를 사용해서 Controller의 파라마미터에서 임의로 만든 dto에 받아줄 수도 있다.

여기서는 AuthResolver라는 이름으로 생성했으며, 이 리졸버는 모든 요청에 대해 실행될 것이다.

 

AuthResolver 생성

//AuthResolver.java

package com.endofma.blog.config;

import org.springframework.core.MethodParameter;
import org.springframework.web.bind.support.WebDataBinderFactory;
import org.springframework.web.context.request.NativeWebRequest;
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
import org.springframework.web.method.support.ModelAndViewContainer;

public class AuthResolver implements HandlerMethodArgumentResolver {
    @Override
    public boolean supportsParameter(MethodParameter parameter) { 
        //컨트롤러에서 사용할 어노테이션이나 DTO가 사용자가 사용하려는 것이 맞는지, 지원하는지 체크한다.
        return false;
    }

    @Override
    public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer, NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception {
        //실제로 해당 DTO에 값을 세팅해준다.
        return null;
    }
}

 

 

3. ArgumentResolver 실습을 위해 DTO클래스를 생성하고 Controller 코드 변경

 

3.1 DTO 클래스 생성

UserSession 생성

//UserSession.java

package com.endofma.blog.config.data;

public class UserSession {

    public String name;
}

UserSession 생성하기. 이 DTO는 PostController에서 argumentResolver를 실습하기 위해 사용한다.

 

 

3.2 컨트롤러 수정

//postController.java
@GetMapping("/foo")
public String foo(UserSession userSession){
    log.info(">>> {}", userSession.name);
    return "foo";
}

 

 

4. ArgumentResolver 내용 작성 01

우선은 사용하려는 클래스가 맞는지 확인하는 코드만 작성한다.

//AuthResolver.java

package com.endofma.blog.config;

import com.endofma.blog.config.data.UserSession;
import org.springframework.core.MethodParameter;
import org.springframework.web.bind.support.WebDataBinderFactory;
import org.springframework.web.context.request.NativeWebRequest;
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
import org.springframework.web.method.support.ModelAndViewContainer;

public class AuthResolver implements HandlerMethodArgumentResolver {
    @Override
    public boolean supportsParameter(MethodParameter parameter) {
        //컨트롤러에서 사용할 어노테이션이나 DTO가 사용자가 사용하려는 것이 맞는지, 지원하는지 체크한다.
        return parameter.getParameterType().equals(UserSession.class); //UserSession 클래스를 사용하는 것이 맞는지 확인. 맞으면 컨트롤러에 값 할당

    }

    @Override
    public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer, NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception {
        //실제로 해당 DTO에 값을 세팅해준다. 추구 내용 작성 예정
        return null;
    }
}

 

5. WebMvcConfigurer에 ArgumentResolver 등록

//WebMvcConfig.java

package com.endofma.blog.config;

import org.springframework.context.annotation.Configuration;
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

import java.util.List;

@Configuration
public class WebMvcConfig implements WebMvcConfigurer {

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

    @Override //인터셉터를 추가해줬듯이 아규먼트 리졸버를 추가해준다.
    public void addArgumentResolvers(List<HandlerMethodArgumentResolver> resolvers) {
        resolvers.add(new AuthResolver());
    }

}

 

 

AuthResolver.java의 

return parameter.getParameterType().equals(UserSession.class);  코드에 Break Point를 걸고

브라우저로 접근하면
위와 같이 파라미터 타입을 확인할 수 있다. 타입이 맞으므로 True를 반환한다.

 

현재는 userSession.name에 아무 값이 들어있지 않기 때문에 NullPointerException이 발생한다.

 

6. ArgumentResolver 내용 작성 02

resolveArgument() 메서드의 내용을 작성해준다.

아래에서는 userSession의 name에 catnails를 넣어줬다. 따라서 컨트롤러에서 catnails로그가 남아야 한다.

//AuthResolver.java

package com.endofma.blog.config;

import com.endofma.blog.config.data.UserSession;
import org.springframework.core.MethodParameter;
import org.springframework.web.bind.support.WebDataBinderFactory;
import org.springframework.web.context.request.NativeWebRequest;
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
import org.springframework.web.method.support.ModelAndViewContainer;

public class AuthResolver implements HandlerMethodArgumentResolver {
    @Override
    public boolean supportsParameter(MethodParameter parameter) {
        //컨트롤러에서 사용할 어노테이션이나 DTO가 사용자가 사용하려는 것이 맞는지, 지원하는지 체크한다.
        return parameter.getParameterType().equals(UserSession.class); //UserSession 클래스를 사용하는 것이 맞는지 확인. 맞으면 컨트롤러에 값 할당

    }

    @Override
    public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer, NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception {
        //실제로 해당 DTO에 값을 세팅해준다.
        UserSession userSession = new UserSession();
        userSession.name = "catnails";
        return userSession;
    }
}
먼저 인터셉터에서 걸리고, 그다음 아규먼트 리졸버, -==>controller

 

7. Interceptor 제거

API를 만들다 보면 인증이 필요한 라우터와 필요 없는 라우터가 갈리게 된다. 프로젝트가 커지면서 그 양은 더욱 많아진다. 따라서 일일이 인터셉터에 패턴 추가, 제외 해주는 작업은 번거로워진다. 

 

우리가 만약 위에서 만든 UserSession 클래스처럼, 해당 객체가 포함된 API는 반드시 인증이 필요한 API라는 것을 알 수 있다. 왜냐하면 인증이 필요 없는데 굳이 인증이 필요한 객체를 파라미터에 사용할 필요는 없기 때문이다. 

 

이러한 논리를 바탕으로 내가 생성한 AuthInterceptor 인터셉터를 WebMvcConfig에서 주석처리 할 것이다. 또한 인증처리를 ArgumentResolver에서 처리할 것이다.

 

//WebMvcConfig.java

package com.endofma.blog.config;

import org.springframework.context.annotation.Configuration;
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

import java.util.List;

@Configuration
public class WebMvcConfig implements WebMvcConfigurer {

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

    @Override //인터셉터를 추가해줬듯이 아규먼트 리졸버를 추가해준다.
    public void addArgumentResolvers(List<HandlerMethodArgumentResolver> resolvers) {
        resolvers.add(new AuthResolver());
    }

}

 

 

8. ArgumentResolver에 인증 로직 추가

//AuthResolver.java

package com.endofma.blog.config;

import com.endofma.blog.config.data.UserSession;
import com.endofma.blog.exception.Unauthorized;
import org.springframework.core.MethodParameter;
import org.springframework.web.bind.support.WebDataBinderFactory;
import org.springframework.web.context.request.NativeWebRequest;
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
import org.springframework.web.method.support.ModelAndViewContainer;

public class AuthResolver implements HandlerMethodArgumentResolver {
    @Override
    public boolean supportsParameter(MethodParameter parameter) {
        //컨트롤러에서 사용할 어노테이션이나 DTO가 사용자가 사용하려는 것이 맞는지, 지원하는지 체크한다.
        return parameter.getParameterType().equals(UserSession.class); //UserSession 클래스를 사용하는 것이 맞는지 확인. 맞으면 컨트롤러에 값 할당

    }

    @Override
    public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer, NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception {
        //실제로 해당 DTO에 값을 세팅해준다.

        //인증 작업 추가
        String accessToken = webRequest.getParameter("accessToken");
        if (accessToken == null || accessToken.equals("")) {
            throw new Unauthorized();
        }

        UserSession userSession = new UserSession();
//        userSession.name = "catnails";
        userSession.name = accessToken;
        return userSession;
    }
}

 

현재는 값이 존재하는지 여부만 체크한다. 값이 존재하고 공백이 아니라면 무조건 통과. 값이 존재하지 않거나 있더라도 공백이라면 예외 발생

 

@GetMapping("/foo")
public String foo(UserSession userSession){
    log.info(">>> {}", userSession.name);
    return userSession.name;
}

 

accessToken 이 있는 존재하는 경우

 

존재하지 않는 경우 unauthorized()

 

이렇듯 파라미터 값에 UserSession 객체가 존재하면 아규먼트 리졸버의 인증 기능이 작동한다. 이는 supportParameter()에서 userSession.class가 파라미터에 존재하는지 확인하기 때문이다. 존재한다면 resolveArgument() 메서드에서 인증 기능을 수행한다.

 

@GetMapping("/bar")
public String bar(){
    return "인증이 필요 없는 페이지";
}

만약 위와 같은 api가 있다면 accessToken 값을 넣어 요청하지 않아도 예외가 발생하지 않는다. "인증이 필요 없는 페이지"라는 문자열을 브라우저 창에서 볼 수 있을 것이다. 해당 api의 파라미터에 UserSession이 없기 때문이다.

 

인터셉터는 실무에서 생각보다 많이 쓰이지는 않는 듯하다.

Comments