[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