이전 이야기
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'Spring > - [시리즈] Spring 프로젝트 되살리기' 카테고리의 다른 글
| Spring 프로젝트 되살리기 9 - API 로깅 기능 구현 (AOP) (0) | 2026.07.27 |
|---|---|
| Spring 프로젝트 되살리기 7 - 코드 리팩토링 4 (record, 정적 팩토리 메서드 적용) (0) | 2026.07.26 |
| Spring 프로젝트 되살리기 6 - 유닛 테스트 코드 고치기 (0) | 2026.07.25 |
| Spring 프로젝트 되살리기 5 - 코드 리팩토링 3 (@EntityGraph 적용) (0) | 2026.07.25 |
| Spring 프로젝트 되살리기 4 - 코드 리팩토링 2 (@Valid 적용) (0) | 2026.07.25 |