본문 바로가기

Back-End/Spring-MVC

JWT 기반 인증/인가 흐름에서 AOP와 필터(URI기반)

JWT에 대해서는 저번 시간에 많이 설명했으니 이 글에서는 간단히 설명만 하고 넘어가겠다.

1. JWT 기반 인증/인가 개념

JWT는 사용자 정보를 안전하게 클라이언트와 서버 간에 교환하기 위해 사용. 클라이언트는 서버로 요청을 보낼 때 JWT를 포함하고, 서버는 JWT를 검증하여 사용자를 인증하거나 권한을 확인한다.

  • JWT 구조
    • Header : 토큰 타입(JWT)과 서명 알고리즘 정보
    • Payload : 사용자 정보(ex: userId, role 등)
    • Signature : 데이터 위조 방지용 서명

2. 필터와 AOP의 역할

1) 필터의 역할 : 인증

필터는 요청(Request)이 들어올 때 가장 먼저 실행된다. 주로 JWT 토큰을 검증하여 사용자를 인증하는 데 사용된다.

  • 필터의 주요 작업
    1. HTTP 요청 헤더에서 JWT 토큰 추출.
    2. JWT의 유효성 검증(서명, 만료 여부 확인).
    3. JWT에서 사용자 정보(userId, role)를 추출.
    4. 요청 객체에 사용자 정보를 추가(HttpServletRequest.setAttribute).
    5. 요청을 다음 단계(다음 필터 또는 컨트롤러)로 전달.

2) AOP의 열할 : 인가 

AOP는 컨트롤러의 특정 메서드 실행 시 동작하여 권한을 검증한다. 요청의 URI와 사용자 역할을 기반으로 사용자가 해당 API에 접근할 권한이 있는지 확인한다.

  • AOP의 주요 작업
    1. 컨트롤러 메서드 실행 전 동작.(꼭 컨트롤러가 아니여도 서비스에서도 쓸 수 있다)
    2. 요청의 URI와 사용자 역할을 비교하여 권한 검증
    3. 권한이 없으면 예외를 발생시켜 요청 차단

3. 필터와 AOP의 요청 흐름

1) 요청 처리 흐름

  1. 클라이언트 요청 -> 필터
    • 클라이언트는 Authorizaton 헤더에 JWT를 포함하여 서버로 요청
    • 필터가 JWT를 검증하고 사용자 정보를 요청 객체에 추가
  2. 필터 -> 컨트롤러 + AOP
    • 필터가 filterChain.doFilter()를 호출하여 요청을 컨트롤러로 전달.
    • AOP는 컨트롤러 메서드 실행 전에 권한 검증을 수행
  3. 컨트롤러 -> 서비스 -> 응답
    • 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(인가)는 컨트롤러 메서드 실행 전 권한을 검증하는 담당을 한다.