본문 바로가기
Spring/- [시리즈] Spring 프로젝트 되살리기

Spring 프로젝트 되살리기 8 - 인가(Authorization) 인터셉터 구현

by 정구정구 2026. 7. 27.

이전 이야기

https://record47584.tistory.com/93

 

Spring 프로젝트 되살리기 7 - 리팩토링 4 (record, 정적 팩토리 메서드)

이전 이야기https://record47584.tistory.com/91 Spring 프로젝트 되살리기 6 - 유닛 테스트 코드 고치기이전 이야기https://record47584.tistory.com/90 Spring 프로젝트 되살리기 5 - 리펙토링 3 (@EntityGraph 적용)이전 이

record47584.tistory.com

 

 

시나리오 8

더보기
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
    HttpServletRequest httpRequest = (HttpServletRequest) request;
    HttpServletResponse httpResponse = (HttpServletResponse) response;

    String url = httpRequest.getRequestURI();

    if (url.startsWith("/auth")) {
        chain.doFilter(request, response);
        return;
    }

    String bearerJwt = httpRequest.getHeader("Authorization");

    if (bearerJwt == null) {
        log.warn("인증 헤더 누락: URI={}", url);
        sendErrorResponse(httpResponse, HttpStatus.UNAUTHORIZED, "인증이 필요합니다.");
        return;
    }

    String jwt = jwtUtil.substringToken(bearerJwt);

    try {
        // JWT 유효성 검사와 claims 추출
        Claims claims = jwtUtil.extractClaims(jwt);
        if (claims == null) {
            log.warn("Claims 추출 실패: URI={}", url);
            sendErrorResponse(httpResponse, HttpStatus.UNAUTHORIZED, "인증이 필요합니다.");
            return;
        }

        UserRole userRole = UserRole.valueOf(claims.get("userRole", String.class));

        httpRequest.setAttribute("userId", Long.parseLong(claims.getSubject()));
        httpRequest.setAttribute("email", claims.get("email"));
        httpRequest.setAttribute("userRole", claims.get("userRole"));
        
        //// 권한 확인 (인가) -> 인터셉터에서 담당하는게 유리함
        if (url.startsWith("/admin") && !UserRole.ADMIN.equals(userRole)) {
            log.warn("권한 부족: userId={}, role={}, URI={}", claims.getSubject(), userRole, url);
            sendErrorResponse(httpResponse, HttpStatus.FORBIDDEN, "접근 권한이 없습니다.");
            return;
        }
        ////

        chain.doFilter(request, response);
    } catch (ExpiredJwtException e) {
        log.info("JWT 만료: userId={}, URI={}", e.getClaims().getSubject(), url);
        sendErrorResponse(httpResponse, HttpStatus.UNAUTHORIZED, "인증이 필요합니다.");
    } catch (SecurityException | MalformedJwtException | UnsupportedJwtException e) {
        log.error("JWT 검증 실패 [{}]: URI={}", e.getClass().getSimpleName(), url, e);
        sendErrorResponse(httpResponse, HttpStatus.BAD_REQUEST, "인증이 필요합니다.");
    } catch (Exception e) {
        log.error("예상치 못한 오류: URI={}", url, e);
        sendErrorResponse(httpResponse, HttpStatus.INTERNAL_SERVER_ERROR, "요청 처리 중 오류가 발생했습니다.");
    }
}

 현재 위와 같이, 인증/인가 모두 JWT 필터에서 진행하고 있다. 이는 사실 좋지 않은 방법이다.

 

인가를 담당해줄 인터셉터를 추가해보자.

 

(인터셉터의 개념, 인증/인가를 분리하는 이유, 구현 방법 등은 자세한 내용은 아래 글에서 이미 다뤘다.)

https://record47584.tistory.com/92

 

인터셉터(Interceptor)의 개념과 구현

인터셉터(Interceptor)란? Controller가 호출되기 전/후를 가로채서 공통 로직을 실행하는 Spring MVC의 기능 DispatcherServlet이 Handler(Controller)를 찾은 다음, 그 Controller를 호출하기 직전과 직후 시점에 끼어

record47584.tistory.com

 

 

인가 인터셉터 구현

HandlerInterceptor 구현

@Slf4j
@Component
@RequiredArgsConstructor
public class AdminCheckInterceptor implements HandlerInterceptor {

    private static final String START_TIME = "startTime";

    private final CommentService commentService;

    // Cotroller로 들어가기 전
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws IOException {

        // 1. 요청 시간, url 기록
        long startTime = System.currentTimeMillis();
        request.setAttribute(START_TIME, startTime);
        log.info("Request URI: {}, Start Time: {}", request.getRequestURI(), startTime);

        // 2. JwtFilter 에서 set 한 userId, email, userRole 값을 가져옴
        Long userId = (Long) request.getAttribute("userId");
        String email = (String) request.getAttribute("email");
        UserRole userRole = UserRole.of((String) request.getAttribute("userRole"));

        // 3. 유저 role 체크
        if (userRole != UserRole.ADMIN) {
            log.warn("관리자가 아닌 사람이 접근했습니다.\n -> userId: {}, email: {}, userRole: {}", userId, email, userRole);
            response.sendError(HttpServletResponse.SC_FORBIDDEN, "관리자만 할 수 있는 요청입니다.");
            return false;
        }

        return true;
    }

    // Cotroller로 들어간 후
    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws IOException {

        // 1. 응답 시간, url 기록
        Long startTime = (Long) request.getAttribute(START_TIME);

        if (startTime != null) {
            long endTime = System.currentTimeMillis();
            long executeTime = endTime - startTime;
            log.info("Request URI: {} took {} ms", request.getRequestURI(), executeTime);
        }
    }
}

 

WebConfig에 등록

@Configuration
@RequiredArgsConstructor
public class WebConfig implements WebMvcConfigurer {

    private final AuthUserArgumentResolver authUserArgumentResolver;

    @Override
    public void addArgumentResolvers(List<HandlerMethodArgumentResolver> resolvers) {
        resolvers.add(authUserArgumentResolver);
    }

    private final AdminInterceptor adminCheckInterceptor;

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(adminCheckInterceptor)
                .order(1)
                .addPathPatterns(
                        "/admin/**"
                );
    }
}

 

이전에 추가 했던 WebConfig에 인터셉터를 등록해준다.

 

Filter에서 인가 코드 제거

/* 인터셉터에서 인가를 처리
if (url.startsWith("/admin") && !UserRole.ADMIN.equals(userRole)) {
    log.warn("권한 부족: userId={}, role={}, URI={}", claims.getSubject(), userRole, url);
    sendErrorResponse(httpResponse, HttpStatus.FORBIDDEN, "접근 권한이 없습니다.");
    return;
}
*/

 

 

동작 확인

관리자 권한이 필요한 요청을 보내면 다음과 같은 로그를 확인할 수 있다.

 

성공

10:16:41.686 [INFO ] : Request URI: /admin/users/4, Start Time: 2026-07-27 10:16:41.686
10:16:41.719 [INFO ] : Request URI: /admin/users/4 took 33 ms

 

거부

10:18:51.868 [INFO ] : Request URI: /admin/users/4, Start Time: 2026-07-27 10:18:51.868
10:18:51.868 [WARN ] : 관리자가 아닌 사람이 접근했습니다.
 -> userId: 1, email: wjdals0508@naver.com, userRole: USER