[JWT 필터 개선] JwtTokenValidationFilter에서 JWT 검증 로직을 개선하여 Access Token 유효성 검사 후 자동으로 Refresh Token을 갱신하는 기능 추가. WebSecurity에서 필터 설명을 업데이트하고, 허용 경로 설정을 정리하여 보안 설정을 강화.

This commit is contained in:
2025-08-21 17:06:14 +09:00
parent 6d1b89609b
commit 09e06cd10b
2 changed files with 65 additions and 55 deletions

View File

@@ -32,68 +32,76 @@ public class JwtTokenValidationFilter extends OncePerRequestFilter {
private final MemberService memberService;
private final Environment env;
@Override
protected boolean shouldNotFilter(HttpServletRequest request) throws ServletException {
String path = request.getRequestURI();
// permitAll 경로는 JWT 검증을 건너뜀 (WebSecurity.java의 설정과 동기화)
return path.equals("/login") ||
path.startsWith("/members/register") ||
path.startsWith("/swagger-ui/") ||
path.startsWith("/api-docs/") ||
path.startsWith("/ws/");
}
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response,
FilterChain filterChain) throws ServletException, IOException {
log.debug("JWT 토큰 검증 필터 실행 - URI: {}", request.getRequestURI());
String accessToken = jwtUtils.extractAccessJwtFromRequest(request);
String refreshToken = jwtUtils.extractRefreshJwtFromCookie(request);
if(accessToken == null){
sendJsonResponse(response, ApiResponseDto.fail(ApiResponseCode.JWT_TOKEN_NULL, null));
// Access Token이 있고 유효한 경우
if (accessToken != null && jwtUtils.validateAccessToken(accessToken)) {
String username = jwtUtils.extractUsername(accessToken);
UserDetails userDetails = memberService.loadUserByUsername(username);
if (userDetails != null) {
UsernamePasswordAuthenticationToken authentication =
new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());
authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
SecurityContextHolder.getContext().setAuthentication(authentication);
log.debug("Access Token 인증 성공: {}", username);
filterChain.doFilter(request, response);
return;
}
}
// Access Token이 없거나 만료된 경우, Refresh Token으로 갱신 시도
if (refreshToken != null && jwtUtils.validateRefreshToken(refreshToken)) {
String username = jwtUtils.extractUsername(refreshToken);
String role = (String) jwtUtils.extractAllClaims(refreshToken).get("role");
// 새로운 Access Token 생성
String newAccessToken = jwtUtils.generateToken(username, role,
Long.parseLong(Objects.requireNonNull(env.getProperty("token.expiration_time_access"))));
// 새로운 Access Token을 응답 헤더에 설정
response.setHeader("Authorization", "Bearer " + newAccessToken);
// Refresh Token 갱신
String newRefreshToken = jwtUtils.refreshTokens(username, role);
jwtUtils.setRefreshTokenCookie(response, newRefreshToken);
// 인증 정보 설정
UserDetails userDetails = memberService.loadUserByUsername(username);
if (userDetails != null) {
UsernamePasswordAuthenticationToken authentication =
new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());
authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
SecurityContextHolder.getContext().setAuthentication(authentication);
}
log.info("토큰 자동 갱신 성공: {}", username);
filterChain.doFilter(request, response);
return;
}
try {
// Access Token 유효성 검사
if (jwtUtils.validateAccessToken(accessToken)) {
String username = jwtUtils.extractUsername(accessToken);
UserDetails userDetails = memberService.loadUserByUsername(username);
if (userDetails != null) {
UsernamePasswordAuthenticationToken authentication =
new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());
authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
SecurityContextHolder.getContext().setAuthentication(authentication);
filterChain.doFilter(request, response);
return;
}
}
// Access Token이 유효하지 않거나 만료된 경우 Refresh Token 검증
if (refreshToken != null && jwtUtils.validateRefreshToken(refreshToken)) {
String username = jwtUtils.extractUsername(refreshToken);
String role = (String) jwtUtils.extractAllClaims(refreshToken).get("role");
// 새로운 Access Token 생성
String newAccessToken = jwtUtils.generateToken(username, role,
Long.parseLong(Objects.requireNonNull(env.getProperty("token.expiration_time_access"))));
// 새로운 Access Token을 응답 헤더에 설정
response.setHeader("Authorization", "Bearer " + newAccessToken);
// Refresh Token 갱신
String newRefreshToken = jwtUtils.refreshTokens(username, role);
jwtUtils.setRefreshTokenCookie(response, newRefreshToken);
// 인증 정보 설정
UserDetails userDetails = memberService.loadUserByUsername(username);
if (userDetails != null) {
UsernamePasswordAuthenticationToken authentication =
new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());
authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
SecurityContextHolder.getContext().setAuthentication(authentication);
}
filterChain.doFilter(request, response);
} else {
sendJsonResponse(response, ApiResponseDto.fail(ApiResponseCode.ALL_TOKEN_INVALID));
}
} catch (Exception e) {
log.error("JWT 토큰 검증 중 오류 발생", e);
sendJsonResponse(response, ApiResponseDto.fail(ApiResponseCode.COMMON_INTERNAL_SERVER_ERROR));
}
// 토큰이 없거나 모두 유효하지 않은 경우
log.warn("유효한 JWT 토큰이 없습니다. URI: {}", request.getRequestURI());
sendJsonResponse(response, ApiResponseDto.fail(ApiResponseCode.JWT_TOKEN_NULL));
}
private void sendJsonResponse(HttpServletResponse response, ApiResponseDto<?> apiResponse) throws IOException {

View File

@@ -15,7 +15,7 @@ import org.springframework.security.web.authentication.UsernamePasswordAuthentic
import com.fasterxml.jackson.databind.ObjectMapper;
import jakarta.servlet.Filter;
import com.bio.bio_backend.domain.base.member.service.MemberService;
import com.bio.bio_backend.global.exception.CustomAuthenticationFailureHandler;
import com.bio.bio_backend.global.exception.JwtAccessDeniedHandler;
@@ -43,10 +43,12 @@ public class WebSecurity {
return filter;
}
private Filter getJwtTokenValidationFilter() {
private JwtTokenValidationFilter getJwtTokenValidationFilter() {
return new JwtTokenValidationFilter(jwtUtils, memberService, env);
}
@Bean
SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
@@ -60,7 +62,7 @@ public class WebSecurity {
http.csrf(AbstractHttpConfigurer::disable) //csrf 비활성화
.authorizeHttpRequests(request -> //request 허용 설정
request
.requestMatchers("/api/auth/**").permitAll() // 인증 관련 엔드포인트 허용
.requestMatchers("/members/register").permitAll()
.requestMatchers("/swagger-ui/**", "/api-docs/**").permitAll() // Swagger 허용
.requestMatchers("/ws/**").permitAll() // WebSocket 허용
.anyRequest().authenticated() // 나머지 요청은 인증 필요
@@ -81,7 +83,7 @@ public class WebSecurity {
http
// 1단계: JWT 토큰 발급 필터 (로그인 요청 처리 및 토큰 발급)
.addFilterBefore(getJwtTokenIssuanceFilter(authenticationManager), UsernamePasswordAuthenticationFilter.class)
// 2단계: JWT 토큰 검증 필터 (모든 요청에 대해 토큰 유효성 검증)
// 2단계: JWT 토큰 검증 필터 (자동 토큰 갱신 포함)
.addFilterBefore(getJwtTokenValidationFilter(), UsernamePasswordAuthenticationFilter.class);
return http.build();