개발 기록
> [Spring] Request Value 조작 - 1. HandlerMethodArgumentResolver 를 직접 구현해보자! 본문
> [Spring] Request Value 조작 - 1. HandlerMethodArgumentResolver 를 직접 구현해보자!
1z 2023. 12. 7. 17:27
환경
java 17
spring version 6.0.11
spring boot version 3.1.3
■
1. 개념
HandlerMethodArgumentResolver 인터페이스의 역할은 Controller로 들어온 파라미터를 가공하거나 수정, 바인딩 하는 역할이다.
▶ Interceptor vs Filter vs Resolver Argument vs AOP "◀
ArgumentResolver 에서 처리하는 중복 코드는 interceptor, AOP, Filter 로도 구현이 가능하지만, 각 호출 되는 시점과 return type 이 다르기 때문에 해당로직을 어디 구간에서 실행할지 고민해봐야 한다.
1. Fitler : filter 는 DispatcherServlet 요청이 있기 전 호출 (ex. token 검사)
2. Interceptor : 클라이언트가 요청한 API 를 찾은 뒤 호출 (ex. role 검사)
3. Resolver Argument : interceptor 요청 뒤 호출
4. AOP : pointcut 으로 호출 지정 (어노테이션, 메서드, 경로)
▶ 사용 예시 ◀
ex. 클라이언트로부터 암호화된 데이터를 받을 때 복호화 하는 작업을 AgumentResolver 를 이용하여 처리하고 실제 사용할 수 있는 값은 Controller 로 넘겨 줄 수 있다.
■
2. 실습
목표
1. Ip, 브라우저 정보, 회원 정보를 UserDTO 담아 Controller 로 넘겨주기
2. 인증로직 => heaer 에서 토큰을 가져와 회원 ID 만 Controller 로 넘겨주기
(1) WebConfig
: addArgument 함수를 재정의하여 arguementResolver 리스트에 사용자 정의 ArgumentResovler 을 추가한다.
@Configuration
@RequiredArgsConstructor
public class WebConfig implements WebMvcConfigurer {
private final AuthCustomArgumentResolver authCustomArgumentResolver;
@Override
public void addArgumentResolvers(List<HandlerMethodArgumentResolver> resolvers) {
resolvers.add(authCustomArgumentResolver);
}
}
(2) Annotataion
: 필수적인 것은 아니지만, 기본적으로 같이 많이 사용한다. 특정한 부분에서만 자동으로 객체가 Argument Resolver를 통해 바인딩되도록 만들고 싶은 경우 커스텀 어노테이션을 사용하여 해결한다.
// 파라미터에 해당 애노테이션 사용
@Target(ElementType.PARAMETER)
// 라이프 사이프 설정 : 런타임까지 애노테이션 메타 정보 보유(리플렉션 등을 활용할 수 있다.)
@Retention(RetentionPolicy.RUNTIME)
public @interface AuthUser {
}
(3) HandlerMethodArgumentResolver 구현체 작성
1. supportsParameter() : Argument Resolver 적용 대상을 제한하고 resolveArgument() 실행여부를 판단한다.
ⓛ getParameterType() :
: Controller method parameter type 이 UserDTO 타입인지 확인한다.
② hasParameterAnnotaion()
: 파라미터의 어노테이션으로 Argument Resolver 적용 대상을 제한한다.
2. resolveArgument() : 반복되는 로직, 바인딩 시 필요한 로직등을 넣어준다. 그리고 최종적으로 UserDto 를 생성해서 반환한다.
ⓛ supportsParameter() return 값이 true 일 때 실행된다.
/** HandlerMethodArgumentResolver
* 컨트롤러 메서드에서 특정 조건에 맞는 파라미터가 있을 때 요청에 들어온 값을 이용해 원하는 값을 바인딩 한다.
* **/
@Slf4j
public class AuthCustomArgumentResolver implements HandlerMethodArgumentResolver {
/** 호출되는 Controller의 파라미터 값을 검사하는 콜백 함수
* supportsParameter: 들어온 파라미터에 대해 resolveArgument 메서드 실행여부 판단
* ex. 리턴 값이 true => resolveArgument 메서드 실행
*/
@Override
public boolean supportsParameter(MethodParameter parameter) {
return
// getParameterType() : 컨트롤러 메소드의 파라미터가 UserDTO 타입인지 검사
parameter.getParameterType().equals(UserDTO.class) ||
// hasParameterAnnotation() : 컨트롤러 메소드의 파라미터가 @AuthUser 어노테이션을 가지고 있는지 확인한다.
parameter.hasParameterAnnotation(AuthUser.class);
}
/* 파라미터를 가공하는 역할: 반환값이 대상이 되는 메소드의 파라미터에 바인딩 */
@Override
public Object resolveArgument(
MethodParameter parameter,
ModelAndViewContainer mavContainer,
NativeWebRequest webRequest,
WebDataBinderFactory binderFactory) throws Exception {
HttpServletRequest request = (HttpServletRequest)webRequest.getNativeRequest();
// RequestParameter value(=name) 로 사용자 PK 를 조회하여 바인딩한다.
String name = (String) webRequest.getAttribute("name", WebRequest.SCOPE_REQUEST);
// TODO select * from tbl_user where name = :name
// 예제 2
if(parameter.hasParameterAnnotation(AuthUser.class)){
String authorization = request.getHeader("Authorization");
// .. 인증 로직 진행
return 1; // 회원 ID return
}
// 예제 1
return new RequestInfo(1L, getClientIP(request), request.getHeader("User-Agent"));
}
public static String getClientIP(HttpServletRequest request) {
String ip = request.getHeader("X-Forwarded-For");
if (ip == null) {
ip = request.getHeader("Proxy-Client-IP");
}
if (ip == null) {
ip = request.getHeader("WL-Proxy-Client-IP");
}
if (ip == null) {
ip = request.getHeader("HTTP_CLIENT_IP");
}
if (ip == null) {
ip = request.getHeader("HTTP_X_FORWARDED_FOR");
}
if (ip == null) {
ip = request.getRemoteAddr();
}
return ip;
}
}
(3) Controller - 파라미터 적용
▶예제 1. 어노테이션
파리미터에 @AuthUser 존재를 인식하고 인증처리를 위한 ArgumentResolver가 실행된다.
※ Spring Security vs ArgumentResolver
Spring Security 도 ArgumentResolver 메카니즘 등을 이용한 인증관련 통합 라이브러리라서 Spring security 를 사용하여 인증로직을 처리해도 되고, 간단한 인증은 Spring Filter(or Interceptor), ArgumentResolver같은 객체를 이용해서 직접 구현해도 된다. 당연히 둘다 사용해도 된다.
▶ 예제 2. Class Type
파리미터 타입이 UserDTO 인 것을 확인하고 요청정보(ip 등) 을 추가해주기 위한 ArgumentResolver가 실행된다.
public class ArgumentTestController {
// 매핑 일치 기준 : 어노테이션
@GetMapping("/all")
public void findItems(@AuthUser int requestId) {
System.out.println(requestId);
}
// 매핑 일치 기준 : Class Ttype
@GetMapping("/all")
public void findItems(UserDTO request) {
System.out.println(request.toString());
}
}
'Spring' 카테고리의 다른 글
| > [Spring] Multi Thread - 선착순 티켓 예매로 알아보는 동시성 이슈 2. Pessimistic Lock, Optimisitc Lock, Named Lock (0) | 2023.12.15 |
|---|---|
| > [Spring] Multi Thread - 선착순 티켓 예매로 알아보는 동시성 이슈 1. 동시성 이슈 발견, Synchronized 로 해결 (0) | 2023.12.15 |
| > [Spring] Bean 생명 주기에 사용자 정의 작업 연결 - @PostConstruct, @PreDestory (0) | 2023.12.01 |
| > [Spring Security] - Filter Chain 생성과 호출 (0) | 2023.11.13 |
| > [Spring Batch] Job, Step 속성 (0) | 2022.10.28 |