[JWT 개선] MemberController에서 로그아웃 시 모든 토큰 쿠키 삭제 기능 추가 및 JwtUtils에 Access/Refresh Token 쿠키 설정 및 삭제 메서드 추가. JwtTokenIssuanceFilter와 JwtTokenValidationFilter에서 Access Token을 헤더 대신 쿠키에 저장하도록 변경.
This commit is contained in:
		@@ -22,6 +22,8 @@ import io.swagger.v3.oas.annotations.media.Schema;
 | 
				
			|||||||
import com.bio.bio_backend.global.constants.ApiResponseCode;
 | 
					import com.bio.bio_backend.global.constants.ApiResponseCode;
 | 
				
			||||||
import com.bio.bio_backend.global.annotation.LogExecution;
 | 
					import com.bio.bio_backend.global.annotation.LogExecution;
 | 
				
			||||||
import com.bio.bio_backend.global.utils.SecurityUtils;
 | 
					import com.bio.bio_backend.global.utils.SecurityUtils;
 | 
				
			||||||
 | 
					import com.bio.bio_backend.global.utils.JwtUtils;
 | 
				
			||||||
 | 
					import jakarta.servlet.http.HttpServletResponse;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@Tag(name = "Member", description = "회원 관련 API")
 | 
					@Tag(name = "Member", description = "회원 관련 API")
 | 
				
			||||||
@@ -33,6 +35,7 @@ public class MemberController {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    private final MemberService memberService;
 | 
					    private final MemberService memberService;
 | 
				
			||||||
    private final MemberMapper memberMapper;
 | 
					    private final MemberMapper memberMapper;
 | 
				
			||||||
 | 
					    private final JwtUtils jwtUtils;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    @LogExecution("회원 등록")
 | 
					    @LogExecution("회원 등록")
 | 
				
			||||||
    @Operation(summary = "회원 등록", description = "새로운 회원을 등록합니다.")
 | 
					    @Operation(summary = "회원 등록", description = "새로운 회원을 등록합니다.")
 | 
				
			||||||
@@ -57,10 +60,14 @@ public class MemberController {
 | 
				
			|||||||
        @ApiResponse(responseCode = "401", description = "인증 실패", content = @Content(schema = @Schema(implementation = ApiResponseDto.class)))
 | 
					        @ApiResponse(responseCode = "401", description = "인증 실패", content = @Content(schema = @Schema(implementation = ApiResponseDto.class)))
 | 
				
			||||||
    })
 | 
					    })
 | 
				
			||||||
    @PostMapping("/logout")
 | 
					    @PostMapping("/logout")
 | 
				
			||||||
    public ResponseEntity<ApiResponseDto<Void>> logout() {
 | 
					    public ResponseEntity<ApiResponseDto<Void>> logout(HttpServletResponse response) {
 | 
				
			||||||
        try {
 | 
					        try {
 | 
				
			||||||
            String userId = SecurityUtils.getCurrentUserId();
 | 
					            String userId = SecurityUtils.getCurrentUserId();
 | 
				
			||||||
            memberService.deleteRefreshToken(userId);
 | 
					            memberService.deleteRefreshToken(userId);
 | 
				
			||||||
 | 
					            
 | 
				
			||||||
 | 
					            // 모든 토큰 쿠키 삭제
 | 
				
			||||||
 | 
					            jwtUtils.deleteAllTokenCookies(response);
 | 
				
			||||||
 | 
					            
 | 
				
			||||||
            log.info("사용자 로그아웃 완료: {}", userId);
 | 
					            log.info("사용자 로그아웃 완료: {}", userId);
 | 
				
			||||||
            
 | 
					            
 | 
				
			||||||
            return ResponseEntity.ok(ApiResponseDto.success(ApiResponseCode.COMMON_SUCCESS));
 | 
					            return ResponseEntity.ok(ApiResponseDto.success(ApiResponseCode.COMMON_SUCCESS));
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -16,8 +16,6 @@ public class CorsConfig {
 | 
				
			|||||||
        config.addAllowedHeader("*");
 | 
					        config.addAllowedHeader("*");
 | 
				
			||||||
        config.addAllowedMethod("*");
 | 
					        config.addAllowedMethod("*");
 | 
				
			||||||
        config.setAllowCredentials(true);
 | 
					        config.setAllowCredentials(true);
 | 
				
			||||||
 | 
					 | 
				
			||||||
        config.addExposedHeader("Authorization");
 | 
					 | 
				
			||||||
        source.registerCorsConfiguration("/**", config);
 | 
					        source.registerCorsConfiguration("/**", config);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        return new CorsFilter(source);
 | 
					        return new CorsFilter(source);
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -77,8 +77,8 @@ public class JwtTokenIssuanceFilter extends UsernamePasswordAuthenticationFilter
 | 
				
			|||||||
        // Refresh 토큰 쿠키 저장
 | 
					        // Refresh 토큰 쿠키 저장
 | 
				
			||||||
        jwtUtils.setRefreshTokenCookie(response, refreshToken);
 | 
					        jwtUtils.setRefreshTokenCookie(response, refreshToken);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        // Access 토큰 전달
 | 
					        // Access 토큰 쿠키 저장
 | 
				
			||||||
        response.setHeader("Authorization", "Bearer " + accessToken);
 | 
					        jwtUtils.setAccessTokenCookie(response, accessToken);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        SecurityContextHolderStrategy contextHolder = SecurityContextHolder.getContextHolderStrategy();
 | 
					        SecurityContextHolderStrategy contextHolder = SecurityContextHolder.getContextHolderStrategy();
 | 
				
			||||||
        SecurityContext context = contextHolder.createEmptyContext();
 | 
					        SecurityContext context = contextHolder.createEmptyContext();
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -92,8 +92,8 @@ public class JwtTokenValidationFilter extends OncePerRequestFilter {
 | 
				
			|||||||
                // 새로운 Access Token 생성
 | 
					                // 새로운 Access Token 생성
 | 
				
			||||||
                String newAccessToken = jwtUtils.generateToken(username, Long.parseLong(Objects.requireNonNull(env.getProperty("token.expiration_time_access"))));
 | 
					                String newAccessToken = jwtUtils.generateToken(username, Long.parseLong(Objects.requireNonNull(env.getProperty("token.expiration_time_access"))));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                // 새로운 Access Token을 응답 헤더에 설정
 | 
					                // 새로운 Access Token을 쿠키에 설정
 | 
				
			||||||
                response.setHeader("Authorization", "Bearer " + newAccessToken);
 | 
					                jwtUtils.setAccessTokenCookie(response, newAccessToken);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                // Refresh Token 갱신
 | 
					                // Refresh Token 갱신
 | 
				
			||||||
                String newRefreshToken = jwtUtils.createRefreshToken(username, httpUtils.getClientIp());
 | 
					                String newRefreshToken = jwtUtils.createRefreshToken(username, httpUtils.getClientIp());
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -13,7 +13,6 @@ import lombok.extern.slf4j.Slf4j;
 | 
				
			|||||||
import org.springframework.beans.factory.annotation.Value;
 | 
					import org.springframework.beans.factory.annotation.Value;
 | 
				
			||||||
import org.springframework.core.env.Environment;
 | 
					import org.springframework.core.env.Environment;
 | 
				
			||||||
import org.springframework.stereotype.Component;
 | 
					import org.springframework.stereotype.Component;
 | 
				
			||||||
import org.springframework.util.StringUtils;
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
import javax.crypto.SecretKey;
 | 
					import javax.crypto.SecretKey;
 | 
				
			||||||
import java.util.Date;
 | 
					import java.util.Date;
 | 
				
			||||||
@@ -49,7 +48,7 @@ public class JwtUtils {
 | 
				
			|||||||
    public String generateToken(String username, String clientIp, long expirationTime) {
 | 
					    public String generateToken(String username, String clientIp, long expirationTime) {
 | 
				
			||||||
       return Jwts.builder()
 | 
					       return Jwts.builder()
 | 
				
			||||||
               .subject(username)
 | 
					               .subject(username)
 | 
				
			||||||
               .claim("ip", clientIp)  // IP 정보 추가
 | 
					               .claim("ip", clientIp)
 | 
				
			||||||
               .issuedAt(new Date(System.currentTimeMillis()))
 | 
					               .issuedAt(new Date(System.currentTimeMillis()))
 | 
				
			||||||
               .expiration(new Date(System.currentTimeMillis() + expirationTime))
 | 
					               .expiration(new Date(System.currentTimeMillis() + expirationTime))
 | 
				
			||||||
               .signWith(getSigningKey())
 | 
					               .signWith(getSigningKey())
 | 
				
			||||||
@@ -124,8 +123,6 @@ public class JwtUtils {
 | 
				
			|||||||
        return extractAllClaims(token).getSubject();
 | 
					        return extractAllClaims(token).getSubject();
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    public Claims extractAllClaims(String token) {
 | 
					    public Claims extractAllClaims(String token) {
 | 
				
			||||||
        return Jwts.parser()
 | 
					        return Jwts.parser()
 | 
				
			||||||
                .verifyWith(getSigningKey())
 | 
					                .verifyWith(getSigningKey())
 | 
				
			||||||
@@ -133,14 +130,19 @@ public class JwtUtils {
 | 
				
			|||||||
                .parseSignedClaims(token).getPayload();
 | 
					                .parseSignedClaims(token).getPayload();
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // Access Token을 쿠키에서 추출
 | 
				
			||||||
    public String extractAccessJwtFromRequest(HttpServletRequest request) {
 | 
					    public String extractAccessJwtFromRequest(HttpServletRequest request) {
 | 
				
			||||||
        String bearerToken = request.getHeader("Authorization");
 | 
					        if (request.getCookies() != null) {
 | 
				
			||||||
        if (StringUtils.hasText(bearerToken) && bearerToken.startsWith("Bearer ")) {
 | 
					            for (Cookie cookie : request.getCookies()) {
 | 
				
			||||||
            return bearerToken.substring(7);    // "Bearer " 제거
 | 
					                if ("AccessToken".equals(cookie.getName())) {
 | 
				
			||||||
 | 
					                    return cookie.getValue();
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
        return bearerToken;
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        return null;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // Refresh Token을 쿠키에서 추출
 | 
				
			||||||
    public String extractRefreshJwtFromCookie(HttpServletRequest request) {
 | 
					    public String extractRefreshJwtFromCookie(HttpServletRequest request) {
 | 
				
			||||||
        if (request.getCookies() != null) {
 | 
					        if (request.getCookies() != null) {
 | 
				
			||||||
            for (Cookie cookie : request.getCookies()) {
 | 
					            for (Cookie cookie : request.getCookies()) {
 | 
				
			||||||
@@ -154,12 +156,39 @@ public class JwtUtils {
 | 
				
			|||||||
    
 | 
					    
 | 
				
			||||||
    // Refresh Token 쿠키 설정
 | 
					    // Refresh Token 쿠키 설정
 | 
				
			||||||
    public void setRefreshTokenCookie(HttpServletResponse response, String refreshToken) {
 | 
					    public void setRefreshTokenCookie(HttpServletResponse response, String refreshToken) {
 | 
				
			||||||
        Cookie refreshTokenCookie = new Cookie("RefreshToken", refreshToken);
 | 
					        setCookie(response, "RefreshToken", refreshToken, 
 | 
				
			||||||
        refreshTokenCookie.setHttpOnly(true);
 | 
					                Integer.parseInt(env.getProperty("token.expiration_time_refresh")) / 1000);
 | 
				
			||||||
        refreshTokenCookie.setSecure(false);
 | 
					    }
 | 
				
			||||||
        refreshTokenCookie.setPath("/");
 | 
					 | 
				
			||||||
        refreshTokenCookie.setMaxAge(Integer.parseInt(env.getProperty("token.expiration_time_refresh")));
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
        response.addCookie(refreshTokenCookie);
 | 
					    // Access Token 쿠키 설정
 | 
				
			||||||
 | 
					    public void setAccessTokenCookie(HttpServletResponse response, String accessToken) {
 | 
				
			||||||
 | 
					        setCookie(response, "AccessToken", accessToken, 
 | 
				
			||||||
 | 
					                Integer.parseInt(env.getProperty("token.expiration_time_access")) / 1000);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // Access Token 쿠키 삭제
 | 
				
			||||||
 | 
					    public void deleteAccessTokenCookie(HttpServletResponse response) {
 | 
				
			||||||
 | 
					        setCookie(response, "AccessToken", "", 0);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // Refresh Token 쿠키 삭제
 | 
				
			||||||
 | 
					    public void deleteRefreshTokenCookie(HttpServletResponse response) {
 | 
				
			||||||
 | 
					        setCookie(response, "RefreshToken", "", 0);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // 모든 토큰 쿠키 삭제
 | 
				
			||||||
 | 
					    public void deleteAllTokenCookies(HttpServletResponse response) {
 | 
				
			||||||
 | 
					        deleteAccessTokenCookie(response);
 | 
				
			||||||
 | 
					        deleteRefreshTokenCookie(response);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // 쿠키 설정 헬퍼 메서드
 | 
				
			||||||
 | 
					    private void setCookie(HttpServletResponse response, String name, String value, int maxAge) {
 | 
				
			||||||
 | 
					        Cookie cookie = new Cookie(name, value);
 | 
				
			||||||
 | 
					        cookie.setHttpOnly(true);
 | 
				
			||||||
 | 
					        cookie.setSecure(false);
 | 
				
			||||||
 | 
					        cookie.setPath("/");
 | 
				
			||||||
 | 
					        cookie.setMaxAge(maxAge);
 | 
				
			||||||
 | 
					        response.addCookie(cookie);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user