Skip to content

Commit 74b5ffd

Browse files
committed
refactor: 리프레시 토큰 재발급 로직 리팩터링 및 JWT 검증 파이프라인 최적화 (#55)
1 parent 4a061c5 commit 74b5ffd

8 files changed

Lines changed: 40 additions & 65 deletions

File tree

src/main/java/com/sofa/linkiving/domain/auth/controller/AuthApi.java

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
package com.sofa.linkiving.domain.auth.controller;
22

3-
import com.sofa.linkiving.domain.member.entity.Member;
43
import com.sofa.linkiving.global.common.BaseResponse;
54

65
import io.swagger.v3.oas.annotations.Operation;
@@ -11,9 +10,8 @@
1110

1211
@Tag(name = "Auth", description = "인증 및 토큰 관리 API")
1312
public interface AuthApi {
14-
@Operation(summary = "토큰 재발급", description = "쿠키에 저장된 Refresh Token을 검증하여 Access/Refresh Token을 재발급합니다.")
13+
@Operation(summary = "토큰 재발급", description = "Refresh Token을 검증하여 Access/Refresh Token을 재발급합니다.")
1514
BaseResponse<Void> reissue(
16-
Member member,
1715
@Parameter(description = "리프레시 토큰") String refreshToken,
1816
HttpServletRequest request,
1917
HttpServletResponse response

src/main/java/com/sofa/linkiving/domain/auth/controller/AuthController.java

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,8 @@
77

88
import com.sofa.linkiving.domain.auth.dto.internal.TokenDto;
99
import com.sofa.linkiving.domain.auth.service.AuthService;
10-
import com.sofa.linkiving.domain.member.entity.Member;
1110
import com.sofa.linkiving.global.common.BaseResponse;
1211
import com.sofa.linkiving.global.util.CookieUtils;
13-
import com.sofa.linkiving.security.annotation.AuthMember;
1412

1513
import jakarta.servlet.http.HttpServletRequest;
1614
import jakarta.servlet.http.HttpServletResponse;
@@ -26,12 +24,11 @@ public class AuthController implements AuthApi {
2624
@Override
2725
@PostMapping("/reissue")
2826
public BaseResponse<Void> reissue(
29-
@AuthMember Member member,
3027
@CookieValue(value = "refreshToken", required = false) String refreshToken,
3128
HttpServletRequest request,
3229
HttpServletResponse response
3330
) {
34-
TokenDto newTokens = authService.reissue(refreshToken, member);
31+
TokenDto newTokens = authService.reissue(refreshToken);
3532

3633
cookieUtils.addCookie(request, response, "accessToken", newTokens.accessToken(), newTokens.accessExp());
3734
cookieUtils.addCookie(request, response, "refreshToken", newTokens.refreshToken(), newTokens.refreshExp());

src/main/java/com/sofa/linkiving/domain/auth/service/AuthService.java

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,8 @@
11
package com.sofa.linkiving.domain.auth.service;
22

33
import org.springframework.stereotype.Service;
4-
import org.springframework.transaction.annotation.Transactional;
54

65
import com.sofa.linkiving.domain.auth.dto.internal.TokenDto;
7-
import com.sofa.linkiving.domain.member.entity.Member;
86
import com.sofa.linkiving.security.jwt.JwtProperties;
97
import com.sofa.linkiving.security.jwt.JwtTokenProvider;
108

@@ -16,10 +14,9 @@ public class AuthService {
1614
private final JwtTokenProvider jwtTokenProvider;
1715
private final JwtProperties jwtProperties;
1816

19-
@Transactional
20-
public TokenDto reissue(String refreshToken, Member member) {
21-
String email = member.getEmail();
22-
jwtTokenProvider.validateRefreshToken(refreshToken, email);
17+
public TokenDto reissue(String refreshToken) {
18+
19+
String email = jwtTokenProvider.validateRefreshToken(refreshToken);
2320

2421
String newAccessToken = jwtTokenProvider.createAccessToken(email);
2522
String newRefreshToken = jwtTokenProvider.createRefreshToken(email);

src/main/java/com/sofa/linkiving/security/auth/config/SecurityConstants.java

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,10 @@ public abstract class SecurityConstants {
2222
"/v1/member/signup", "/v1/member/login", "/mock/**",
2323

2424
/* oauth2 */
25-
"/oauth2/**"
25+
"/oauth2/**",
26+
27+
/* auth */
28+
"/v1/auth/reissue"
2629
};
2730

2831
private static final String[] SEMI_PERMIT_URLS = {

src/main/java/com/sofa/linkiving/security/jwt/JwtTokenProvider.java

Lines changed: 21 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -114,57 +114,49 @@ public String resolveToken(HttpServletRequest request) {
114114
return null;
115115
}
116116

117-
public Date getExpiration(String token) {
118-
return parseClaims(token).getExpiration();
119-
}
117+
private Claims validateToken(String token) {
118+
if (token == null || token.isBlank()) {
119+
throw new CustomJwtException(JwtErrorCode.EMPTY_TOKEN);
120+
}
120121

121-
public String getUserIdFromToken(String token) {
122-
return parseClaims(token).getSubject();
122+
try {
123+
return parseClaims(token);
124+
} catch (ExpiredJwtException e) {
125+
throw new CustomJwtException(JwtErrorCode.EXPIRED_JWT_TOKEN);
126+
} catch (SecurityException | MalformedJwtException | IllegalArgumentException e) {
127+
throw new CustomJwtException(JwtErrorCode.INVALID_JWT_TOKEN);
128+
} catch (UnsupportedJwtException e) {
129+
throw new CustomJwtException(JwtErrorCode.UNSUPPORTED_JWT_TOKEN);
130+
}
123131
}
124132

125-
public void validateRefreshToken(String refreshToken, String userId) {
126-
if (refreshToken == null || refreshToken.isBlank()) {
127-
throw new CustomJwtException(JwtErrorCode.EMPTY_TOKEN);
128-
}
133+
public String validateRefreshToken(String refreshToken) {
134+
Claims claims = validateToken(refreshToken);
129135

130-
Claims claims = parseClaims(refreshToken);
131136
String tokenType = claims.get(JwtKeys.Claims.TOKEN_TYPE, String.class);
132137

133138
if (!JwtKeys.TokenType.REFRESH.equals(tokenType)) {
134139
throw new CustomJwtException(JwtErrorCode.INVALID_REFRESH);
135140
}
136141

142+
String userId = claims.getSubject();
143+
137144
if (redisService.hasNoKey(RedisKeyRegistry.REFRESH_TOKEN, userId)) {
138145
throw new CustomJwtException(JwtErrorCode.CANNOT_REFRESH);
139146
}
140147

141148
String redisToken = redisService.get(RedisKeyRegistry.REFRESH_TOKEN, userId);
142-
143149
if (!redisToken.equals(refreshToken)) {
144150
throw new CustomJwtException(JwtErrorCode.INVALID_JWT_TOKEN);
145151
}
146152

153+
return userId;
147154
}
148155

149156
public boolean validateAccessToken(String token) {
150-
if (token == null || token.isBlank()) {
151-
throw new CustomJwtException(JwtErrorCode.EMPTY_TOKEN);
152-
}
157+
Claims claims = validateToken(token);
153158

154-
try {
155-
Claims claims = parseClaims(token);
156-
boolean notExpired = !claims.getExpiration().before(new Date());
157-
158-
String tokenType = claims.get(JwtKeys.Claims.TOKEN_TYPE, String.class);
159-
boolean isAccess = JwtKeys.TokenType.ACCESS.equals(tokenType);
160-
161-
return notExpired && isAccess;
162-
} catch (SecurityException | MalformedJwtException | IllegalArgumentException e) {
163-
throw new CustomJwtException(JwtErrorCode.INVALID_JWT_TOKEN);
164-
} catch (ExpiredJwtException e) {
165-
throw new CustomJwtException(JwtErrorCode.EXPIRED_JWT_TOKEN);
166-
} catch (UnsupportedJwtException e) {
167-
throw new CustomJwtException(JwtErrorCode.UNSUPPORTED_JWT_TOKEN);
168-
}
159+
String tokenType = claims.get(JwtKeys.Claims.TOKEN_TYPE, String.class);
160+
return JwtKeys.TokenType.ACCESS.equals(tokenType);
169161
}
170162
}

src/test/java/com/sofa/linkiving/domain/auth/integration/AuthApiIntegrationTest.java

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -14,18 +14,15 @@
1414
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
1515
import org.springframework.boot.test.context.SpringBootTest;
1616
import org.springframework.http.MediaType;
17-
import org.springframework.security.core.userdetails.UserDetails;
1817
import org.springframework.test.context.ActiveProfiles;
1918
import org.springframework.test.context.bean.override.mockito.MockitoBean;
2019
import org.springframework.test.web.servlet.MockMvc;
2120
import org.springframework.transaction.annotation.Transactional;
2221

2322
import com.sofa.linkiving.domain.member.entity.Member;
24-
import com.sofa.linkiving.domain.member.enums.Role;
2523
import com.sofa.linkiving.domain.member.repository.MemberRepository;
2624
import com.sofa.linkiving.infra.redis.RedisService;
2725
import com.sofa.linkiving.security.jwt.JwtTokenProvider;
28-
import com.sofa.linkiving.security.userdetails.CustomMemberDetail;
2926

3027
import jakarta.servlet.http.Cookie;
3128

@@ -48,15 +45,13 @@ public class AuthApiIntegrationTest {
4845
private RedisService redisService;
4946

5047
private Member testMember;
51-
private UserDetails testUserDetails;
5248

5349
@BeforeEach
5450
void setUp() {
5551
testMember = memberRepository.save(Member.builder()
5652
.email("auth@test.com")
5753
.password("password")
5854
.build());
59-
testUserDetails = new CustomMemberDetail(testMember, Role.USER);
6055
}
6156

6257
@Test
@@ -75,14 +70,12 @@ void shouldReissueTokensAndReturnOk() throws Exception {
7570
post("/v1/auth/reissue")
7671
.cookie(refreshTokenCookie)
7772
.with(csrf())
78-
.with(user(testUserDetails))
7973
.accept(MediaType.APPLICATION_JSON)
8074
)
8175
.andDo(print())
8276
.andExpect(status().isOk())
8377
.andExpect(jsonPath("$.success").value(true))
8478
.andExpect(jsonPath("$.message").value("토큰 재발급 완료"))
85-
// 쿠키가 응답 헤더에 세팅되었는지 확인
8679
.andExpect(cookie().exists("accessToken"))
8780
.andExpect(cookie().exists("refreshToken"));
8881
}
@@ -96,7 +89,6 @@ void shouldFailWhenRefreshTokenCookieIsMissing() throws Exception {
9689
mockMvc.perform(
9790
post("/v1/auth/reissue")
9891
.with(csrf())
99-
.with(user(testUserDetails))
10092
.accept(MediaType.APPLICATION_JSON)
10193
)
10294
.andDo(print())

src/test/java/com/sofa/linkiving/domain/auth/service/AuthServiceTest.java

Lines changed: 6 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,6 @@
1111
import org.mockito.junit.jupiter.MockitoExtension;
1212

1313
import com.sofa.linkiving.domain.auth.dto.internal.TokenDto;
14-
import com.sofa.linkiving.domain.member.entity.Member;
1514
import com.sofa.linkiving.security.jwt.JwtProperties;
1615
import com.sofa.linkiving.security.jwt.JwtTokenProvider;
1716
import com.sofa.linkiving.security.jwt.error.CustomJwtException;
@@ -34,14 +33,13 @@ public class AuthServiceTest {
3433
@DisplayName("유효한 RefreshToken이 주어지면 새로운 토큰 쌍을 발급한다")
3534
void shouldReissueTokensSuccessfully() {
3635
// given
37-
Member member = mock(Member.class);
38-
given(member.getEmail()).willReturn("test@test.com");
36+
3937
String oldRefreshToken = "old-refresh-token";
4038

4139
String newAccessToken = "new-access-token";
4240
String newRefreshToken = "new-refresh-token";
4341

44-
willDoNothing().given(jwtTokenProvider).validateRefreshToken(oldRefreshToken, "test@test.com");
42+
given(jwtTokenProvider.validateRefreshToken(oldRefreshToken)).willReturn("test@test.com");
4543
given(jwtTokenProvider.createAccessToken("test@test.com")).willReturn(newAccessToken);
4644
given(jwtTokenProvider.createRefreshToken("test@test.com")).willReturn(newRefreshToken);
4745

@@ -50,7 +48,7 @@ void shouldReissueTokensSuccessfully() {
5048
given(jwtProperties.refreshTokenValidTime()).willReturn(1209600000L);
5149

5250
// when
53-
TokenDto result = authService.reissue(oldRefreshToken, member);
51+
TokenDto result = authService.reissue(oldRefreshToken);
5452

5553
// then
5654
assertThat(result).isNotNull();
@@ -59,22 +57,20 @@ void shouldReissueTokensSuccessfully() {
5957
assertThat(result.refreshToken()).isEqualTo(newRefreshToken);
6058
assertThat(result.refreshExp()).isEqualTo(1209600);
6159

62-
verify(jwtTokenProvider, times(1)).validateRefreshToken(oldRefreshToken, "test@test.com");
60+
verify(jwtTokenProvider, times(1)).validateRefreshToken(oldRefreshToken);
6361
}
6462

6563
@Test
6664
@DisplayName("RefreshToken 검증에 실패하면 예외가 발생한다")
6765
void shouldThrowExceptionWhenRefreshTokenIsInvalid() {
6866
// given
69-
Member member = mock(Member.class);
70-
given(member.getEmail()).willReturn("test@test.com");
7167
String invalidToken = "invalid-token";
7268

7369
willThrow(new CustomJwtException(JwtErrorCode.INVALID_JWT_TOKEN))
74-
.given(jwtTokenProvider).validateRefreshToken(invalidToken, "test@test.com");
70+
.given(jwtTokenProvider).validateRefreshToken(invalidToken);
7571

7672
// when & then
77-
assertThatThrownBy(() -> authService.reissue(invalidToken, member))
73+
assertThatThrownBy(() -> authService.reissue(invalidToken))
7874
.isInstanceOf(RuntimeException.class);
7975
}
8076
}

src/test/java/com/sofa/linkiving/global/security/jwt/JwtTokenProviderTest.java

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -174,7 +174,7 @@ void shouldThrowCannotRefreshWhenNoTokenInRedis() {
174174
.willReturn(true);
175175

176176
// when & then
177-
assertThatThrownBy(() -> provider.validateRefreshToken(refreshToken, userId))
177+
assertThatThrownBy(() -> provider.validateRefreshToken(refreshToken))
178178
.isInstanceOf(CustomJwtException.class)
179179
.extracting("errorCode")
180180
.isEqualTo(JwtErrorCode.CANNOT_REFRESH);
@@ -190,7 +190,7 @@ void shouldThrowWhenRefreshTokenTypeIsInvalid() {
190190
String accessToken = provider.createAccessToken("member");
191191

192192
// when & then
193-
assertThatThrownBy(() -> provider.validateRefreshToken(accessToken, "member-5"))
193+
assertThatThrownBy(() -> provider.validateRefreshToken(accessToken))
194194
.isInstanceOf(CustomJwtException.class)
195195
.extracting("errorCode")
196196
.isEqualTo(JwtErrorCode.INVALID_REFRESH);
@@ -211,7 +211,7 @@ void shouldThrowWhenRefreshTokenMismatch() {
211211
.willReturn("another-token");
212212

213213
// when & then
214-
assertThatThrownBy(() -> provider.validateRefreshToken(refreshToken, userId))
214+
assertThatThrownBy(() -> provider.validateRefreshToken(refreshToken))
215215
.isInstanceOf(CustomJwtException.class)
216216
.extracting("errorCode")
217217
.isEqualTo(JwtErrorCode.INVALID_JWT_TOKEN);
@@ -231,7 +231,7 @@ void shouldValidateRefreshTokenSuccessfully() {
231231
given(redisService.get(RedisKeyRegistry.REFRESH_TOKEN, userId)).willReturn(refreshToken);
232232

233233
// when & then
234-
assertThatCode(() -> provider.validateRefreshToken(refreshToken, userId))
234+
assertThatCode(() -> provider.validateRefreshToken(refreshToken))
235235
.doesNotThrowAnyException();
236236
}
237237
}

0 commit comments

Comments
 (0)