Skip to content

Commit a8b4317

Browse files
authored
Merge pull request #144 from prgrms-web-devcourse-final-project/fix/#143
[Auth] 액세스 토큰 재발급 로직 리팩토링 및 samesite 설정, 도메인 설정
2 parents defb1e6 + fb00d65 commit a8b4317

6 files changed

Lines changed: 78 additions & 59 deletions

File tree

src/main/java/com/back/web7_9_codecrete_be/domain/auth/controller/AuthController.java

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
import com.back.web7_9_codecrete_be.domain.auth.dto.request.LoginRequest;
66
import com.back.web7_9_codecrete_be.domain.auth.dto.request.SignupRequest;
77
import com.back.web7_9_codecrete_be.domain.auth.dto.response.LoginResponse;
8+
import com.back.web7_9_codecrete_be.domain.auth.dto.response.TokenResponse;
89
import com.back.web7_9_codecrete_be.domain.auth.service.AuthService;
910
import com.back.web7_9_codecrete_be.domain.auth.service.TokenService;
1011
import com.back.web7_9_codecrete_be.domain.users.dto.response.UserResponse;
@@ -87,8 +88,8 @@ public RsData<?> getMyInfo() {
8788
@Operation(summary = "액세스 토큰 재발급", description = "리프레시 토큰을 이용하여 새로운 액세스 토큰을 발급합니다.")
8889
@PostMapping("/refresh")
8990
public RsData<?> refresh() {
90-
String newAccessToken = tokenService.reissueAccessToken();
91-
return RsData.success("토큰 재발급 완료", newAccessToken);
91+
TokenResponse response = tokenService.reissueAccessToken();
92+
return RsData.success("토큰 재발급 완료", response);
9293
}
9394

9495
@Operation(summary = "카카오 소셜 로그인", description = "카카오 OAuth 인가 코드를 이용해 로그인/회원가입을 진행합니다.")
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,12 @@
11
package com.back.web7_9_codecrete_be.domain.auth.dto.response;
22

3+
import io.swagger.v3.oas.annotations.media.Schema;
4+
import lombok.AllArgsConstructor;
5+
import lombok.Getter;
6+
7+
@Getter
8+
@AllArgsConstructor
39
public class TokenResponse {
10+
@Schema(description = "액세스 토큰")
11+
private String accessToken;
412
}

src/main/java/com/back/web7_9_codecrete_be/domain/auth/service/TokenService.java

Lines changed: 6 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package com.back.web7_9_codecrete_be.domain.auth.service;
22

3+
import com.back.web7_9_codecrete_be.domain.auth.dto.response.TokenResponse;
34
import com.back.web7_9_codecrete_be.domain.auth.entity.RefreshToken;
45
import com.back.web7_9_codecrete_be.domain.auth.repository.RefreshTokenRedisRepository;
56
import com.back.web7_9_codecrete_be.domain.users.entity.User;
@@ -28,8 +29,8 @@ public void issueTokens(User user) {
2829
String access = jwtTokenProvider.generateAccessToken(user.getEmail());
2930
String refresh = jwtTokenProvider.generateRefreshToken(user.getEmail());
3031

31-
rq.setCookie("ACCESS_TOKEN", access, (int) jwtProperties.getAccessTokenExpiration());
32-
rq.setCookie("REFRESH_TOKEN", refresh, (int) jwtProperties.getRefreshTokenExpiration());
32+
rq.setCookie("ACCESS_TOKEN", access, jwtProperties.getAccessTokenExpiration());
33+
rq.setCookie("REFRESH_TOKEN", refresh, jwtProperties.getRefreshTokenExpiration());
3334

3435
refreshTokenRedisRepository.save(
3536
new RefreshToken(
@@ -47,18 +48,15 @@ public void removeTokens(User user) {
4748
refreshTokenRedisRepository.deleteByUserId(user.getId());
4849
}
4950

50-
public String reissueAccessToken() {
51+
public TokenResponse reissueAccessToken() {
5152

52-
// Refresh Token 쿠키 찾기
5353
String refresh = rq.getCookieValue("REFRESH_TOKEN");
5454
if (refresh == null) {
5555
throw new BusinessException(AuthErrorCode.TOKEN_MISSING);
5656
}
5757

58-
// RefreshToken 검증
5958
jwtTokenProvider.validateToken(refresh);
6059

61-
// RefreshToken에서 email(Subject) 추출
6260
String email = jwtTokenProvider.getEmailFromToken(refresh);
6361

6462
User user = userRepository.findByEmail(email)
@@ -69,17 +67,14 @@ public String reissueAccessToken() {
6967
}
7068

7169
String savedRefresh = refreshTokenRedisRepository.findByUserId(user.getId());
72-
7370
if (savedRefresh == null || !savedRefresh.equals(refresh)) {
7471
throw new BusinessException(AuthErrorCode.INVALID_TOKEN);
7572
}
7673

77-
// AccessToken 재발급
7874
String newAccess = jwtTokenProvider.generateAccessToken(email);
7975

80-
// AccessToken 쿠키에 다시 저장
81-
rq.setCookie("ACCESS_TOKEN", newAccess, (int) jwtProperties.getAccessTokenExpiration());
76+
rq.setCookie("ACCESS_TOKEN", newAccess, jwtProperties.getAccessTokenExpiration());
8277

83-
return newAccess;
78+
return new TokenResponse(newAccess);
8479
}
8580
}

src/main/java/com/back/web7_9_codecrete_be/global/rq/Rq.java

Lines changed: 41 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@
77
import jakarta.servlet.http.Cookie;
88
import jakarta.servlet.http.HttpServletRequest;
99
import jakarta.servlet.http.HttpServletResponse;
10+
import org.springframework.beans.factory.annotation.Value;
11+
import org.springframework.http.ResponseCookie;
1012
import org.springframework.security.core.Authentication;
1113
import org.springframework.security.core.context.SecurityContextHolder;
1214
import org.springframework.stereotype.Component;
@@ -16,10 +18,15 @@ public class Rq {
1618

1719
private final HttpServletRequest request;
1820
private final HttpServletResponse response;
21+
private final boolean isProd;
1922

20-
public Rq(HttpServletRequest request, HttpServletResponse response) {
23+
public Rq(HttpServletRequest request,
24+
HttpServletResponse response,
25+
@Value("${spring.profiles.active:local}") String activeProfile)
26+
{
2127
this.request = request;
2228
this.response = response;
29+
this.isProd = activeProfile.equals("prod");
2330
}
2431

2532
// 현재 인증된 사용자 정보 가져오기
@@ -39,27 +46,47 @@ public User getUser() {
3946
}
4047

4148
// 쿠키 설정
42-
public void setCookie(String name, String value, int maxAge) {
49+
public void setCookie(String name, String value, long maxAge) {
4350
String safeValue = value != null ? value : "";
4451

45-
Cookie cookie = new Cookie(name, safeValue);
46-
cookie.setPath("/");
47-
cookie.setHttpOnly(true);
48-
cookie.setMaxAge(maxAge);
49-
cookie.setSecure(true); // https 환경 권장 옵션
52+
ResponseCookie.ResponseCookieBuilder builder = ResponseCookie.from(name, safeValue)
53+
.path("/")
54+
.httpOnly(true)
55+
.maxAge(maxAge);
56+
57+
if (isProd) {
58+
builder
59+
.secure(true)
60+
.sameSite("None")
61+
.domain(".naeconcertbutakhae.shop");
62+
} else {
63+
builder
64+
.secure(false)
65+
.sameSite("Lax");
66+
}
5067

51-
response.addCookie(cookie);
68+
response.addHeader("Set-Cookie", builder.build().toString());
5269
}
5370

5471
// 쿠키 제거
5572
public void removeCookie(String name) {
56-
Cookie cookie = new Cookie(name, null);
57-
cookie.setPath("/");
58-
cookie.setMaxAge(0);
59-
cookie.setHttpOnly(true);
60-
cookie.setSecure(true);
73+
ResponseCookie.ResponseCookieBuilder builder = ResponseCookie.from(name, "")
74+
.path("/")
75+
.httpOnly(true)
76+
.maxAge(0);
77+
78+
if (isProd) {
79+
builder
80+
.secure(true)
81+
.sameSite("None")
82+
.domain(".naeconcertbutakhae.shop");
83+
} else {
84+
builder
85+
.secure(false)
86+
.sameSite("Lax");
87+
}
6188

62-
response.addCookie(cookie);
89+
response.addHeader("Set-Cookie", builder.build().toString());
6390
}
6491

6592
public String getCookieValue(String name) {

src/main/java/com/back/web7_9_codecrete_be/global/security/JwtAuthenticationFilter.java

Lines changed: 15 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
package com.back.web7_9_codecrete_be.global.security;
22

33
import com.back.web7_9_codecrete_be.domain.auth.service.TokenService;
4-
import com.back.web7_9_codecrete_be.global.error.code.AuthErrorCode;
54
import com.back.web7_9_codecrete_be.global.error.exception.BusinessException;
65
import jakarta.servlet.FilterChain;
76
import jakarta.servlet.ServletException;
@@ -33,42 +32,33 @@ protected void doFilterInternal(HttpServletRequest request,
3332
FilterChain filterChain)
3433
throws ServletException, IOException {
3534

36-
String accessToken = resolveToken(request);
35+
String accessToken = resolveToken(request);
36+
37+
log.info("[JwtFilter] URI = {}", request.getRequestURI());
38+
log.info("[JwtFilter] ACCESS_TOKEN = {}", accessToken);
3739

38-
// Access Token이 있는 경우 우선 검증 시도
3940
if (StringUtils.hasText(accessToken)) {
4041
try {
42+
log.info("[JwtFilter] validating token");
43+
4144
if (jwtTokenProvider.validateToken(accessToken)) {
45+
log.info("[JwtFilter] token valid");
46+
4247
Authentication auth =
4348
jwtTokenProvider.getAuthentication(accessToken);
49+
50+
log.info("[JwtFilter] auth = {}", auth);
51+
4452
SecurityContextHolder.getContext().setAuthentication(auth);
45-
filterChain.doFilter(request, response);
46-
return;
4753
}
4854
} catch (BusinessException e) {
49-
// Access Token 만료가 아닌 경우 재발급 안 함
50-
if (e.getErrorCode() != AuthErrorCode.TOKEN_EXPIRED) {
51-
log.debug("Invalid access token: {}", e.getErrorCode());
52-
filterChain.doFilter(request, response);
53-
return;
54-
}
55-
// TOKEN_EXPIRED 인 경우만 아래 재발급 로직으로 내려감
55+
log.info("[JwtFilter] token invalid: {}", e.getErrorCode());
56+
SecurityContextHolder.clearContext();
5657
}
5758
}
5859

59-
// Access Token이 없거나 / 만료된 경우 Refresh 기반 재발급 시도
60-
try {
61-
String newAccess = tokenService.reissueAccessToken();
62-
63-
Authentication auth =
64-
jwtTokenProvider.getAuthentication(newAccess);
65-
SecurityContextHolder.getContext().setAuthentication(auth);
66-
67-
} catch (BusinessException ex) {
68-
// 재발급 실패 시 SecurityContext 비우기
69-
SecurityContextHolder.clearContext();
70-
log.debug("Access Token 재발급 실패: {}", ex.getErrorCode());
71-
}
60+
log.info("[JwtFilter] SecurityContext = {}",
61+
SecurityContextHolder.getContext().getAuthentication());
7262

7363
filterChain.doFilter(request, response);
7464
}

src/main/java/com/back/web7_9_codecrete_be/global/security/SecurityConfig.java

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
package com.back.web7_9_codecrete_be.global.security;
22

3-
import java.util.List;
4-
3+
import com.back.web7_9_codecrete_be.domain.auth.service.TokenService;
4+
import lombok.RequiredArgsConstructor;
55
import org.springframework.context.annotation.Bean;
66
import org.springframework.context.annotation.Configuration;
77
import org.springframework.security.authentication.AuthenticationManager;
@@ -14,9 +14,7 @@
1414
import org.springframework.web.cors.CorsConfiguration;
1515
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
1616

17-
import com.back.web7_9_codecrete_be.domain.auth.service.TokenService;
18-
19-
import lombok.RequiredArgsConstructor;
17+
import java.util.List;
2018

2119
@Configuration
2220
@RequiredArgsConstructor
@@ -85,7 +83,7 @@ public AuthenticationManager authenticationManager(AuthenticationConfiguration c
8583
public UrlBasedCorsConfigurationSource corsConfigurationSource() {
8684
CorsConfiguration configuration = new CorsConfiguration();
8785

88-
configuration.setAllowedOrigins(List.of("http://localhost:3000", "https://web-6-7-codecrete-fe.vercel.app", "https://www.naeconcertbutakhae.shop"));
86+
configuration.setAllowedOrigins(List.of("http://localhost:3000", "https://*.naeconcertbutakhae.shop"));
8987
configuration.setAllowedMethods(List.of("GET", "POST", "PUT", "DELETE", "PATCH", "OPTIONS"));
9088

9189
configuration.setAllowedHeaders(List.of("*"));
@@ -94,7 +92,7 @@ public UrlBasedCorsConfigurationSource corsConfigurationSource() {
9492
configuration.setAllowCredentials(true);
9593

9694
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
97-
source.registerCorsConfiguration("/api/**", configuration);
95+
source.registerCorsConfiguration("/**", configuration);
9896

9997
return source;
10098
}

0 commit comments

Comments
 (0)