너디너리 해커톤에서 로그인을 구현했는데 빠르게 개발해야 하다보니 스프링 시큐리티를 사용하지 않고 개발했다. 그리고 클라이언트는 다음과 같이 access token을 헤더에 담아 요청해야 했다.
Authorization: Bearer {access token}
스프링 시큐리티를 사용하면 컨트롤러에서 인증 객체를 바로 받아올 수 있지만, 난 사용하지 않았기 때문에 일일히 헤더에 있는 토큰을 가져와서 검증하고 파싱해서 시크릿을 얻어야 한다.
매번 이 과정을 하는 게 귀찮으니 커스텀 어노테이션을 만들어서 사용했다.
그리고 스웨거에서는 제외 시키기 위해 @Parameter(hidden = true)
을 해주었다.
@PostMapping("/register")
public ResponseEntity<FoodRegisterResponse> registerFood(
@Parameter(hidden = true) @JwtValidation Long userId //@JwtValidation 사용
) {
...
}
@Target(ElementType.PARAMETER) //메서드의 파라미터에만 적용 가능
@Retention(RetentionPolicy.RUNTIME) //런타임에도 유지
@Documented //자바독에 포함
public @interface JwtValidation {
}
@Component
@RequiredArgsConstructor
public class JwtArgumentResolver implements HandlerMethodArgumentResolver {
private final JwtUtil jwtUtil;
//적용 여부 판단
@Override
public boolean supportsParameter(MethodParameter parameter) {
return parameter.hasParameterAnnotation(JwtValidation.class) &&
parameter.getParameterType().equals(Long.class);
}
//user id 추출
@Override
public Object resolveArgument(MethodParameter parameter,
ModelAndViewContainer mavContainer,
NativeWebRequest webRequest,
WebDataBinderFactory binderFactory) {
String authHeader = webRequest.getHeader("Authorization");
if (authHeader == null || !authHeader.startsWith("Bearer ")) {
throw new UserException(ErrorCode.INVALID_TOKEN);
}
String token = authHeader.substring(7);
if (!jwtUtil.validateToken(token)) {
throw new UserException(ErrorCode.INVALID_TOKEN);
}
return jwtUtil.getUserIdFromToken(token);
}
}
HandlerMethodArgumentResolver
인터페이스를 구현해 리졸버를 만들어준다.
HandlerMethodArgumentResolver
는 Spring MVC에서 컨트롤러 메서드의 파라미터에 값을 바인딩해주는 인터페이스이다.
스프링 내장 리졸버들
어노테이션 | 처리 클래스 |
---|---|
@RequestParam | RequestParamMethodArgumentResolver |
@RequestBody | RequestResponseBodyMethodProcessor |
@PathVariable | PathVariableMethodArgumentResolver |
@RequestHeader | RequestHeaderMethodArgumentResolver |
@Configuration
@RequiredArgsConstructor
public class WebConfig implements WebMvcConfigurer {
private final JwtArgumentResolver jwtArgumentResolver;
@Override
public void addArgumentResolvers(List<HandlerMethodArgumentResolver> resolvers) {
resolvers.add(jwtArgumentResolver);
}
}
이렇게 하면 컨트롤러 메서드 실행 직전 리졸버가 파라미터를 바인딩 해준다.
1. HTTP 요청 도착
2. DispatcherServlet이 컨트롤러 파라미터 조사
3. supportsParameter() → 어노테이션 붙었는지 확인
4. resolveArgument() → 토큰 검증 → userId 리턴
5. userId를 컨트롤러 메서드에 자동 주입