티스토리 뷰
[Spring] @PathVariable 또는 @RequestParam 등의 값을 변환하여 받기(암호화된 @PathVaraible 값 복호화)
망나니개발자 2023. 10. 17. 10:00
1. @PathVariable 또는 @RequestParam 등의 값을 변환하여 받기
(암호화된 @PathVaraible 값 복호화)
[ 컨트롤러에서 받는 값 변환의 필요성 ]
애플리케이션을 개발하다 보면 리소스에 대한 고유한 값인 식별자(identifier)을 받아 요청을 처리하는 경우가 있다. 리소스의 식별자는 시스템 내부 정보이므로, 해당 값이 1씩 단순 증가하는 값이라면 암/복호화를 해서 사용해야 한다.
예를 들어 다음과 같이 특정 멤버의 포인트를 증가시키는 API가 있다고 하자. 이때 PathVariable로 받는 memberId 값은 암호화되어 있기 때문에 컨트롤러 혹은 이를 사용하는 하위 계층에 복호화 로직을 넣어주어야 한다.
@RestController
@RequiredArgsConstructor
public class AddPointController {
private final AddMemberUseCase useCase;
@PostMapping("/members/{encryptedMemberId}/points")
public ResponseEntity<Void> add(
@PathVariable String encryptedMemberId,
@RequestBody @Valid AddPointRequest request
) {
Long memberId = MemberIdEncryptor.decrypt(encryptedMemberId);
useCase.add(request.toInput(memberId));
return ResponseEntity.ok()
.build();
}
}
하지만 이러한 로직을 모든 컨트롤러에 적용하는 것은 상당히 번거로우며 중복에 해당한다.
누군가는 이를 위해 ArgumentResolver를 떠올리지만 ArgumentResolver로는 해결이 불가능하다. 왜냐하면 ArgumentResolver의 역할은 파라미터로 받는 값을 생성해주는 것으로, @PathVariable이나 @RequestParam 등으로 받는 값은 이미 ArgumentResolver를 통해 처리되어 반환된 값이기 때문이다. 현재 필요한 로직은 ArgumentResolver를 통해 생성된 값을 변환하는 것이다.
[ 컨버터(Converter)를 이용해 값 변환하기 ]
스프링은 String 타입의 파라미터를 Long 타입으로 받을 수 있는 등 다양한 타입 변환을 지원해주는데, 이는 스프링의 컨버터(Converter) 덕분이다. 따라서 추가적인 컨버터를 구현한다면 원하는 값으로 변환하여 받을 수 있다.
이번에는 @PathVariable로 받은 암호화된 String 타입의 memberId 값을, 복호화된 Long 타입 상태로 변환해주는 컨버터를 작성해보도록 하자. 복호화될 @PathVariable을 식별하기 위해 추가적인 어노테이션이 필요하다.
먼저 복호화할 값을 식별하도록 어노테이션을 생성한다.
@Target(ElementType.PARAMETER)
@Retention(RetentionPolicy.RUNTIME)
public @interface DecryptId {
}
그리고 다음의 조건을 만족하는 컨버터를 구현한다. 이를 위해 ConditionalGenericConverter를 사용하면 된다. 참고로 해당 컨버터를 코틀린에서 적용하고자 한다면 ConvertiblePair로 Long.class 대신 Number.class를 넣어주어야 한다.
- String 타입을 Long 타입으로 변경하는 경우에 실행됨
- @DecryptId 어노테이션이 있는 경우에만 실행됨
public class MyConverter implements ConditionalGenericConverter {
@Override
public boolean matches(final TypeDescriptor sourceType, final TypeDescriptor targetType) {
return targetType.hasAnnotation(DecryptId.class);
}
@Override
public Set<ConvertiblePair> getConvertibleTypes() {
return Set.of(
new ConvertiblePair(String.class, Long.class)
);
}
@Override
public Object convert(final Object source, final TypeDescriptor sourceType, final TypeDescriptor targetType) {
return MemberIdEncryptor.decrypt((String)source);
}
}
그리고 구현된 컨버터를 Web 설정에 추가해주면 된다.
@Configuration
public class WebConfig implements WebMvcConfigurer {
@Override
public void addFormatters(FormatterRegistry registry) {
registry.addConverter(new DecryptIdConverter());
}
}
그러면 이제 컨트롤러에서 다음과 같이 복호화된 상태로 값을 받을 수 있다.
@RestController
@RequiredArgsConstructor
public class AddPointController {
private final AddMemberUseCase useCase;
@PostMapping("/members/{memberId}/points")
public ResponseEntity<Void> add(
@PathVariable @DecryptId Long memberId,
@RequestBody @Valid AddPointRequest request
) {
useCase.add(request.toInput(memberId));
return ResponseEntity.ok()
.build();
}
}
컨버터의 확장 가능성은 거의 무한하기 때문에 복호화된 ID를 넘어서 Member 객체로도 받도록 확장할 수 있다. 따라서 필요에 따라 컨버터를 수정해서 사용해주도록 하자.