From 09e06cd10b0084cb67eed70d0b9654cadf960033 Mon Sep 17 00:00:00 2001 From: sohot8653 Date: Thu, 21 Aug 2025 17:06:14 +0900 Subject: [PATCH] =?UTF-8?q?[JWT=20=ED=95=84=ED=84=B0=20=EA=B0=9C=EC=84=A0]?= =?UTF-8?q?=20JwtTokenValidationFilter=EC=97=90=EC=84=9C=20JWT=20=EA=B2=80?= =?UTF-8?q?=EC=A6=9D=20=EB=A1=9C=EC=A7=81=EC=9D=84=20=EA=B0=9C=EC=84=A0?= =?UTF-8?q?=ED=95=98=EC=97=AC=20Access=20Token=20=EC=9C=A0=ED=9A=A8?= =?UTF-8?q?=EC=84=B1=20=EA=B2=80=EC=82=AC=20=ED=9B=84=20=EC=9E=90=EB=8F=99?= =?UTF-8?q?=EC=9C=BC=EB=A1=9C=20Refresh=20Token=EC=9D=84=20=EA=B0=B1?= =?UTF-8?q?=EC=8B=A0=ED=95=98=EB=8A=94=20=EA=B8=B0=EB=8A=A5=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80.=20WebSecurity=EC=97=90=EC=84=9C=20=ED=95=84=ED=84=B0?= =?UTF-8?q?=20=EC=84=A4=EB=AA=85=EC=9D=84=20=EC=97=85=EB=8D=B0=EC=9D=B4?= =?UTF-8?q?=ED=8A=B8=ED=95=98=EA=B3=A0,=20=ED=97=88=EC=9A=A9=20=EA=B2=BD?= =?UTF-8?q?=EB=A1=9C=20=EC=84=A4=EC=A0=95=EC=9D=84=20=EC=A0=95=EB=A6=AC?= =?UTF-8?q?=ED=95=98=EC=97=AC=20=EB=B3=B4=EC=95=88=20=EC=84=A4=EC=A0=95?= =?UTF-8?q?=EC=9D=84=20=EA=B0=95=ED=99=94.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../security/JwtTokenValidationFilter.java | 110 ++++++++++-------- .../global/security/WebSecurity.java | 10 +- 2 files changed, 65 insertions(+), 55 deletions(-) diff --git a/src/main/java/com/bio/bio_backend/global/security/JwtTokenValidationFilter.java b/src/main/java/com/bio/bio_backend/global/security/JwtTokenValidationFilter.java index 7fabe85..5fecd4c 100644 --- a/src/main/java/com/bio/bio_backend/global/security/JwtTokenValidationFilter.java +++ b/src/main/java/com/bio/bio_backend/global/security/JwtTokenValidationFilter.java @@ -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 { diff --git a/src/main/java/com/bio/bio_backend/global/security/WebSecurity.java b/src/main/java/com/bio/bio_backend/global/security/WebSecurity.java index 477b2c5..95362ac 100644 --- a/src/main/java/com/bio/bio_backend/global/security/WebSecurity.java +++ b/src/main/java/com/bio/bio_backend/global/security/WebSecurity.java @@ -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,9 +43,11 @@ 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();