[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