AOP로 사용자 검증을 처리하는 이유와 전략
JWT 기반 인증 시스템에서는 클라이언트가 토큰을 보관하고 요청마다 서버에 전달한다.
하지만 토큰이 유효하다고 해서 무조건 신뢰할 수 있는 건 아니다. 다음과 같은 이유 때문이다.
토큰이 위조되거나 탈취됐을 수 있고, 탈퇴한 유저일 수도 있다. 또 토큰의 userId가 실제 DB와 불일치할 수 있다.
즉, 로그인 이후에도 사용자 검증이 필요함을 느꼈다.
필터 vs AOP 사용자 검증은 어디서 처리해야 할까?
필터 방식 JWT 인증 필터에서 토큰을 파싱하고 SecurityContext에 인증 정보를 설정하는 방식이다.
모든 요청에 일괄적으로 적용이 가능하다는 큰 장점이 있었지만, 필터에서 userId로 UserRepository를 직접 조회하는 방식은 구조적으로 어색하기도 했고, 인증과 검증 책임이 과도하게 몰린다는 느낌을 받았다.
예시 코드
if (authorizationHeader != null && authorizationHeader.startsWith("Bearer ")) {
String jwt = jwtUtil.substringToken(authorizationHeader);
try {
Claims claims = jwtUtil.extractClaims(jwt);
// 사용자 정보 추출
Long userId = claims.get("userId", Long.class);
UserRole userRole = claims.get("userRole", UserRole.class);
// 사용자 검증 로직 (필터 내부 직접 처리)
User user = userRepository.findById(userId)
.filter(u -> u.getRole() == userRole)
.orElseThrow(() -> new AuthException(AuthErrorCode.ACCESS_TOKEN_MISMATCH));
...
그렇다고 모든 메서드에 검증 로직을 반복해서 넣는 것도 굉장히 비효율적이라 생각했다.
그래서 선택한 방식이 AOP 기반 사용자 검증 방식이다.
AOP란 공통 관심사를 핵심 로직과 분리하여, 필요한 지점에만 횡단 관심사를 적용할 수 있게 해준다.
이를 통해 필요한 곳만 선택적으로 검증을 할 수 있고, 로직을 분리해 재사용성과 가독성도 향상된다.
적용 예시
@ValidUser
@DeleteMapping("/withdraw")
public ResponseEntity<ApiResponse<String>> withdraw(
@AuthenticationPrincipal AuthUser user,
@Valid @RequestBody WithdrawRequest request
) {
authService.withdraw(user.id(), request);
return ApiResponse.success(null, "회원탈퇴 되었습니다.");
}
특정 어노테이션(@ValidUser)이 붙은 메서드에만 사용자 검증을 수행한다.
회원탈퇴처럼 민감한 작업에만 검증을 적용할 수 있어 효율적이다.
결론
필터는 인증(토큰 파싱)에 집중하고, AOP는 인가(사용자 상태 검증)에 적합하다고 보여진다.
그리고 필터에서 너무 많은 검증을 처리하려하면 구조가 무거워지고 유지보수도 어려워진다.
오늘 구현한 방식은 책임을 분리하고 반복을 줄이면서, 구조적으로 자연스러운 위치에서 검증을 수행할 수 있다는 장점이 있음을 배웠다.