Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: 로그아웃 기능 구현 #493

Merged
merged 17 commits into from
Sep 10, 2024
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -37,8 +37,8 @@ public ResponseEntity<AuthResponse> refreshAccessToken(@RequestHeader(HttpHeader

@Override
@PostMapping("/v1/auth/logout")
public ResponseEntity<Void> logout(@RequestHeader(HttpHeaders.AUTHORIZATION) String authorization) {
authService.logout(authorization);
public ResponseEntity<Void> logout(@RequestHeader(HttpHeaders.AUTHORIZATION) String rawAccessTokenValue) {
authService.logout(rawAccessTokenValue);
return ResponseEntity.ok().build();
}
}
29 changes: 9 additions & 20 deletions backend/src/main/java/com/ody/auth/service/AuthService.java
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ public AuthResponse renewTokens(String rawAuthorizationHeader) {

jwtTokenProvider.validate(refreshToken);
long memberId = jwtTokenProvider.parseAccessToken(accessToken);
checkSameMemberToken(memberId, refreshToken);
checkAlreadyLogout(memberId);
return issueNewTokens(memberId);
}

Expand All @@ -60,30 +60,19 @@ private AuthResponse issueNewTokens(long memberId) {
}

@Transactional
public void logout(String rawAuthorizationHeader) {
AuthorizationHeader authorizationHeader = new AuthorizationHeader(rawAuthorizationHeader);
AccessToken accessToken = authorizationHeader.getAccessToken();
RefreshToken refreshToken = authorizationHeader.getRefreshToken();

checkTokenExpiration(accessToken, refreshToken);
public void logout(String rawAccessTokenValue) {
AccessToken accessToken = new AccessToken(rawAccessTokenValue);
jwtTokenProvider.validate(accessToken);

long memberId = jwtTokenProvider.parseAccessToken(accessToken);
checkSameMemberToken(memberId, refreshToken);
checkAlreadyLogout(memberId);
memberService.updateRefreshToken(memberId, null);
}

private void checkTokenExpiration(AccessToken accessToken, RefreshToken refreshToken) {
try {
jwtTokenProvider.validate(accessToken);
jwtTokenProvider.validate(refreshToken);
} catch (OdyUnauthorizedException exception) {
throw new OdyBadRequestException(exception.getMessage());
}
}

private void checkSameMemberToken(long memberId, RefreshToken refreshToken) {
if (!memberService.isMemberRefreshToken(memberId, refreshToken)) {
throw new OdyBadRequestException("리프레시 토큰 정보가 일치하지 않습니다");
private void checkAlreadyLogout(long memberId) {
Member member = memberService.findById(memberId);
if(member.isLogout()){
throw new OdyBadRequestException("이미 회원이 로그아웃 상태입니다.");
}
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[질문] 로그아웃 상태에서 로그아웃을 다시 시도했을 때 400과 200 중 무엇을 줄지 차람과 함께 고려했나요? 회원 삭제 API에서는 이런 상황에서 200을 주기로 결정해서 질문드립니다

Copy link
Contributor Author

@coli-geonwoo coli-geonwoo Sep 10, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

반영했습니다! 좋은 포인트의 리뷰 감사합니다 🙇

a5e40a0

}
4 changes: 4 additions & 0 deletions backend/src/main/java/com/ody/member/domain/Member.java
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,10 @@ public String getProviderId() {
return authProvider.getProviderId();
}

public boolean isLogout() {
return refreshToken == null;
}

public boolean isSame(RefreshToken otherRefreshToken) {
return this.refreshToken.equals(otherRefreshToken);
}
Expand Down
34 changes: 11 additions & 23 deletions backend/src/test/java/com/ody/auth/service/AuthServiceTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -25,13 +25,13 @@ class AuthServiceTest extends BaseServiceTest {
@Autowired
private MemberRepository memberRepository;

@DisplayName("로그 아웃 테스트")
@DisplayName("로그아웃 테스트")
@Nested
class LogOutTest {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[필수]
logout으로 통일해주세요~

Suggested change
class LogOutTest {
class LogoutTest {

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

반영 완료!


@DisplayName("성공 : 유효한 액세스 토큰 + 유효한 리프레시 토큰")
@DisplayName("성공 : 유효한 액세스 토큰")
@Test
void logOutSuccess() {
void logoutSuccess() {
RefreshToken validRefreshToken = TokenFixture.getValidRefreshToken();
Member member = createMemberByRefreshToken(validRefreshToken);
AccessToken validAccessToken = TokenFixture.getValidAccessToken(member.getId());
Expand All @@ -42,39 +42,27 @@ void logOutSuccess() {
.doesNotThrowAnyException();
}

@DisplayName("실패 : 만료된 액세스 토큰으로 로그아웃 시도 시 400을 반환한다")
@DisplayName("실패 : 만료된 액세스 토큰으로 로그아웃 시도 시 401을 반환한다")
@Test
void logOutFail_When_ExpiredAccessToken() {
void logoutFailWhenExpiredAccessToken() {
RefreshToken validRefreshToken = TokenFixture.getValidRefreshToken();
Member member = createMemberByRefreshToken(validRefreshToken);
AccessToken expiredAccessToken = TokenFixture.getExpiredAccessToken(member.getId());
String authorizationHeader = resolveAuthorizationHeader(expiredAccessToken, validRefreshToken);

assertThatThrownBy(() -> authService.logout(authorizationHeader))
.isInstanceOf(OdyBadRequestException.class);
}

@DisplayName("실패 : 만료된 리프레시 토큰으로 로그아웃 시도 시 400을 반환한다")
@Test
void logOutFail_When_ExpiredRefreshToken() {
RefreshToken expiredRefreshToken = TokenFixture.getExpiredRefreshToken();
Member member = createMemberByRefreshToken(expiredRefreshToken);
AccessToken validAccessToken = TokenFixture.getValidAccessToken(member.getId());
String authorizationHeader = resolveAuthorizationHeader(validAccessToken, expiredRefreshToken);

assertThatThrownBy(() -> authService.logout(authorizationHeader))
.isInstanceOf(OdyBadRequestException.class);
.isInstanceOf(OdyUnauthorizedException.class);
}

@DisplayName("실패 : 액세스 토큰 파싱 멤버와 리프레시 토큰 정보가 일치하지 않으면 401을 반환한다")
@DisplayName("실패 : 이미 로그아웃한 유저 엑세스 토큰으로 로그아웃 시도 시 400을 반환한다")
@Test
void logOutFail_When_NotSameMemberToken() {
RefreshToken validRefreshToken = TokenFixture.getRefreshToken(TokenFixture.authPropertiesForValidToken);
void logoutFailWhenTryAlreadyLogoutMember() {
RefreshToken validRefreshToken = TokenFixture.getValidRefreshToken();
Member member = createMemberByRefreshToken(validRefreshToken);
AccessToken validAccessToken = TokenFixture.getValidAccessToken(member.getId());
String authorizationHeader = resolveAuthorizationHeader(validAccessToken, validRefreshToken);

RefreshToken otherRefreshToken = TokenFixture.getRefreshToken(TokenFixture.authPropertiesForValidToken2);
String authorizationHeader = resolveAuthorizationHeader(validAccessToken, otherRefreshToken);
member.updateRefreshToken(null);

assertThatThrownBy(() -> authService.logout(authorizationHeader))
.isInstanceOf(OdyBadRequestException.class);
Expand Down