🌏 WEB/Spring

Interceptor ν™œμš©ν•˜κΈ° ( feat . ArgumentResolver, Custom Annotation )

μ• μ •μ“° 2022. 3. 25. 14:52

 

λ™κΈ°λΆ„μ˜ λ„μ›€μœΌλ‘œ 처음으둜 Interceptor λ₯Ό μ μš©ν•΄λ³΄μ•„μ„œ λ‚˜μ€‘μ— μ‚¬μš©ν•  일이 μžˆμ„ λ•Œ 찾아보렀고 μ¨λ΄…λ‹ˆλ‹€!

μ•„λž˜μ™€ 같은 Controller 에 Header μ—μ„œ 인증을 μœ„ν•œ 값을 λ°›μ•„μ•Όμ§€λ§Œ μ ‘κ·Όν•  수 μžˆλ„λ‘ μ„€μ •ν•˜κ² μŠ΅λ‹ˆλ‹€!

 

@RestController
@RequestMapping("/orders")
@RequiredArgsConstructor
public class OrdersController {

  private final OrdersService ordersService;


  @Auth(type = ApiServiceType.HOMEPAGE)
  @GetMapping
  public ResponseEntity<List<OrdersDto>> getOrders(
      @RequestParam(required = false) String searchKeyword,
      @AuthAccount Long accountId
  ) {
    List<OrdersDto> response = ordersService.getAccessibleOrders(accountId,
        searchKeyword);

    return new ResponseEntity<>(response, HttpStatus.OK);
  }
}

보톡은 μ•„λž˜μ™€ 같이 경둜λ₯Ό μΆ”κ°€ν•΄μ„œ interceptorλ₯Ό κ±Έμ–΄μ£ΌλŠ”λ° μ»€μŠ€ν…€ μ–΄λ…Έν…Œμ΄μ…˜μœΌλ‘œ ν™œμš©ν• μˆ˜λ„ μžˆλ”λΌκ΅¬μš”! ( 정말 생각도 λͺ»ν–ˆλ‹€,,)

registry.addInterceptor(interceptor)
    .addPathPatterns("/**")
    .excludePathPatterns("/main");

 

https://www.baeldung.com/java-custom-annotation

μ•„λž˜μ™€ 같이 μ»€μŠ€ν…€ μ–΄λ…Έν…Œμ΄μ…˜μ„ λ§Œλ“€μ–΄ μ€λ‹ˆλ‹€.

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface Auth {

  ApiServiceType type();
}


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

}

@Retention 은 λ§κ·ΈλŒ€λ‘œ 이 μ–΄λ…Έν…Œμ΄μ…˜μ΄ μ–Έμ œκΉŒμ§€ μœ μ§€ν• μ§€λ₯Ό μ •ν•©λ‹ˆλ‹€. 

- RUNTIME λŸ°νƒ€μž„ κΉŒμ§€ μœ μ§€ν•œλ‹€. = 사싀상 사라지지 μ•ŠλŠ”λ‹€. (λ©”λͺ¨λ¦¬μ— 같이 올라감)

- SOURCE μ†ŒμŠ€μ½”λ“œ κΉŒμ§€ μœ μ§€ν•œλ‹€. =  .java μ†ŒμŠ€κΉŒμ§€ μœ μ§€ν•œλ‹€ (μ»΄νŒŒμΌλ˜μ–΄ 클래슀 파일이 되면 사라진닀) 

- CLASS 클래슀파일 κΉŒμ§€ μœ μ§€ν•œλ‹€. = μ»΄νŒŒμΌν•œ class νŒŒμΌκΉŒμ§€ μœ μ§€ν•œλ‹€ (λ©”λͺ¨λ¦¬λ‘œ μ½μ–΄μ˜€λ©΄ 사라짐)

 

사싀 μ–΄λ–€μ‹μœΌλ‘œ μ‚¬μš©ν•΄μ•Όν• μ§€ 감이 μ•ˆμ˜¨λ‹€ ... λͺ‡κ°€μ§€ μ°Ύμ•„λ³Έκ²Œ μžˆλ‹€λ©΄

 

μŠ€ν”„λ§μ˜ λŒ€λΆ€λΆ„μ˜ μ–΄λ…Έν…Œμ΄μ…˜μ€  RUNTIME 으둜 λ˜μ–΄μžˆλ‹€κ³  ν•œλ‹€.

μ™œλƒν•˜λ©΄ 싀행쀑인 μƒνƒœμ—μ„œ μŠ€ν”„λ§ μ»΄ν¬λ„ŒνŠΈ μŠ€μΊ”μ΄ μŠ€μΊ”μ„ ν•΄μ•Όν•˜κΈ° λ•Œλ¬Έμ—!

@getter 같은 κ²½μš°λŠ” 둬볡이 μ½”λ“œλ₯Ό μƒμ„±ν•΄μ£ΌλŠ” κ±°λ‹ˆκΉŒ λ°”μ΄νŠΈμ½”λ“œμ— μ†ŒμŠ€κ°€ λ“€μ–΄κ°ˆ ν•„μš”κ°€ μ—†μ–΄μ„œ SOURCE 둜 λ˜μ–΄μžˆλ‹€.

Maven μ΄λ‚˜ Gradle μ—μ„œ 받은 라이브러리 듀은 class 파일둜 있기 λ•Œλ¬Έμ— μ–΄λ…Έν…Œμ΄μ…˜μ˜ 정보λ₯Ό 정보λ₯Ό μ‘°νšŒν•˜κΈ° μœ„ν•΄ CLASS 둜 λ˜μ–΄μžˆλ‹€.

 

@Target 은 μžλ°” μ»΄νŒŒμΌλŸ¬κ°€ annotation이 어디에 μ μš©λ μ§€ κ²°μ •ν•˜κΈ° μœ„ν•΄ μ‚¬μš©ν•©λ‹ˆλ‹€! 

ElementType.PACKAGE : νŒ¨ν‚€μ§€ μ„ μ–Έ
ElementType.TYPE : νƒ€μž… μ„ μ–Έ
ElementType.ANNOTATION_TYPE : μ–΄λ…Έν…Œμ΄μ…˜ νƒ€μž… μ„ μ–Έ
ElementType.CONSTRUCTOR : μƒμ„±μž μ„ μ–Έ
ElementType.FIELD : 멀버 λ³€μˆ˜ μ„ μ–Έ
ElementType.LOCAL_VARIABLE : 지역 λ³€μˆ˜ μ„ μ–Έ
ElementType.METHOD : λ©”μ„œλ“œ μ„ μ–Έ
ElementType.PARAMETER : μ „λ‹¬μΈμž μ„ μ–Έ
ElementType.TYPE_PARAMETER : μ „λ‹¬μΈμž νƒ€μž… μ„ μ–Έ
ElementType.TYPE_USE : νƒ€μž… μ„ μ–Έ

 

 

κ³΅μ‹λ¬Έμ„œ! μ—μ„œ 보면 array 둜 μ—¬λŸ¬κ°œλ₯Ό μ‚¬μš©ν•  μˆ˜λ„ μžˆλ„€μš”!

https://docs.oracle.com/javase/8/docs/api/java/lang/annotation/ElementType.html

 

ElementType (Java Platform SE 8 )

The constants of this enumerated type provide a simple classification of the syntactic locations where annotations may appear in a Java program. These constants are used in java.lang.annotation.Target meta-annotations to specify where it is legal to write

docs.oracle.com

 

μ§€κΈˆμ€ METHOD , PARAMETER λ₯Ό μ–΄λ–»κ²Œ μ‚¬μš©ν–ˆλŠ”μ§€ λ³΄κ² μŠ΅λ‹ˆλ‹€!

처음 μ»¨νŠΈλ‘€λŸ¬μ—μ„œ μ•„λž˜μ™€ 같이 쓰인것은 μ»€νŠΈν…€ μ–΄λ…Έν…Œμ΄μ…˜μΈ @Auth 의 ElmentType 이 METHD 인데 λ©”μ„œλ“œλ₯Ό μ„ μ–Έν•  수 μžˆλ‹€λŠ” κ²λ‹ˆλ‹€!  κ·Έλž˜μ„œ μœ„μ— μ½”λ“œλ₯Ό ν™•μΈν•΄λ³΄μ‹œλ©΄  ApiServiceType은 enum 으둜 λ©”μ„œλ“œλ₯Ό μ„ μ–Έν•΄λ†“μ•˜μŠ΅λ‹ˆλ‹€! 

@Auth(type = ApiServiceType.HOMEPAGE)

@AuthAccount λŠ” ElemntType 이 parameter 둜  μ»¨νŠΈλ‘€λŸ¬μ—μ„œ νŒŒλΌλ―Έν„°λ‘œ λ°›κ³  μžˆλŠ”κ±Έ 확인할 수 μžˆμŠ΅λ‹ˆλ‹€

 

 

본격적으둜 Interceptor μ μš©μ„ ν•΄λ³΄κ² μŠ΅λ‹ˆλ‹€!

Interceptor μ μš©ν•  class λ₯Ό λ§Œλ“€μ–΄μ€λ‹ˆλ‹€!

@Component
@RequiredArgsConstructor
public class Interceptor implements HandlerInterceptor {

  private final AuthService authService;


  // ν˜ΈμΆœν•œ Controller κ°€ μ‹€ν–‰λ˜κΈ°μ „ μ‹€ν–‰λ˜λŠ” λ©”μ„œλ“œ (사전 μ œμ–΄)
  @Override
  public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
      throws Exception {
    HandlerMethod handlerMethod = (HandlerMethod) handler;
    // MethodAnnotation 으둜 μ»¨νŠΈλ‘€λŸ¬μ— Auth.class 둜 μ§€μ •ν•œ 값을 κ°€μ§€κ³ μ˜¨λ‹€
    // ν˜„μž¬ Api.ServiceType.HOMPAGE 둜 μ§€μ •ν–ˆμŒ(Controller class 확인)
    Auth auth = handlerMethod.getMethodAnnotation(Auth.class);
    
    if (auth == null) { 
      return true;
    }

    AuthCheck authCheck = AuthCheck.builder()
    	.authId(request.getHeader("auth_id"))
        .authSecret(request.getHeader("auth_secret"))
        // hadlerMethod 둜 κ°€μ§€κ³ μ˜¨ auth
        .apiServiceType(auth.type())
        .build();
        
	// 인증 λ‘œμ§μ—μ„œ μ‹€νŒ¨
    if (!authService.authentication(authCehck)) {
      throw new InvalidClientException();
    }
	// return 이 true 일경우 ν•΄λ‹Ή Controller둜 λ„˜μ–΄κ° 
    return true; 
  }
}
implements HandlerInterceptor

μ—¬κΈ°μ„œ HandlerInterceptor λ₯Ό implemnts λ°›μ•„μ„œ Interverptor λ₯Ό μ μš©ν•˜λ©΄ λ©λ‹ˆλ‹€. κ³΅μ‹λ¬Έμ„œμ—μ„œ μ•„λž˜μ™€ 같이 μ„€λͺ…λ˜μ–΄μžˆμŠ΅λ‹ˆλ‹€!

Spring interceptor λŠ” HandlerInterceptor λ₯Ό μ΄μš©ν•˜μ—¬ κ΅¬ν˜„ν•˜κ±°λ‚˜ HandlerInterceptorAdapter λ₯Ό μ΄μš©ν•΄ ν™•μž₯ν•  수 μžˆλ‹€κ³  λ‚˜μ™€μžˆλ„€μš”! 

λ‹€μ‹œ λ§ν•΄μ„œ μΈν„°νŽ˜μ΄μŠ€λ₯Ό  κ΅¬ν˜„ν•˜κ±°λ‚˜ μΆ”μƒν΄λž˜μŠ€λ₯Ό  μ˜€λ²„λΌμ΄λ”©μ„ ν†΅ν•΄μ„œ μžμ‹ λ§Œμ˜ 인터셉터λ₯Ό λ§Œλ“œλŠ”κ±°λ„€μš”!

 

    HandlerMethod handlerMethod = (HandlerMethod) handler;

 

이 λΆ€λΆ„ μ—μ„œ handler λŠ” ν˜„μž¬ μ‹€ν–‰ν•˜λ €λŠ” λ©”μ„œλ“œ 자체λ₯Ό μ˜λ―Έν•©λ‹ˆλ‹€, ν˜„μž¬ μ‹€ν–‰λ˜λŠ” 컨트둀러λ₯Ό νŒŒμ•…ν•˜λŠ” μž‘μ—…μ΄ κ°€λŠ₯ν•˜μ£ !

κ·Έλž˜μ„œ handlerMethod.get 을 톡해 ν˜„μž¬ μ‹€ν–‰ν•œ 컨트둀러의 Auth.class 값을 가지고 였거라~ μž…λ‹ˆλ‹€! λ§Œμ•½ Auth.class λ₯Ό 뢙이지 μ•Šμ€ 컨트둀러이면 interceptorλ₯Ό 거쳐갈 ν•„μš”κ°€ μ—†μœΌλ‹ˆ true λ₯Ό λ°˜ν™˜ν•΄μ„œ Controller κ°€ μ‹€ν–‰λ˜κ²Œ ν•©λ‹ˆλ‹€

 

 

이제 HandlerMethodArgumentResolver κ°€ μ–΄λ–»κ²Œ μ“°μ΄λŠ”μ§€ ν™•μΈν•΄λ΄…μ‹œλ‹€

 

주어진 μš”μ²­μ„ μ²˜λ¦¬ν•  λ•Œ, λ©”μ„œλ“œ νŒŒλΌλ―Έν„°λ₯Ό μΈμžκ°’λ“€μ— μ£Όμž… ν•΄μ£ΌλŠ” μΈν„°νŽ˜μ΄μŠ€ μž…λ‹ˆλ‹€!

인증에 ν•„μš”ν•œ parameter 이 μ€‘λ³΅μœΌλ‘œ λ°œμƒλ  수 있기 λ•Œλ¬Έμ— κ³΅ν†΅μœΌλ‘œ μž…λ ₯λ˜λŠ” μž‘μ—…λ“€μ„ ν•œλ²ˆμ— μ²˜λ¦¬ν•˜κ³  싢을 λ•Œ μ‚¬μš©ν•©λ‹ˆλ‹€!

 

 

 

@Component
public class ArgumentResolver implements HandlerMethodArgumentResolver {

  @Override
  public boolean supportsParameter(MethodParameter methodParameter) {
    return methodParameter.hasParameterAnnotation(AuthAccount.class);
  }

  @Override
  public Object resolveArgument(MethodParameter methodParameter,
      ModelAndViewContainer modelAndViewContainer, NativeWebRequest request,
      WebDataBinderFactory webDataBinderFactory) throws Exception {

    String accountId = request.getHeader("accountId");

    return crypto.toDecryptAsLong(accountId);
  }
}

supportsParameter μ–΄λ–€ νŒŒλΌλ―Έν„°μ— λŒ€ν•΄ μž‘μ—…μ„ 할건지 μ •μ˜ ν•˜λŠ” λ©”μ„œλ“œ μž…λ‹ˆλ‹€. 

μ €ν¬λŠ” μ»€μŠ€ν…€ μ–΄λ…Έν…Œμ΄μ…˜μΈ @AuthAccount λ₯Ό μ‚¬μš© ν–ˆμœΌλ‹ˆ hsParameterAnnotaion 을 톡해 ν™•μΈν•©λ‹ˆλ‹€. 

λ§Œμ•½ μ»€μŠ€ν…€ μ–΄λ…Έν…Œμ΄μ…˜μΈ μ•„λ‹Œ νŒŒλΌλ―Έν„°λ‘œ 객체 (Dto) 둜 μ‚¬μš©ν•˜κ³  μ‹Άλ‹€λ©΄ μ•„λž˜μ™€ 같이 μ‚¬μš©ν•©λ‹ˆλ‹€. ν•˜μ§€λ§Œ orderDto λ₯Ό νŒŒλΌλ―Έν„°λ‘œ λ°›κ³  μžˆλŠ” μ»¨νŠΈλ‘€λŸ¬κ°€ λ§Žλ‹€λ©΄... 같은 μž‘μ—…μ„ μˆ˜ν–‰ν•˜λŠ”κ²Œ μ•„λ‹ˆλΌλ©΄(보톡 κ·Έλ ‡κ² μ£ ..?) μ–΄λ””μ„œ μ•Œμˆ˜ μ—†λŠ” 값이 νŠ€μ–΄λ‚˜μ˜¬μ§€ μ˜ˆμƒν•  수 없을 κ±°κ°™μ•„ λΉ„μΆ”ν•œλ‹€

return methodParameter.getParameterType().equals(orderDto.class);

 resolveArgument λŠ” 바인딩할 객체λ₯Ό μ‘°μž‘ν•˜λŠ” λ©”μ„œλ“œ μž…λ‹ˆλ‹€!

μ €λŠ” μ €κΈ°μ„œ AuthAccount 둜 받은 accountId 이 μ•”ν˜Έν™”λ˜μ–΄ 있기 λ•Œλ¬Έμ— λ³΅ν˜Έν™” ν•΄μ£Όμ–΄ 리턴해주고 μžˆμŠ΅λ‹ˆλ‹€.

 

이제 이 인터셉터와 리볼버λ₯Ό μ‚¬μš©ν•˜λ„λ‘ 등둝해쀄 μ°¨λ‘€μž…λ‹ˆλ‹€! 

 

@Configuration
@RequiredArgsConstructor
public class WebConfig implements WebMvcConfigurer {

  private final ArgumentResolver argumentResolver;
  private final Interceptor interceptor;

  @Override
  public void addInterceptors(InterceptorRegistry registry) {
    registry.addInterceptor(interceptor);
  }

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

 

휴^^....;; 틀린뢀뢄이 μžˆλ‹€λ©΄ λŒ“κΈ€ λ‚¨κ²¨μ£Όμ„Έμš”! 

λ°˜μ‘ν˜•