11package com .ai .lawyer .global .jwt ;
22
3+ import com .ai .lawyer .domain .member .entity .Member ;
4+ import com .ai .lawyer .domain .member .repositories .MemberRepository ;
35import jakarta .servlet .FilterChain ;
46import jakarta .servlet .ServletException ;
57import jakarta .servlet .http .HttpServletRequest ;
@@ -23,17 +25,35 @@ public class JwtAuthenticationFilter extends OncePerRequestFilter {
2325
2426 private final TokenProvider tokenProvider ;
2527 private final CookieUtil cookieUtil ;
28+ private final MemberRepository memberRepository ;
2629
2730 @ Override
2831 protected void doFilterInternal (@ Nullable HttpServletRequest request , @ Nullable HttpServletResponse response , @ Nullable FilterChain filterChain )
2932 throws ServletException , IOException {
3033
31- if (request != null ) {
32- String accessToken = cookieUtil .getAccessTokenFromCookies (request );
34+ if (request != null && response != null ) {
35+ // 1. Authorization 헤더에서 Bearer 토큰 추출 시도 (우선순위 1)
36+ String accessToken = extractTokenFromAuthorizationHeader (request );
37+ boolean fromHeader = accessToken != null ;
38+
39+ // 2. Authorization 헤더에 없으면 쿠키에서 토큰 추출 (우선순위 2)
40+ if (accessToken == null ) {
41+ accessToken = cookieUtil .getAccessTokenFromCookies (request );
42+ }
3343
3444 // JWT 액세스 토큰 검증 및 인증 처리
35- if (accessToken != null && tokenProvider .validateToken (accessToken )) {
36- setAuthentication (accessToken );
45+ if (accessToken != null ) {
46+ TokenProvider .TokenValidationResult validationResult = tokenProvider .validateTokenWithResult (accessToken );
47+
48+ if (validationResult == TokenProvider .TokenValidationResult .VALID ) {
49+ // 유효한 토큰인 경우 인증 처리
50+ setAuthentication (accessToken );
51+ } else if (validationResult == TokenProvider .TokenValidationResult .EXPIRED && !fromHeader ) {
52+ // 만료된 토큰이고 쿠키에서 왔을 경우에만 자동 갱신 시도
53+ // (Authorization 헤더 토큰은 클라이언트가 직접 관리해야 함)
54+ tryAutoRefreshToken (request , response , accessToken );
55+ }
56+ // INVALID인 경우 아무 처리 하지 않음 (인증되지 않은 상태로 진행)
3757 }
3858 }
3959
@@ -42,6 +62,19 @@ protected void doFilterInternal(@Nullable HttpServletRequest request, @Nullable
4262 }
4363 }
4464
65+ /**
66+ * Authorization 헤더에서 Bearer 토큰을 추출합니다.
67+ * @param request HTTP 요청
68+ * @return Bearer 토큰 값 또는 null
69+ */
70+ private String extractTokenFromAuthorizationHeader (HttpServletRequest request ) {
71+ String authHeader = request .getHeader ("Authorization" );
72+ if (authHeader != null && authHeader .startsWith ("Bearer " )) {
73+ return authHeader .substring (7 ); // "Bearer " 제거
74+ }
75+ return null ;
76+ }
77+
4578 /**
4679 * JWT 토큰에서 사용자 정보를 추출하여 Spring Security 인증 객체를 설정합니다.
4780 * @param token JWT 액세스 토큰
@@ -70,6 +103,61 @@ private void setAuthentication(String token) {
70103 }
71104 }
72105
106+ /**
107+ * 만료된 액세스 토큰으로 자동 갱신을 시도합니다.
108+ * @param request HTTP 요청
109+ * @param response HTTP 응답
110+ * @param expiredAccessToken 만료된 액세스 토큰
111+ */
112+ private void tryAutoRefreshToken (HttpServletRequest request , HttpServletResponse response , String expiredAccessToken ) {
113+ try {
114+ // 1. 만료된 토큰에서 loginId 추출
115+ String loginId = tokenProvider .getLoginIdFromExpiredToken (expiredAccessToken );
116+ if (loginId == null ) {
117+ log .warn ("만료된 토큰에서 loginId 추출 실패" );
118+ return ;
119+ }
120+
121+ // 2. 쿠키에서 리프레시 토큰 추출
122+ String refreshToken = cookieUtil .getRefreshTokenFromCookies (request );
123+ if (refreshToken == null ) {
124+ log .info ("리프레시 토큰이 없어 자동 갱신 불가: {}" , loginId );
125+ return ;
126+ }
127+
128+ // 3. 리프레시 토큰 유효성 검증
129+ if (!tokenProvider .validateRefreshToken (loginId , refreshToken )) {
130+ log .info ("유효하지 않은 리프레시 토큰으로 자동 갱신 불가: {}" , loginId );
131+ return ;
132+ }
133+
134+ // 4. 회원 정보 조회
135+ Member member = memberRepository .findByLoginId (loginId ).orElse (null );
136+ if (member == null ) {
137+ log .warn ("존재하지 않는 회원으로 자동 갱신 불가: {}" , loginId );
138+ return ;
139+ }
140+
141+ // 5. RTR(Refresh Token Rotation) 패턴: 기존 리프레시 토큰 삭제
142+ tokenProvider .deleteRefreshToken (loginId );
143+
144+ // 6. 새로운 액세스 토큰과 리프레시 토큰 생성
145+ String newAccessToken = tokenProvider .generateAccessToken (member );
146+ String newRefreshToken = tokenProvider .generateRefreshToken (member );
147+
148+ // 7. 새로운 토큰들을 쿠키에 설정
149+ cookieUtil .setTokenCookies (response , newAccessToken , newRefreshToken );
150+
151+ // 8. 새로운 액세스 토큰으로 인증 설정
152+ setAuthentication (newAccessToken );
153+
154+ log .info ("액세스 토큰 자동 갱신 성공: {}" , loginId );
155+
156+ } catch (Exception e ) {
157+ log .warn ("액세스 토큰 자동 갱신 실패: {}" , e .getMessage ());
158+ }
159+ }
160+
73161 /**
74162 * JWT 인증이 필요하지 않은 경로들을 필터링에서 제외합니다.
75163 * @param request HTTP 요청
0 commit comments