🪄 발단
카카오 로그인을 구현하면서 클라이언트로부터 token 값을 넘겨받는 api가 많이 생겼다. 현황은 이 token을 검증하지 않고 있었다. 그러나 안전한 서비스를 제공하기 위하여 우리가 가진 jwt secret key로 token을 검증할 필요가 있어보였다. 그래서 오늘 실행에 옮겼다.
사실 지금처럼 검증하지 않더라도 유효하지 않은 token이 넘어올 경우 그냥 개인정보를 제공하지 않도록 처리되어있어서 현재 우리 서비스에 token 검증은 필요하지 않을 수 있다고 생각할 수 있다. 그렇지만, 유효하지 않은 토큰이 넘어올 경우에 대한 관리를 일관된 장소에서 할 수 있다는 장점이 있다고 보았다. 더 나아가, 유효하지 않은 token일 경우에 수행되지 않아도 될 불필요한 비즈니스 로직, 디버깅 비용을 감소시키기도 좋을 것 같다.
🪄 계획
token을 받아올 때마다 검증을 해야 하니까 AOP를 사용하는 것이 가장 적절하다고 생각했다.
Spring AOP는 배우기만 했지 적용하는건 처음이라 기대되었다.
참고: 한창 배웠을때 쓴 글이다.
https://splendidlolli.tistory.com/567
🪄 구현
Aspect
먼저 다음의 TokenValidationAspect와 같이 @Aspect를 두었다.
참고로 Spring에서는 런타임 시점에 Aspect를 제공하며, Proxy 방식으로 Advice(부가기능)가 적용된다.
Advice 종류는 '언제 적용할지'에 따라 여러가지가 있는데 @Before를 선택한 이유는, 메서드 실행 전에 tokenId를 검증하기 때문이다. Advice에는 PointCut(부가기능을 적용할 위치)을 지정하게 되어있다. 내가 만든 @VaildateToken 이라는 어노테이션이 적용된 곳에서 이 Advice가 수행되도록 했다.
@Aspect
@Component
public class TokenValidationAspect {
private final TokenProvider provider;
private static final String TOKEN_ID_NAME = "tokenId";
@Autowired
public TokenValidationAspect(TokenProvider provider) {
this.provider = provider;
}
@Before("@annotation(ValidateToken)")
public void validateToken(JoinPoint joinPoint) throws InvalidTokenException {
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
String[] paramNames = signature.getParameterNames();
Object[] args = joinPoint.getArgs();
String tokenId = null;
for (int i = 0; i < args.length; i++) {
if (paramNames[i].equals(TOKEN_ID_NAME)) {
tokenId = (String) args[i];
break;
}
}
// 토큰검증에 대한 Exception도 일관된 장소에서 터뜨릴 수 있다는 장점
if (tokenId == null || tokenId.length() == 0) {
throw new InvalidTokenException("Invalid Token");
}
if (!provider.validateToken(tokenId)) {
throw new InvalidTokenException("Invalid Token");
}
}
}
ValidateToken 어노테이션
어노테이션의 Target은 METHOD로 지정하였다.
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface ValidateToken {
}
그리고 이 어노테이션을 토큰검증 대상 메서드에 붙이면, 해당 메서드 파라미터 중 tokenId에 대하여 @Aspect의 @Before("@annotation(ValidateToken)")가 동작하여 validateToken(JoinPoint joinPoint) 메서드가 실행된다. 그리고 JointPoint로부터 tokenId 파라미터 값을 불러온다. 그리고 우리가 가지고 있는 TokenProvider를 통해 provider.validateToken(tokenId)를 활용하여 검증할 수 있다.
🪄 사용 예
다음과 같이 @ValidateToken 으로 간편하게 토큰검증 가능해졌다.
@PostMapping("/review/like/{review_id}/{token_id}")
@ValidateToken
public LikeResponseDto postLikeCount(@PathVariable("review_id") Long reviewId,
@PathVariable("token_id") String tokenId){
...
}
'JAVA > Application' 카테고리의 다른 글
[Spring] Cache : 프로젝트에 간단히 적용해둠 (1) | 2024.03.28 |
---|---|
[Java][Backend] null 반환 리팩터링 (0) | 2024.03.08 |
[Java] 시스템 환경변수는 System.getProperty가 아니라 System.getenv로 가져온다 (4) | 2024.02.22 |
Spring의 캐싱 기능 (0) | 2023.08.27 |
[자바/스프링] 비즈니스 로직 층 설계 (0) | 2023.08.12 |