Skip to content

Commit 3b16bda

Browse files
authored
refactor: 로그아웃, 탈퇴 시 refresh 토큰을 삭제하도록 (#303)
* feat: 액세스 토큰에 해당하는 리프레시 토큰 삭제 함수 구현 * refactor: 로그아웃 시, 리프레시 토큰 삭제하도록 * refactor: 탈퇴 시, 로그아웃하도록 * refactor: 엑세스 토큰 가져오는 함수 분리 - Authentication 자체가 null 인 경우도 예외처리
1 parent 9713f51 commit 3b16bda

5 files changed

Lines changed: 60 additions & 12 deletions

File tree

src/main/java/com/example/solidconnection/auth/controller/AuthController.java

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -97,19 +97,18 @@ public ResponseEntity<SignInResponse> signUp(
9797
public ResponseEntity<Void> signOut(
9898
Authentication authentication
9999
) {
100-
String accessToken = (String) authentication.getCredentials();
101-
if (accessToken == null || accessToken.isBlank()) {
102-
throw new CustomException(ErrorCode.AUTHENTICATION_FAILED, "토큰이 없습니다.");
103-
}
100+
String accessToken = getAccessToken(authentication);
104101
authService.signOut(accessToken);
105102
return ResponseEntity.ok().build();
106103
}
107104

108105
@PatchMapping("/quit")
109106
public ResponseEntity<Void> quit(
110-
@AuthorizedUser SiteUser siteUser
107+
@AuthorizedUser SiteUser siteUser,
108+
Authentication authentication // todo: #299를 작업하며 인자를 (Authentication authentication)만 받도록 수정해야 함
111109
) {
112-
authService.quit(siteUser);
110+
String accessToken = getAccessToken(authentication);
111+
authService.quit(siteUser, accessToken);
113112
return ResponseEntity.ok().build();
114113
}
115114

@@ -120,4 +119,11 @@ public ResponseEntity<ReissueResponse> reissueToken(
120119
ReissueResponse reissueResponse = authService.reissue(reissueRequest);
121120
return ResponseEntity.ok(reissueResponse);
122121
}
122+
123+
private String getAccessToken(Authentication authentication) {
124+
if (authentication == null || !(authentication.getCredentials() instanceof String accessToken)) {
125+
throw new CustomException(ErrorCode.AUTHENTICATION_FAILED, "엑세스 토큰이 없습니다.");
126+
}
127+
return accessToken;
128+
}
123129
}

src/main/java/com/example/solidconnection/auth/service/AuthService.java

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,23 +19,27 @@ public class AuthService {
1919
private final AuthTokenProvider authTokenProvider;
2020

2121
/*
22-
* 로그아웃 한다.
22+
* 로그아웃한다.
2323
* - 엑세스 토큰을 블랙리스트에 추가한다.
24+
* - 리프레시 토큰을 삭제한다.
2425
* */
2526
public void signOut(String token) {
2627
AccessToken accessToken = authTokenProvider.toAccessToken(token);
28+
authTokenProvider.deleteRefreshTokenByAccessToken(accessToken);
2729
authTokenProvider.addToBlacklist(accessToken);
2830
}
2931

3032
/*
3133
* 탈퇴한다.
3234
* - 탈퇴한 시점의 다음날을 탈퇴일로 잡는다.
3335
* - e.g. 2024-01-01 18:00 탈퇴 시, 2024-01-02 00:00 가 탈퇴일이 된다.
36+
* - 로그아웃한다.
3437
* */
3538
@Transactional
36-
public void quit(SiteUser siteUser) {
39+
public void quit(SiteUser siteUser, String token) { // todo: #299를 작업하며 인자를 (String token)만 받도록 수정해야 함
3740
LocalDate tomorrow = LocalDate.now().plusDays(1);
3841
siteUser.setQuitedAt(tomorrow);
42+
signOut(token);
3943
}
4044

4145
/*

src/main/java/com/example/solidconnection/auth/service/AuthTokenProvider.java

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,12 @@ public boolean isValidRefreshToken(String requestedRefreshToken) {
4949
return Objects.equals(requestedRefreshToken, foundRefreshToken);
5050
}
5151

52+
public void deleteRefreshTokenByAccessToken(AccessToken accessToken) {
53+
String subject = accessToken.subject().value();
54+
String refreshTokenKey = TokenType.REFRESH.addPrefix(subject);
55+
redisTemplate.delete(refreshTokenKey);
56+
}
57+
5258
@Override
5359
public boolean isTokenBlacklisted(String accessToken) {
5460
String blackListTokenKey = TokenType.BLACKLIST.addPrefix(accessToken);

src/test/java/com/example/solidconnection/auth/service/AuthServiceTest.java

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

3+
import com.example.solidconnection.auth.domain.TokenType;
34
import com.example.solidconnection.auth.dto.ReissueRequest;
45
import com.example.solidconnection.auth.dto.ReissueResponse;
56
import com.example.solidconnection.custom.exception.CustomException;
@@ -12,12 +13,14 @@
1213
import org.junit.jupiter.api.Nested;
1314
import org.junit.jupiter.api.Test;
1415
import org.springframework.beans.factory.annotation.Autowired;
16+
import org.springframework.data.redis.core.RedisTemplate;
1517

1618
import java.time.LocalDate;
1719

1820
import static com.example.solidconnection.custom.exception.ErrorCode.REFRESH_TOKEN_EXPIRED;
1921
import static org.assertj.core.api.Assertions.assertThat;
2022
import static org.assertj.core.api.Assertions.assertThatCode;
23+
import static org.junit.jupiter.api.Assertions.assertAll;
2124

2225
@DisplayName("인증 서비스 테스트")
2326
@TestContainerSpringBootTest
@@ -32,29 +35,44 @@ class AuthServiceTest {
3235
@Autowired
3336
private SiteUserRepository siteUserRepository;
3437

38+
@Autowired
39+
private RedisTemplate<String, String> redisTemplate;
40+
3541
@Test
3642
void 로그아웃한다() {
3743
// given
38-
AccessToken accessToken = authTokenProvider.generateAccessToken(new Subject("subject")); // todo: #296
44+
Subject subject = new Subject("subject");
45+
AccessToken accessToken = authTokenProvider.generateAccessToken(subject); // todo: #296
3946

4047
// when
4148
authService.signOut(accessToken.token());
4249

4350
// then
44-
assertThat(authTokenProvider.isTokenBlacklisted(accessToken.token())).isTrue();
51+
String refreshTokenKey = TokenType.REFRESH.addPrefix(subject.value());
52+
assertAll(
53+
() -> assertThat(redisTemplate.opsForValue().get(refreshTokenKey)).isNull(),
54+
() -> assertThat(authTokenProvider.isTokenBlacklisted(accessToken.token())).isTrue()
55+
);
4556
}
4657

4758
@Test
4859
void 탈퇴한다() {
4960
// given
5061
SiteUser siteUser = createSiteUser();
62+
Subject subject = authTokenProvider.toSubject(siteUser);
63+
AccessToken accessToken = authTokenProvider.generateAccessToken(subject); // todo: #296
5164

5265
// when
53-
authService.quit(siteUser);
66+
authService.quit(siteUser, accessToken.token());
5467

5568
// then
5669
LocalDate tomorrow = LocalDate.now().plusDays(1);
57-
assertThat(siteUser.getQuitedAt()).isEqualTo(tomorrow);
70+
String refreshTokenKey = TokenType.REFRESH.addPrefix(subject.value());
71+
assertAll(
72+
() -> assertThat(siteUser.getQuitedAt()).isEqualTo(tomorrow),
73+
() -> assertThat(redisTemplate.opsForValue().get(refreshTokenKey)).isNull(),
74+
() -> assertThat(authTokenProvider.isTokenBlacklisted(accessToken.token())).isTrue()
75+
);
5876
}
5977

6078
@Nested

src/test/java/com/example/solidconnection/auth/service/AuthTokenProviderTest.java

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,20 @@ class 리프레시_토큰을_제공한다 {
6969
() -> assertThat(authTokenProvider.isValidRefreshToken(fakeRefreshToken.token())).isFalse()
7070
);
7171
}
72+
73+
@Test
74+
void 액세서_토큰에_해당하는_리프레시_토큰을_삭제한다() {
75+
// given
76+
authTokenProvider.generateAndSaveRefreshToken(subject);
77+
AccessToken accessToken = authTokenProvider.generateAccessToken(subject);
78+
79+
// when
80+
authTokenProvider.deleteRefreshTokenByAccessToken(accessToken);
81+
82+
// then
83+
String refreshTokenKey = TokenType.REFRESH.addPrefix(subject.value());
84+
assertThat(redisTemplate.opsForValue().get(refreshTokenKey)).isNull();
85+
}
7286
}
7387

7488
@Nested

0 commit comments

Comments
 (0)