1 분 소요


헤더에 담긴 인증정보를 처리해 사용해야 하는 경우가 있습니다.
이 때 컨트롤러에서 @RequestHeader 어노테이션을 사용해 값을 전달받을 수 있습니다.

아래처럼 헤더의 session_id의 값을 읽어 user id를 가져와 응답하는 컨트롤러를 예로 사용해보겠습니다.

@GetMapping("/")
public Integer getUserId(@RequestHeader("session_id") String sessionId) {
  return sessionRepository.getUserIdBy(sessionId);
}

하나의 컨트롤러에서 이렇게 session_id를 이용해 user id를 가져와 사용할 때는 문제가 없어보입니다.
하지만 API에서는 인증정보를 이용하는 컨트롤러가 굉장히 많을 것입니다.

이럴 때마다 같은 로직을 반복해서 작성해야 할까요?
어노테이션을 직접 작성해 반복된 내용을 없애줄 수 있습니다.

@RequestHeader 어노테이션처럼 @SessionIdToUserId 어노테이션을 직접 만들어보겠습니다.

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

  String value() default "session_id";
}

이 어노테이션을 사용한 컨트롤러는 아래처럼 바뀝니다.

@GetMapping("/")
public Integer getUserId(@SessionIdToUserId Integer userId) {
  return userId;
}

반복된 로직을 제거할 수 있는 간단한 컨트롤러 메소드로 바꼈습니다.
하지만 이 어노테이션은 아직 아무 역할을 하지 않기 때문에 HandlerMethodArgumentResolver를 이용해 session_iduserId로 변경해줄 수 있도록 해야 합니다.

HandlerMethodArgumentResolver을 구현하는 클래스를 만들어줍니다.

@Component
public class SessionIdToUserIdResolver implements HandlerMethodArgumentResolver {

  private final SessionRepository sessionRepository;

  public SessionIdToUserIdResolver(SessionRepository sessionRepository) {
    this.sessionRepository = sessionRepository;
  }

  @Override
  public boolean supportsParameter(MethodParameter parameter) {
    return parameter.hasParameterAnnotation(SessionIdToUserId.class) 
      && Integer.class.equals(parameter.getGenericParameterType());
  }

  @Override
  public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer, NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception {
    SessionIdToUserId annotation = parameter.getParameterAnnotation(SessionIdToUserId.class);

    assert annotation != null;

    String sessionIdHeaderName = annotation.value();
    String sessionId = webRequest.getHeader(sessionIdHeaderName);

    return sessionRepository.getUserIdBy(sessionId);
  }
}

supportsParameter 메소드에 파라미터가 이 resolver를 지원하는지 정의해줍니다.
Integer의 파라미터에 이 어노테이션이 사용된 경우 true를 리턴합니다.

resolveArgument 메소드는 supportsParametertrue인 경우 이 어노테이션이 붙은 파라미터에 값을 넣을 수 있도록 구현합니다.
요청의 헤더에서 @SessionIdToUserIdvalue에 해당하는 헤더를 읽어 sessionId를 가져와
변경 전 컨트롤러의 로직처럼 sessionRepository를 주입받아 userId를 조회할 수 있도록 했습니다.

마지막으로 WebMvcConfigurer를 상속받은 클래스의 addArgumentResolver 메소드를 오버라이드해 이 resolver 클래스를 추가해줍니다.

@Configuration
public class WebMvcConfigurerImpl implements WebMvcConfigurer {

  private final SessionIdToUserIdResolver sessionIdToUserIdResolver;

  public WebMvcConfigurerImpl(SessionIdToUserIdResolver sessionIdToUserIdResolver) {
    this.sessionIdToUserIdResolver = sessionIdToUserIdResolver;
  }

  @Override
  public void addArgumentResolvers(List<HandlerMethodArgumentResolver> resolvers) {
    resolvers.add(sessionIdToUserIdResolver);
  }
}

@SessionIdToUserId 어노테이션을 사용한 변경 된 컨트롤러를 테스트 합니다.

@WebMvcTest(AfterController.class)
@DisplayName("@SessionIdToUserID 어노테이션")
class AfterControllerTest {

  @Autowired
  MockMvc mockMvc;

  @MockBean
  SessionRepository sessionRepository;

  @Test
  @DisplayName("MethodArgumentResolver 이용해 userId 획득")
  public void test() throws Exception {
    final String sessionId = "MTQ0NjJkZmQ5OTM2NDE1ZTZjNGZmZjI3";
    final Integer userId = 1001;

    given(sessionRepository.getUserIdBy(sessionId)).willReturn(userId);

    mockMvc
        .perform(
            MockMvcRequestBuilders
                .get("/")
                .header("session_id", sessionId)
        )
        .andExpect(MockMvcResultMatchers.content().string(String.valueOf(userId)));
  }
}

image


댓글남기기