[JWT 필터 개선] JwtTokenValidationFilter에서 JWT 검증 로직을 개선하여 Access Token 유효성 검사 후 자동으로 Refresh Token을 갱신하는 기능 추가. WebSecurity에서 필터 설명을 업데이트하고, 허용 경로 설정을 정리하여 보안 설정을 강화.
This commit is contained in:
@@ -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 {
|
||||
|
@@ -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();
|
||||
|
Reference in New Issue
Block a user