diff --git a/src/main/java/com/bio/bio_backend/domain/base/member/controller/MemberController.java b/src/main/java/com/bio/bio_backend/domain/base/member/controller/MemberController.java index aed8687..9fcc38a 100644 --- a/src/main/java/com/bio/bio_backend/domain/base/member/controller/MemberController.java +++ b/src/main/java/com/bio/bio_backend/domain/base/member/controller/MemberController.java @@ -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.annotation.LogExecution; 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") @@ -33,6 +35,7 @@ public class MemberController { private final MemberService memberService; private final MemberMapper memberMapper; + private final JwtUtils jwtUtils; @LogExecution("회원 등록") @Operation(summary = "회원 등록", description = "새로운 회원을 등록합니다.") @@ -57,10 +60,14 @@ public class MemberController { @ApiResponse(responseCode = "401", description = "인증 실패", content = @Content(schema = @Schema(implementation = ApiResponseDto.class))) }) @PostMapping("/logout") - public ResponseEntity> logout() { + public ResponseEntity> logout(HttpServletResponse response) { try { String userId = SecurityUtils.getCurrentUserId(); memberService.deleteRefreshToken(userId); + + // 모든 토큰 쿠키 삭제 + jwtUtils.deleteAllTokenCookies(response); + log.info("사용자 로그아웃 완료: {}", userId); return ResponseEntity.ok(ApiResponseDto.success(ApiResponseCode.COMMON_SUCCESS)); diff --git a/src/main/java/com/bio/bio_backend/global/config/CorsConfig.java b/src/main/java/com/bio/bio_backend/global/config/CorsConfig.java index 68ff624..8642df5 100644 --- a/src/main/java/com/bio/bio_backend/global/config/CorsConfig.java +++ b/src/main/java/com/bio/bio_backend/global/config/CorsConfig.java @@ -16,8 +16,6 @@ public class CorsConfig { config.addAllowedHeader("*"); config.addAllowedMethod("*"); config.setAllowCredentials(true); - - config.addExposedHeader("Authorization"); source.registerCorsConfiguration("/**", config); return new CorsFilter(source); diff --git a/src/main/java/com/bio/bio_backend/global/filter/JwtTokenIssuanceFilter.java b/src/main/java/com/bio/bio_backend/global/filter/JwtTokenIssuanceFilter.java index 1b0efd8..1f637f3 100644 --- a/src/main/java/com/bio/bio_backend/global/filter/JwtTokenIssuanceFilter.java +++ b/src/main/java/com/bio/bio_backend/global/filter/JwtTokenIssuanceFilter.java @@ -77,8 +77,8 @@ public class JwtTokenIssuanceFilter extends UsernamePasswordAuthenticationFilter // Refresh 토큰 쿠키 저장 jwtUtils.setRefreshTokenCookie(response, refreshToken); - // Access 토큰 전달 - response.setHeader("Authorization", "Bearer " + accessToken); + // Access 토큰 쿠키 저장 + jwtUtils.setAccessTokenCookie(response, accessToken); SecurityContextHolderStrategy contextHolder = SecurityContextHolder.getContextHolderStrategy(); SecurityContext context = contextHolder.createEmptyContext(); diff --git a/src/main/java/com/bio/bio_backend/global/filter/JwtTokenValidationFilter.java b/src/main/java/com/bio/bio_backend/global/filter/JwtTokenValidationFilter.java index a4ee234..b3dca92 100644 --- a/src/main/java/com/bio/bio_backend/global/filter/JwtTokenValidationFilter.java +++ b/src/main/java/com/bio/bio_backend/global/filter/JwtTokenValidationFilter.java @@ -92,8 +92,8 @@ public class JwtTokenValidationFilter extends OncePerRequestFilter { // 새로운 Access Token 생성 String newAccessToken = jwtUtils.generateToken(username, Long.parseLong(Objects.requireNonNull(env.getProperty("token.expiration_time_access")))); - // 새로운 Access Token을 응답 헤더에 설정 - response.setHeader("Authorization", "Bearer " + newAccessToken); + // 새로운 Access Token을 쿠키에 설정 + jwtUtils.setAccessTokenCookie(response, newAccessToken); // Refresh Token 갱신 String newRefreshToken = jwtUtils.createRefreshToken(username, httpUtils.getClientIp()); diff --git a/src/main/java/com/bio/bio_backend/global/utils/JwtUtils.java b/src/main/java/com/bio/bio_backend/global/utils/JwtUtils.java index c3a46b3..3b5f64c 100644 --- a/src/main/java/com/bio/bio_backend/global/utils/JwtUtils.java +++ b/src/main/java/com/bio/bio_backend/global/utils/JwtUtils.java @@ -13,7 +13,6 @@ import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Value; import org.springframework.core.env.Environment; import org.springframework.stereotype.Component; -import org.springframework.util.StringUtils; import javax.crypto.SecretKey; import java.util.Date; @@ -49,7 +48,7 @@ public class JwtUtils { public String generateToken(String username, String clientIp, long expirationTime) { return Jwts.builder() .subject(username) - .claim("ip", clientIp) // IP 정보 추가 + .claim("ip", clientIp) .issuedAt(new Date(System.currentTimeMillis())) .expiration(new Date(System.currentTimeMillis() + expirationTime)) .signWith(getSigningKey()) @@ -123,8 +122,6 @@ public class JwtUtils { public String extractUsername(String token) { return extractAllClaims(token).getSubject(); } - - public Claims extractAllClaims(String token) { return Jwts.parser() @@ -133,14 +130,19 @@ public class JwtUtils { .parseSignedClaims(token).getPayload(); } + // Access Token을 쿠키에서 추출 public String extractAccessJwtFromRequest(HttpServletRequest request) { - String bearerToken = request.getHeader("Authorization"); - if (StringUtils.hasText(bearerToken) && bearerToken.startsWith("Bearer ")) { - return bearerToken.substring(7); // "Bearer " 제거 + if (request.getCookies() != null) { + for (Cookie cookie : request.getCookies()) { + if ("AccessToken".equals(cookie.getName())) { + return cookie.getValue(); + } + } } - return bearerToken; + return null; } + // Refresh Token을 쿠키에서 추출 public String extractRefreshJwtFromCookie(HttpServletRequest request) { if (request.getCookies() != null) { for (Cookie cookie : request.getCookies()) { @@ -154,12 +156,39 @@ public class JwtUtils { // Refresh Token 쿠키 설정 public void setRefreshTokenCookie(HttpServletResponse response, String refreshToken) { - Cookie refreshTokenCookie = new Cookie("RefreshToken", refreshToken); - refreshTokenCookie.setHttpOnly(true); - refreshTokenCookie.setSecure(false); - refreshTokenCookie.setPath("/"); - refreshTokenCookie.setMaxAge(Integer.parseInt(env.getProperty("token.expiration_time_refresh"))); - - response.addCookie(refreshTokenCookie); + setCookie(response, "RefreshToken", refreshToken, + Integer.parseInt(env.getProperty("token.expiration_time_refresh")) / 1000); + } + + // 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); } }