JWT에 대해서는 저번 시간에 많이 설명했으니 이 글에서는 간단히 설명만 하고 넘어가겠다.
1. JWT 기반 인증/인가 개념
JWT는 사용자 정보를 안전하게 클라이언트와 서버 간에 교환하기 위해 사용. 클라이언트는 서버로 요청을 보낼 때 JWT를 포함하고, 서버는 JWT를 검증하여 사용자를 인증하거나 권한을 확인한다.
- JWT 구조
- Header : 토큰 타입(JWT)과 서명 알고리즘 정보
- Payload : 사용자 정보(ex: userId, role 등)
- Signature : 데이터 위조 방지용 서명
2. 필터와 AOP의 역할
1) 필터의 역할 : 인증
필터는 요청(Request)이 들어올 때 가장 먼저 실행된다. 주로 JWT 토큰을 검증하여 사용자를 인증하는 데 사용된다.
- 필터의 주요 작업
- HTTP 요청 헤더에서 JWT 토큰 추출.
- JWT의 유효성 검증(서명, 만료 여부 확인).
- JWT에서 사용자 정보(userId, role)를 추출.
- 요청 객체에 사용자 정보를 추가(HttpServletRequest.setAttribute).
- 요청을 다음 단계(다음 필터 또는 컨트롤러)로 전달.
2) AOP의 열할 : 인가
AOP는 컨트롤러의 특정 메서드 실행 시 동작하여 권한을 검증한다. 요청의 URI와 사용자 역할을 기반으로 사용자가 해당 API에 접근할 권한이 있는지 확인한다.
- AOP의 주요 작업
- 컨트롤러 메서드 실행 전 동작.(꼭 컨트롤러가 아니여도 서비스에서도 쓸 수 있다)
- 요청의 URI와 사용자 역할을 비교하여 권한 검증
- 권한이 없으면 예외를 발생시켜 요청 차단
3. 필터와 AOP의 요청 흐름
1) 요청 처리 흐름
- 클라이언트 요청 -> 필터
- 클라이언트는 Authorizaton 헤더에 JWT를 포함하여 서버로 요청
- 필터가 JWT를 검증하고 사용자 정보를 요청 객체에 추가
- 필터 -> 컨트롤러 + AOP
- 필터가 filterChain.doFilter()를 호출하여 요청을 컨트롤러로 전달.
- AOP는 컨트롤러 메서드 실행 전에 권한 검증을 수행
- 컨트롤러 -> 서비스 -> 응답
- AOP에서 권한 검증이 통과되면, 컨트롤러 메서드 실행
- 서비스 로직 처리 후 클라이언트에 응답 반환
2) 필터와 AOP의 역할 분리
- 필터(Filter) : 요청 흐름의 처음에서 JWT를 검증하고 사용자 정보를 설정.
- AOP : 컨트롤러의 특정 메서드 실행 전에 권한을 검증.
4. JWT 필터 구현 예제
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
HttpServletRequest httpRequest = (HttpServletRequest) request;
HttpServletResponse httpResponse = (HttpServletResponse) response;
// JWT 토큰 추출
String authorizationHeader = httpRequest.getHeader("Authorization");
if (authorizationHeader == null || !authorizationHeader.startsWith("Bearer ")) {
httpResponse.sendError(HttpServletResponse.SC_UNAUTHORIZED, "JWT 토큰이 필요합니다.");
return;
}
String jwt = authorizationHeader.substring(7);
// JWT 유효성 검증
if (!jwtUtil.validateToken(jwt)) {
httpResponse.sendError(HttpServletResponse.SC_FORBIDDEN, "유효하지 않은 JWT 토큰입니다.");
return;
}
// 사용자 정보 추출
String userId = jwtUtil.extractUserId(jwt);
String role = jwtUtil.extractRoles(jwt);
// 요청에 사용자 정보 추가
httpRequest.setAttribute("userId", userId);
httpRequest.setAttribute("role", role);
// 다음 단계로 요청 전달
chain.doFilter(request, response);
}
5. AOP를 활용한 권한 검증 예제
@Aspect
@Component
public class CheckMemberRoleAspect {
@Before("execution(* com.example.controller.StoreController.createStore(..))")
public void checkRoleStore() {
//요청 URI와 권한 가져오기
String requestURI = request.getRequestURI();
String role = (String) request.getAttribute("role");
//권한 정보가 없는 경우 예외 처리
if (role == null) {
throw new UserRoleNotFoundException("사용자 권한 정보가 없습니다.");
}
if (requestURI.startsWith("/api/stores")) {
if (!"OWNER".equals(role)) {
throw new UnauthorizedAccessException("사장님 권한만 접근할 수 있습니다.");
}
}
log.info("권한 확인 완료. 권한 : {}, 요청 URI : {}", role, requestURI);
}
}
- 컨트롤러 메서드 createStore 실행 전 동작
- 요청 객체에서 role을 확인
- OWNER 역할이 아닌 경우 예외 발생
6. 필터와 AOP의 흐름 정리
왜 필터와 AOP는 서로 독립적인가?
1. 필터는 요청의 흐름을 제어
- 요청의 처음부터 끝까지 전체 요청 흐름을 관리
- filterChain.doFilter()로 요청을 다음 단계(다음 필터 또는 컨트롤러)로 전달.
2. AOP는 컨트롤러 메서드에 동작
- 특정 메서드 실행 전/후에만 동작하며, 요청 흐름 자체를 제어하지 않음.
7. Q&A: 필터와 AOP의 관계
Q1 : AOP가 필터로 다시 돌아갈 필요가 있나?
A1 : 아니다. 필터는 요청흐름 전체를 제어하며, AOP는 메서드 레벨에서 동작하므로 서로 독립적으로 작동한다.
Q2 : 필터가 filterChain.doFilter()를 호출하지 않으면?
A2 : 요청 흐름이 중단되고, 컨트롤러와 AOP가 실행되지 않는다. 필터는 항상 filterChain.doFilter()를 호출해야 요청이 컨트롤러에 도달할 수 있다.
Q3 : 필터와 AOP는 각각 언제 사용?
- 필터 : 모든 요청에 대한 공통적인 검증
- AOP : 컨트롤러 메서드별로 세부적인 권한 검증
결론적으로 필터(인증)는 JWT를 검증하고 사용자 정보를 요청 객체에 설정하는 담당을 하고 AOP(인가)는 컨트롤러 메서드 실행 전 권한을 검증하는 담당을 한다.
'Back-End > Spring-MVC' 카테고리의 다른 글
| AOP를 활용한 권한 검증 (feat. 커스텀 어노테이션) (0) | 2025.01.09 |
|---|---|
| JWT 기반 인증 및 역할별 API 설계와 구현 : 관리자와 사용자 권한 처리 (0) | 2025.01.06 |
| JWT(Json Web Token) (0) | 2025.01.04 |
| 스프링 글로벌 API 예외 처리와 @Valid 검증 예외 처리 (1) | 2024.12.31 |
| Servlet Filter (0) | 2024.12.12 |