Skip to content

Commit 6e073f8

Browse files
authored
[refactor] CustomAuthenticationFilter 와 HttpRequestContext 에 분산된 쿠키 관리 로직을 CookieManager 에서 통합 관리
1 parent 236df69 commit 6e073f8

8 files changed

Lines changed: 171 additions & 89 deletions

File tree

backend/src/main/java/com/back/api/auth/service/AuthService.java

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -75,8 +75,8 @@ public AuthResponse login(LoginRequest request) {
7575

7676
JwtDto tokens = authTokenService.generateTokens(user);
7777

78-
requestContext.setCookie("accessToken", tokens.accessToken());
79-
requestContext.setCookie("refreshToken", tokens.refreshToken());
78+
requestContext.setAccessTokenCookie(tokens.accessToken());
79+
requestContext.setRefreshTokenCookie(tokens.refreshToken());
8080

8181
return buildAuthResponse(user, tokens);
8282
}
@@ -96,8 +96,7 @@ public void logout() {
9696

9797
refreshToken.revoke();
9898

99-
requestContext.deleteCookie("accessToken");
100-
requestContext.deleteCookie("refreshToken");
99+
requestContext.deleteAuthCookies();
101100
}
102101

103102
@Transactional

backend/src/main/java/com/back/api/auth/service/AuthTokenService.java

Lines changed: 15 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -26,10 +26,16 @@ public JwtDto generateTokens(User user) {
2626
String accessToken = jwtProvider.generateAccessToken(user);
2727
String refreshTokenStr = jwtProvider.generateRefreshToken(user);
2828

29+
// === seconds 기준 ===
30+
long accessValiditySeconds = jwtProvider.getAccessTokenValiditySeconds();
31+
long refreshValiditySeconds = jwtProvider.getRefreshTokenValiditySeconds();
32+
33+
// === 현재 시각 ===
2934
long nowEpochMillis = System.currentTimeMillis();
30-
long refreshValidityMillis = jwtProvider.getRefreshTokenValidityMillis();
3135
LocalDateTime issuedAt = LocalDateTime.now();
32-
LocalDateTime expiresAt = issuedAt.plusNanos(refreshValidityMillis * 1_000_000L);
36+
37+
// === DB 저장용 만료 시각 (LocalDateTime) ===
38+
LocalDateTime expiresAt = issuedAt.plusSeconds(refreshValiditySeconds);
3339

3440
String userAgent = requestContext.getUserAgent();
3541
String ip = requestContext.getClientIp();
@@ -46,16 +52,18 @@ public JwtDto generateTokens(User user) {
4652

4753
tokenRepository.save(refreshToken);
4854

49-
long accessValidityMillis = jwtProvider.getAccessTokenValidityMillis();
55+
// === API 응답용 epoch millis ===
56+
long accessExpiresAtMillis = nowEpochMillis + (accessValiditySeconds * 1000);
57+
long refreshExpiresAtMillis = nowEpochMillis + (refreshValiditySeconds * 1000);
5058

5159
return new JwtDto(
5260
JwtDto.BEARER,
5361
accessToken,
54-
nowEpochMillis + accessValidityMillis,
55-
accessValidityMillis,
62+
accessExpiresAtMillis,
63+
accessValiditySeconds * 1000,
5664
refreshTokenStr,
57-
nowEpochMillis + refreshValidityMillis,
58-
refreshValidityMillis
65+
refreshExpiresAtMillis,
66+
refreshValiditySeconds * 1000
5967
);
6068
}
6169

backend/src/main/java/com/back/api/user/service/UserService.java

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -61,8 +61,7 @@ public void deleteUser(long userId) {
6161
refreshTokenRepository.revokeAllByUserId(userId);
6262

6363
// 현재 요청의 쿠키 삭제 (현재 기기 즉시 로그아웃 UX)
64-
requestContext.deleteCookie("accessToken");
65-
requestContext.deleteCookie("refreshToken");
64+
requestContext.deleteAuthCookies();
6665

6766
user.softDelete();
6867
}
Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
package com.back.global.http;
2+
3+
import org.springframework.http.HttpHeaders;
4+
import org.springframework.http.ResponseCookie;
5+
import org.springframework.stereotype.Component;
6+
7+
import com.back.global.properties.CookieProperties;
8+
9+
import jakarta.servlet.http.HttpServletRequest;
10+
import jakarta.servlet.http.HttpServletResponse;
11+
import lombok.RequiredArgsConstructor;
12+
13+
@Component
14+
@RequiredArgsConstructor
15+
public class CookieManager {
16+
17+
public static final String ACCESS_TOKEN_COOKIE = "accessToken";
18+
public static final String REFRESH_TOKEN_COOKIE = "refreshToken";
19+
20+
private final CookieProperties cookieProperties;
21+
22+
public void setAccessToken(
23+
HttpServletRequest request,
24+
HttpServletResponse response,
25+
String token,
26+
long accessTokenDurationSeconds
27+
) {
28+
set(request, response, ACCESS_TOKEN_COOKIE, token, accessTokenDurationSeconds);
29+
}
30+
31+
public void setRefreshToken(
32+
HttpServletRequest request,
33+
HttpServletResponse response,
34+
String token,
35+
long refreshTokenDurationSeconds
36+
) {
37+
set(request, response, REFRESH_TOKEN_COOKIE, token, refreshTokenDurationSeconds);
38+
}
39+
40+
public void deleteAccessToken(HttpServletRequest request, HttpServletResponse response) {
41+
delete(request, response, ACCESS_TOKEN_COOKIE);
42+
}
43+
44+
public void deleteRefreshToken(HttpServletRequest request, HttpServletResponse response) {
45+
delete(request, response, REFRESH_TOKEN_COOKIE);
46+
}
47+
48+
public void deleteAuthCookies(HttpServletRequest request, HttpServletResponse response) {
49+
deleteAccessToken(request, response);
50+
deleteRefreshToken(request, response);
51+
}
52+
53+
public void set(
54+
HttpServletRequest request,
55+
HttpServletResponse response,
56+
String name,
57+
String value,
58+
long maxAgeSeconds
59+
) {
60+
String cookieValue = (value == null) ? "" : value;
61+
62+
boolean delete = cookieValue.isBlank();
63+
long maxAge = delete ? 0 : Math.max(maxAgeSeconds, 0);
64+
65+
String sameSite = cookieProperties.getSameSite();
66+
boolean secure = cookieProperties.isSecure();
67+
68+
ResponseCookie.ResponseCookieBuilder builder = ResponseCookie.from(name, cookieValue)
69+
.httpOnly(true)
70+
.path("/")
71+
.sameSite(sameSite)
72+
.secure(secure)
73+
.maxAge(maxAge);
74+
75+
String domain = cookieProperties.getDomain();
76+
77+
if (domain != null && !domain.isBlank()) {
78+
builder.domain(domain);
79+
}
80+
81+
ResponseCookie cookie = builder.build();
82+
response.addHeader(HttpHeaders.SET_COOKIE, cookie.toString());
83+
}
84+
85+
public void delete(HttpServletRequest request, HttpServletResponse response, String name) {
86+
set(request, response, name, "", 0);
87+
}
88+
89+
private boolean iaHttpsRequest(HttpServletRequest request) {
90+
String proto = request.getHeader("X-Forwarded-Proto");
91+
92+
if (proto != null && !proto.isBlank()) {
93+
return "https".equalsIgnoreCase(proto);
94+
}
95+
96+
return request.isSecure();
97+
}
98+
}

backend/src/main/java/com/back/global/http/HttpRequestContext.java

Lines changed: 29 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -26,15 +26,13 @@ public class HttpRequestContext {
2626
private final HttpServletRequest request;
2727
private final HttpServletResponse response;
2828
private final UserRepository userRepository;
29-
30-
@Value("${custom.site.domain:localhost}")
31-
private String domain;
29+
private final CookieManager cookieManager;
3230

3331
@Value("${custom.jwt.access-token-duration}")
34-
private long accessTokenDurationMillis;
32+
private long accessTokenDurationSeconds;
3533

3634
@Value("${custom.jwt.refresh-token-duration}")
37-
private long refreshTokenDurationMillis;
35+
private long refreshTokenDurationSeconds;
3836

3937
private Authentication getAuthentication() {
4038
return Optional.ofNullable(SecurityContextHolder.getContext().getAuthentication())
@@ -101,42 +99,40 @@ public String getCookieValue(String name, String defaultValue) {
10199
}
102100

103101
public void setCookie(String name, String value) {
104-
if (value == null) {
105-
value = "";
106-
}
107-
108-
Cookie cookie = new Cookie(name, value);
109-
cookie.setPath("/");
110-
cookie.setHttpOnly(true);
102+
long maxAgeSec = 0;
111103

112-
boolean isLocalhostDomain = domain == null
113-
|| domain.isBlank()
114-
|| "localhost".equalsIgnoreCase(domain)
115-
|| "127.0.0.1".equals(domain);
116-
117-
if (!isLocalhostDomain) {
118-
cookie.setDomain(domain);
119-
}
120-
121-
boolean secureRequest = request.isSecure();
122-
// cookie.setSecure(secureRequest && !isLocalhostDomain); TODO 리팩토링
123-
cookie.setSecure(true);
124-
cookie.setAttribute("SameSite", "None");
125-
126-
if (value.isBlank()) {
127-
cookie.setMaxAge(0);
128-
} else {
104+
if (value != null && !value.isBlank()) {
129105
if ("accessToken".equals(name)) {
130-
cookie.setMaxAge((int)accessTokenDurationMillis);
106+
maxAgeSec = accessTokenDurationSeconds;
131107
} else if ("refreshToken".equals(name)) {
132-
cookie.setMaxAge((int)refreshTokenDurationMillis);
108+
maxAgeSec = refreshTokenDurationSeconds;
133109
}
134110
}
135111

136-
response.addCookie(cookie);
112+
cookieManager.set(request, response, name, value, maxAgeSec);
137113
}
138114

139115
public void deleteCookie(String name) {
140-
setCookie(name, null);
116+
cookieManager.delete(request, response, name);
117+
}
118+
119+
public void setAccessTokenCookie(String token) {
120+
cookieManager.setAccessToken(request, response, token, accessTokenDurationSeconds);
121+
}
122+
123+
public void setRefreshTokenCookie(String token) {
124+
cookieManager.setRefreshToken(request, response, token, refreshTokenDurationSeconds);
125+
}
126+
127+
public void deleteAccessTokenCookie() {
128+
cookieManager.deleteAccessToken(request, response);
129+
}
130+
131+
public void deleteRefreshTokenCookie() {
132+
cookieManager.deleteRefreshToken(request, response);
133+
}
134+
135+
public void deleteAuthCookies() {
136+
cookieManager.deleteAuthCookies(request, response);
141137
}
142138
}

backend/src/main/java/com/back/global/security/CustomAuthenticationFilter.java

Lines changed: 13 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,7 @@
55
import java.util.Map;
66
import java.util.Set;
77

8-
import org.springframework.http.HttpHeaders;
9-
import org.springframework.http.ResponseCookie;
8+
import org.springframework.beans.factory.annotation.Value;
109
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
1110
import org.springframework.security.core.Authentication;
1211
import org.springframework.security.core.authority.SimpleGrantedAuthority;
@@ -21,7 +20,7 @@
2120
import com.back.domain.user.repository.UserRepository;
2221
import com.back.global.error.code.AuthErrorCode;
2322
import com.back.global.error.exception.ErrorException;
24-
import com.back.global.properties.CookieProperties;
23+
import com.back.global.http.CookieManager;
2524

2625
import jakarta.servlet.FilterChain;
2726
import jakarta.servlet.ServletException;
@@ -43,19 +42,21 @@ public class CustomAuthenticationFilter extends OncePerRequestFilter {
4342
private final JwtProvider jwtProvider;
4443
private final AuthTokenService tokenService;
4544
private final UserRepository userRepository;
46-
private final CookieProperties cookieProperties;
45+
private final CookieManager cookieManager;
46+
47+
@Value("${jwt.access-token-duration:3600}")
48+
private long accessTokenDurationSeconds;
49+
50+
@Value("${jwt.refresh-token-duration:1209600}")
51+
private long refreshTokenDurationSeconds;
4752

4853
@Override
4954
protected void doFilterInternal(
5055
HttpServletRequest request,
5156
HttpServletResponse response,
5257
FilterChain filterChain
5358
) throws ServletException, IOException {
54-
try {
55-
authenticate(request, response, filterChain);
56-
} catch (ErrorException e) {
57-
throw e;
58-
}
59+
authenticate(request, response, filterChain);
5960
}
6061

6162
private void authenticate(
@@ -153,7 +154,7 @@ private String ensureValidAccessToken(
153154
return accessToken;
154155
}
155156

156-
String refreshToken = resolveCookie(request, "refreshToken");
157+
String refreshToken = resolveCookie(request, CookieManager.REFRESH_TOKEN_COOKIE);
157158

158159
if (refreshToken == null || refreshToken.isBlank() || jwtProvider.isExpired(refreshToken)) {
159160
// refreshToken도 없거나 만료 > 세션 종료 > 재로그인 필요
@@ -172,28 +173,9 @@ private String ensureValidAccessToken(
172173

173174
JwtDto newTokens = tokenService.generateTokens(user);
174175

175-
addCookie(response, "accessToken", newTokens.accessToken());
176-
addCookie(response, "refreshToken", newTokens.refreshToken());
176+
cookieManager.set(request, response, "accessToken", newTokens.accessToken(), accessTokenDurationSeconds);
177+
cookieManager.set(request, response, "refreshToken", newTokens.refreshToken(), refreshTokenDurationSeconds);
177178

178179
return newTokens.accessToken();
179180
}
180-
181-
private void addCookie(
182-
HttpServletResponse response,
183-
String name,
184-
String value
185-
) {
186-
ResponseCookie.ResponseCookieBuilder builder = ResponseCookie.from(name, value)
187-
.httpOnly(true)
188-
.path("/")
189-
.secure(cookieProperties.isSecure())
190-
.sameSite(cookieProperties.getSameSite());
191-
192-
if (cookieProperties.getDomain() != null && !cookieProperties.getDomain().isBlank()) {
193-
builder.domain(cookieProperties.getDomain());
194-
}
195-
196-
ResponseCookie cookie = builder.build();
197-
response.addHeader(HttpHeaders.SET_COOKIE, cookie.toString());
198-
}
199181
}

backend/src/main/java/com/back/global/security/JwtProvider.java

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -21,10 +21,10 @@ public class JwtProvider {
2121
private String secret;
2222

2323
@Value("${custom.jwt.access-token-duration}")
24-
private long accessTokenDurationMillis;
24+
private long accessTokenDurationSeconds;
2525

2626
@Value("${custom.jwt.refresh-token-duration}")
27-
private long refreshTokenDurationMillis;
27+
private long refreshTokenDurationSeconds;
2828

2929
private static final String CLAIM_ID = "id";
3030
private static final String CLAIM_NICKNAME = "nickname";
@@ -39,7 +39,7 @@ public String generateAccessToken(User user) {
3939

4040
return JwtUtil.toString(
4141
secret,
42-
accessTokenDurationMillis,
42+
accessTokenDurationSeconds,
4343
claims
4444
);
4545
}
@@ -51,7 +51,7 @@ public String generateRefreshToken(User user) {
5151

5252
return JwtUtil.toString(
5353
secret,
54-
refreshTokenDurationMillis,
54+
refreshTokenDurationSeconds,
5555
claims
5656
);
5757
}
@@ -65,13 +65,13 @@ private Map<String, Object> createBaseClaims(User user) {
6565
}
6666

6767
/** access token 유효 기간 (ms 단위) */
68-
public long getAccessTokenValidityMillis() {
69-
return accessTokenDurationMillis;
68+
public long getAccessTokenValiditySeconds() {
69+
return accessTokenDurationSeconds;
7070
}
7171

7272
/** refresh token 유효 기간 (ms 단위) */
73-
public long getRefreshTokenValidityMillis() {
74-
return refreshTokenDurationMillis;
73+
public long getRefreshTokenValiditySeconds() {
74+
return refreshTokenDurationSeconds;
7575
}
7676

7777
public Map<String, Object> payloadOrNull(String jwt) {

0 commit comments

Comments
 (0)