Skip to content

Commit e26c565

Browse files
committed
test(JwtServiceTest): 다양한 JWT 테스트 추가
- JWT 토큰 생성 및 검증 로직 테스트: - 토큰이 올바르게 생성되고, 예상한 클레임(`id`, `role`, `expiration`)을 포함하는지 확인. - 동일 사용자에 대해 여러 토큰 생성 시 서로 다른 값이어야 함을 검증. - JWT 만료 및 갱신 로직 테스트: - 만료된 AccessToken이 RefreshToken이 유효한 경우 갱신되는지 확인. - RefreshToken이 유효하지 않거나 존재하지 않을 때 예외 발생을 검증. - RefreshToken 관련 테스트: - 기존 RefreshToken이 올바르게 갱신되는지 확인. - RefreshToken이 없거나 잘못된 경우 예외 발생을 검증. - JWT 예외 처리 테스트: - 잘못된 토큰 및 만료된 토큰이 적절한 `JwtException`을 던지는지 확인. - RefreshToken 저장 및 유효성 확인 테스트: - RefreshToken 저장 로직 검증. - 갱신된 AccessToken이 쿠키에 올바르게 설정되는지 확인.
1 parent aa3ab9e commit e26c565

File tree

1 file changed

+291
-0
lines changed

1 file changed

+291
-0
lines changed
Lines changed: 291 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,291 @@
1+
package com.somemore.auth.jwt.service;
2+
3+
import com.somemore.IntegrationTestSupport;
4+
import com.somemore.auth.jwt.domain.EncodedToken;
5+
import com.somemore.auth.jwt.domain.TokenType;
6+
import com.somemore.auth.jwt.domain.UserRole;
7+
import com.somemore.auth.jwt.exception.JwtErrorType;
8+
import com.somemore.auth.jwt.exception.JwtException;
9+
import com.somemore.auth.jwt.refresh.domain.RefreshToken;
10+
import com.somemore.auth.jwt.refresh.manager.RefreshTokenManager;
11+
import com.somemore.auth.jwt.validator.JwtValidator;
12+
import io.jsonwebtoken.Claims;
13+
import io.jsonwebtoken.Jwts;
14+
import org.junit.jupiter.api.DisplayName;
15+
import org.junit.jupiter.api.Test;
16+
import org.springframework.beans.factory.annotation.Autowired;
17+
import org.springframework.mock.web.MockHttpServletResponse;
18+
19+
import javax.crypto.SecretKey;
20+
import java.time.Instant;
21+
import java.util.Date;
22+
import java.util.UUID;
23+
24+
import static org.assertj.core.api.Assertions.*;
25+
26+
27+
class JwtServiceTest extends IntegrationTestSupport {
28+
29+
@Autowired
30+
private JwtService jwtService;
31+
@Autowired
32+
private JwtValidator jwtValidator;
33+
@Autowired
34+
private SecretKey secretKey;
35+
@Autowired
36+
private RefreshTokenManager refreshTokenManager;
37+
38+
@DisplayName("토큰이 올바르게 생성된다")
39+
@Test
40+
void generateAndValidateToken() {
41+
// given
42+
String userId = UUID.randomUUID().toString();
43+
UserRole role = UserRole.VOLUNTEER;
44+
TokenType tokenType = TokenType.ACCESS;
45+
46+
// when
47+
EncodedToken token = jwtService.generateToken(userId, role.name(), tokenType);
48+
49+
// then
50+
Claims claims = jwtService.getClaims(token);
51+
assertThat(claims.get("id", String.class)).isEqualTo(userId);
52+
assertThat(claims.get("role", String.class)).isEqualTo(role.name());
53+
assertThat(claims.getExpiration()).isNotNull();
54+
}
55+
56+
@DisplayName("토큰 만료 기간이 정확히 설정되어야 한다")
57+
@Test
58+
void tokenExpirationPeriodIsExact() {
59+
// given
60+
String userId = UUID.randomUUID().toString();
61+
UserRole role = UserRole.VOLUNTEER;
62+
63+
// when
64+
EncodedToken accessToken = jwtService.generateToken(userId, role.name(), TokenType.ACCESS);
65+
EncodedToken refreshToken = jwtService.generateToken(userId, role.name(), TokenType.REFRESH);
66+
67+
// then
68+
Claims accessClaims = jwtService.getClaims(accessToken);
69+
Claims refreshClaims = jwtService.getClaims(refreshToken);
70+
71+
long accessTokenDuration = accessClaims.getExpiration().getTime() - accessClaims.getIssuedAt().getTime();
72+
long refreshTokenDuration = refreshClaims.getExpiration().getTime() - refreshClaims.getIssuedAt().getTime();
73+
74+
assertThat(accessTokenDuration).isEqualTo(TokenType.ACCESS.getPeriod());
75+
assertThat(refreshTokenDuration).isEqualTo(TokenType.REFRESH.getPeriod());
76+
}
77+
78+
@DisplayName("동일한 사용자로 여러 토큰 생성 시 서로 다른 값이어야 한다")
79+
@Test
80+
void multipleTokensForSameUserAreDifferent() {
81+
// given
82+
String userId = UUID.randomUUID().toString();
83+
UserRole role = UserRole.VOLUNTEER;
84+
85+
// when
86+
EncodedToken token1 = jwtService.generateToken(userId, role.name(), TokenType.ACCESS);
87+
EncodedToken token2 = jwtService.generateToken(userId, role.name(), TokenType.ACCESS);
88+
89+
// then
90+
assertThat(token1.value()).isNotEqualTo(token2.value());
91+
}
92+
93+
@DisplayName("만료된 엑세스 토큰은 리프레시 토큰이 유효하다면 갱신된다")
94+
@Test
95+
void verifyAndRefreshExpiredToken() {
96+
// given
97+
String userId = UUID.randomUUID().toString();
98+
UserRole role = UserRole.VOLUNTEER;
99+
EncodedToken expiredAccessToken = createExpiredToken(userId, role);
100+
createAndSaveRefreshToken(userId, expiredAccessToken, Instant.now().plusMillis(TokenType.REFRESH.getPeriod()));
101+
102+
MockHttpServletResponse mockResponse = new MockHttpServletResponse();
103+
104+
// when
105+
jwtService.processAccessToken(expiredAccessToken, mockResponse);
106+
107+
// then
108+
assertRefreshedAccessToken(mockResponse);
109+
}
110+
111+
@DisplayName("만료된 엑세스 토큰은 리프레시 토큰이 유효하지 않다면 갱신되지 않고 예외가 발생한다")
112+
@Test
113+
void throwExceptionWhenRefreshTokenIsInvalid() {
114+
// given
115+
String userId = UUID.randomUUID().toString();
116+
UserRole role = UserRole.VOLUNTEER;
117+
EncodedToken expiredAccessToken = createExpiredToken(userId, role);
118+
119+
EncodedToken expiredRefreshToken = createExpiredToken(userId, role);
120+
RefreshToken refreshToken = new RefreshToken(userId, expiredAccessToken, expiredRefreshToken);
121+
refreshTokenManager.save(refreshToken);
122+
123+
// when & then
124+
MockHttpServletResponse mockResponse = new MockHttpServletResponse();
125+
126+
assertThatThrownBy(() -> jwtService.processAccessToken(expiredAccessToken, mockResponse))
127+
.isInstanceOf(JwtException.class)
128+
.hasMessage(JwtErrorType.EXPIRED_TOKEN.getMessage());
129+
}
130+
131+
@DisplayName("만료된 엑세스 토큰은 리프레시 토큰이 존재하지 않는다면 갱신되지 않고 예외가 발생한다")
132+
@Test
133+
void throwExceptionWhenRefreshTokenIsMissing() {
134+
// given
135+
String userId = UUID.randomUUID().toString();
136+
UserRole role = UserRole.VOLUNTEER;
137+
EncodedToken expiredAccessToken = createExpiredToken(userId, role);
138+
139+
// when & then
140+
MockHttpServletResponse mockResponse = new MockHttpServletResponse();
141+
142+
assertThatThrownBy(() -> jwtService.processAccessToken(expiredAccessToken, mockResponse))
143+
.isInstanceOf(JwtException.class)
144+
.hasMessage(JwtErrorType.EXPIRED_TOKEN.getMessage());
145+
}
146+
147+
@DisplayName("리프레시된 AccessToken은 쿠키에 올바르게 저장된다")
148+
@Test
149+
void refreshedAccessTokenIsSetInCookie() {
150+
// given
151+
String userId = UUID.randomUUID().toString();
152+
UserRole role = UserRole.VOLUNTEER;
153+
154+
EncodedToken expiredAccessToken = createExpiredToken(userId, role);
155+
createAndSaveRefreshToken(userId, expiredAccessToken, Instant.now().plusMillis(TokenType.REFRESH.getPeriod()));
156+
157+
MockHttpServletResponse mockResponse = new MockHttpServletResponse();
158+
159+
// when
160+
jwtService.processAccessToken(expiredAccessToken, mockResponse);
161+
162+
// then
163+
String cookieHeader = mockResponse.getHeader("Set-Cookie");
164+
assertThat(cookieHeader).contains("ACCESS=");
165+
assertThat(cookieHeader).contains("HttpOnly");
166+
assertThat(cookieHeader).contains("Secure");
167+
}
168+
169+
@DisplayName("기존 RefreshToken이 갱신된다")
170+
@Test
171+
void refreshTokenIsUpdated() {
172+
// given
173+
String userId = UUID.randomUUID().toString();
174+
UserRole role = UserRole.VOLUNTEER;
175+
176+
EncodedToken expiredAccessToken = createExpiredToken(userId, role);
177+
RefreshToken oldRefreshToken = createAndSaveRefreshToken(userId, expiredAccessToken, Instant.now().plusMillis(TokenType.REFRESH.getPeriod()));
178+
179+
EncodedToken newAccessToken = jwtService.generateToken(userId, role.name(), TokenType.ACCESS);
180+
RefreshToken newRefreshToken = createAndSaveRefreshToken(userId, newAccessToken, Instant.now().plusMillis(TokenType.REFRESH.getPeriod()));
181+
182+
// when
183+
// then
184+
assertThatThrownBy(() -> refreshTokenManager.findRefreshToken(expiredAccessToken))
185+
.isInstanceOf(JwtException.class)
186+
.hasMessage(JwtErrorType.EXPIRED_TOKEN.getMessage());
187+
188+
assertThat(newRefreshToken.getAccessToken()).isEqualTo(newAccessToken.value());
189+
assertThat(newRefreshToken.getRefreshToken()).isNotEqualTo(oldRefreshToken.getRefreshToken());
190+
}
191+
192+
193+
@DisplayName("잘못된 JWT 토큰은 예외가 발생한다")
194+
@Test
195+
void invalidTokenThrowsJwtException() {
196+
// given
197+
String invalidToken = "invalid.token.value";
198+
EncodedToken encodedToken = new EncodedToken(invalidToken);
199+
200+
// when
201+
// then
202+
assertThatThrownBy(() -> jwtValidator.validateToken(encodedToken))
203+
.isInstanceOf(JwtException.class)
204+
.hasMessage(JwtErrorType.UNKNOWN_ERROR.getMessage());
205+
}
206+
207+
@DisplayName("만료된 JWT 토큰은 예외가 발생한다")
208+
@Test
209+
void expiredTokenThrowsJwtException() {
210+
// given
211+
String userId = UUID.randomUUID().toString();
212+
UserRole role = UserRole.VOLUNTEER;
213+
EncodedToken expiredAccessToken = createExpiredToken(userId, role);
214+
215+
// when
216+
// then
217+
assertThatThrownBy(() -> jwtValidator.validateToken(expiredAccessToken))
218+
.isInstanceOf(JwtException.class)
219+
.hasMessage(JwtErrorType.EXPIRED_TOKEN.getMessage());
220+
}
221+
222+
@DisplayName("RefreshToken이 존재하지 않으면 예외가 발생한다")
223+
@Test
224+
void refreshTokenNotFoundThrowsJwtException() {
225+
// given
226+
String userId = UUID.randomUUID().toString();
227+
UserRole role = UserRole.VOLUNTEER;
228+
EncodedToken expiredAccessToken = createExpiredToken(userId, role);
229+
230+
// when
231+
// then
232+
assertThatThrownBy(() -> jwtService.processAccessToken(expiredAccessToken, new MockHttpServletResponse()))
233+
.isInstanceOf(JwtException.class)
234+
.hasMessage(JwtErrorType.EXPIRED_TOKEN.getMessage());
235+
}
236+
237+
private EncodedToken createExpiredToken(String userId, UserRole role) {
238+
Claims claims = buildClaims(userId, role);
239+
240+
Instant now = Instant.now();
241+
Instant expiration = now.plusMillis(-1); // 과거
242+
243+
return new EncodedToken(Jwts.builder()
244+
.claims(claims)
245+
.issuedAt(Date.from(now))
246+
.expiration(Date.from(expiration))
247+
.signWith(secretKey, Jwts.SIG.HS256)
248+
.compact());
249+
}
250+
251+
private RefreshToken createAndSaveRefreshToken(String userId, EncodedToken accessToken, Instant expiration) {
252+
Claims claims = buildClaims(userId, UserRole.VOLUNTEER);
253+
Instant now = Instant.now();
254+
Instant refreshExpiration = now.plusMillis(TokenType.REFRESH.getPeriod());
255+
String uniqueId = UUID.randomUUID().toString(); // jti
256+
257+
RefreshToken refreshToken = new RefreshToken(
258+
userId,
259+
accessToken,
260+
new EncodedToken(Jwts.builder()
261+
.claims(claims)
262+
.id(uniqueId)
263+
.issuedAt(Date.from(now))
264+
.expiration(Date.from(refreshExpiration))
265+
.signWith(secretKey, Jwts.SIG.HS256)
266+
.compact()));
267+
268+
refreshTokenManager.save(refreshToken);
269+
270+
return refreshToken;
271+
}
272+
273+
private Claims buildClaims(String userId, UserRole role) {
274+
return Jwts.claims()
275+
.add("id", userId)
276+
.add("role", role)
277+
.build();
278+
}
279+
280+
private void assertRefreshedAccessToken(MockHttpServletResponse mockResponse) {
281+
String cookie = mockResponse.getHeader("Set-Cookie");
282+
assertThat(cookie).isNotNull();
283+
assertThat(cookie).contains(TokenType.ACCESS.name());
284+
285+
EncodedToken refreshedAccessToken = new EncodedToken(
286+
cookie.split(";")[0].substring("ACCESS=".length()));
287+
assertThatCode(() -> jwtValidator.validateToken(refreshedAccessToken))
288+
.doesNotThrowAnyException();
289+
}
290+
291+
}

0 commit comments

Comments
 (0)