작지만 꾸준한 반복

HandlerInterceptor와 ArgumentResolver를 사용한 인증 개선기 - 2 본문

공부기록/Spring

HandlerInterceptor와 ArgumentResolver를 사용한 인증 개선기 - 2

iamjooon2 2023. 5. 8. 16:35

저번 포스트에서 HandlerInterceptor를 적용한 것까지 포스팅했습니다.

 

컨트롤러에서의 반복되는 인증 작업은 줄일 수 있었지만,

여전히 토큰에서 수출한 사용자 정보가 필요한 상황이었습니다

 

이는 ArgumentResolver와 커스텀 어노테이션을 이용하여 해결할 수 있었습니다!

 

먼저, 커스텀 어노테이션부터 한 번 확인해보곘습니다

@Target(ElementType.PARAMETER)
@Retention(RetentionPolicy.RUNTIME)
public @interface AuthPrincipal {
}

 

AuthPrincipl이라는 임의의 어노테이션을 만들어주었습니다.

 

@Target(ElementType.PARAMETER)를 통해 메서드의 파라미터에서 사용할 수 있도록 적용해주었고 

@Retention(RetentionPolicy.RUNTIME)을 통해 런타임까지 유지되도록 설정하였습니다.

실제 런타임시에 이 어노테이션 정보를 유지하여 프로그램에서 사용할 수 있도록 하였습니다.

 

코드를 먼저 보기 전에, ArgumetResolver에 대해 알아봅시다.

 

 

출저 김영한님 강의

Argument Resolver는 Dispatcher Servlet이 클라이언트로부터 요청을 받아 컨트롤러에 그 역할을 위임하기 전,

요청이나 응답과 관련된 매개변수를 처리하는 역할을 합니다.

 

스프링에서 이 ArgumentResolver 사용하기 위해 HandlerMethodArgumentResolver를 구현했습니다.

public class AuthArgumentResolver implements HandlerMethodArgumentResolver {

    private final AuthorizationExtractor<AuthDto> authorizationExtractor;

    public AuthArgumentResolver(final BasicAuthorizationExtractor authorizationExtractor) {
        this.authorizationExtractor = authorizationExtractor;
    }

    @Override
    public boolean supportsParameter(final MethodParameter parameter) {
        return parameter.getParameterType().equals(MemberDto.class) && parameter.hasParameterAnnotation(AuthPrincipal.class);
    }

    @Override
    public Object resolveArgument(
            final MethodParameter parameter,
            final ModelAndViewContainer mavContainer,
            final NativeWebRequest webRequest,
            final WebDataBinderFactory binderFactory
    ) {
        final HttpServletRequest request = (HttpServletRequest) webRequest.getNativeRequest();

        final AuthDto authDto = authorizationExtractor.extract(request);

        final String email = authDto.getEmail();
        final String password = authDto.getPassword();

        return new MemberDto(email, password);
    }
}

 

supportsParameter, resolveParameter의 두 메서드를 구현했습니다.

 

supportsParameter 메서드는 요청 받은 메서드의 인자에 원하는 어노테이션이 붙어있는지와, 원하는 파라미터 타입인지 확인합니다.

resolveArgument는 request로부터 받아온 argument를 반환하고 싶은 타입으로 입맛에 맞게 파싱해주도록 하였습니다.

 

드디어 이제! 대망의 컨트롤러 코드를 확인해봅시다.

 

@RestController
public class CartController {

    // 생성자 및 필드
    
    @GetMapping("/carts")
    public ResponseEntity<List<CartResponse>> getCarts(@AuthPrincipal final MemberDto memberDto) {
        final List<CartResponse> productResponses = cartService.find(memberDto);
        return ResponseEntity.ok(productResponses);
    }

    @PostMapping("/carts")
    public ResponseEntity<Void> addCart(@AuthPrincipal final MemberDto memberDto, @RequestBody @Valid CreateCartRequest createCartRequest) {
        final Long id = cartService.insert(createCartRequest.getProductId(), memberDto);
        return ResponseEntity.created(URI.create("/carts/" + id)).build();
    }
    
}

 

컨트롤러가 말끔해진 것을 볼 수 있다...!

 

이번 우테코 장바구니 미션을 진행하면서

HandlerInterceptor, ArgumentResolver를 사용하지 않고 인증 과정을 구현한 다음,

그 둘을 적용시키는 과정에서 중복되는 코드들이 많이 사라지는 것들을 볼 수 있었습니다.

 

선배 개발자들이 개발하면서 먼저 한 고민들에 대한 해결 방법들이

스프링 프레임워크에 적용되어 있다는 것을 느꼈습니다.

 

거대한 스프링.. 이제 좀 친해졌을지도...?

 

레퍼런스

https://tecoble.techcourse.co.kr/post/2021-05-24-spring-interceptor/

https://www.baeldung.com/spring-mvc-custom-data-binder