-
Notifications
You must be signed in to change notification settings - Fork 2
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/#462 닉네임 변경 API 구현 #470
base: main
Are you sure you want to change the base?
Changes from 6 commits
9ef006e
95bd4c7
ee8bc8a
3d14050
ded41a5
d52d13c
9bfb040
621a625
4685592
5a64990
2847c8d
05fdcf1
514c475
f0e54f9
04af872
3d46182
72ea86a
d9115bf
21358a7
8ca6f07
9c6fc11
21f4d60
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,44 @@ | ||
package shook.shook.auth.application; | ||
|
||
import io.jsonwebtoken.Claims; | ||
import lombok.RequiredArgsConstructor; | ||
import org.springframework.stereotype.Service; | ||
import org.springframework.transaction.annotation.Transactional; | ||
import shook.shook.auth.application.dto.ReissueAccessTokenResponse; | ||
import shook.shook.auth.exception.AuthorizationException; | ||
import shook.shook.auth.repository.InMemoryTokenPairRepository; | ||
|
||
@Transactional(readOnly = true) | ||
@RequiredArgsConstructor | ||
@Service | ||
public class TokenService { | ||
|
||
public static final String EMPTY_REFRESH_TOKEN = "none"; | ||
public static final String TOKEN_PREFIX = "Bearer "; | ||
|
||
private final TokenProvider tokenProvider; | ||
private final InMemoryTokenPairRepository inMemoryTokenPairRepository; | ||
|
||
public void validateRefreshToken(final String refreshToken) { | ||
if (refreshToken.equals(EMPTY_REFRESH_TOKEN)) { | ||
throw new AuthorizationException.RefreshTokenNotFoundException(); | ||
} | ||
} | ||
|
||
public String extractAccessToken(final String authorization) { | ||
return authorization.substring(TOKEN_PREFIX.length()); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 그리고 만약 authorization 문자열이 |
||
} | ||
|
||
public ReissueAccessTokenResponse reissueAccessTokenByRefreshToken(final String refreshToken, | ||
final String accessToken) { | ||
final Claims claims = tokenProvider.parseClaims(refreshToken); | ||
final Long memberId = claims.get("memberId", Long.class); | ||
final String nickname = claims.get("nickname", String.class); | ||
|
||
inMemoryTokenPairRepository.validateTokenPair(refreshToken, accessToken); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
현재 만약 refreshToken이 만료되었는데, 이를 캐치하지 못하고 먼저
그러면 프론트엔드에서는 이렇게 되면 결과적으로 "accessToken이 만료되어서 재발급 받으려 하는데, refreshToken도 이미 만료가 된 상황"에 대한 처리가 제대로 이루어지지 않을 것 같다는 생각이 들어요. 베로는 어떻게 생각하시나요? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 날카로운 지적입니다. |
||
final String reissuedAccessToken = tokenProvider.createAccessToken(memberId, nickname); | ||
inMemoryTokenPairRepository.addOrUpdateTokenPair(refreshToken, reissuedAccessToken); | ||
|
||
return new ReissueAccessTokenResponse(reissuedAccessToken); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -7,9 +7,8 @@ | |
import org.springframework.web.bind.annotation.PostMapping; | ||
import org.springframework.web.bind.annotation.RequestHeader; | ||
import org.springframework.web.bind.annotation.RestController; | ||
import shook.shook.auth.application.AuthService; | ||
import shook.shook.auth.application.TokenService; | ||
import shook.shook.auth.application.dto.ReissueAccessTokenResponse; | ||
import shook.shook.auth.exception.AuthorizationException; | ||
import shook.shook.auth.ui.openapi.AccessTokenReissueApi; | ||
|
||
@RequiredArgsConstructor | ||
|
@@ -18,21 +17,18 @@ public class AccessTokenReissueController implements AccessTokenReissueApi { | |
|
||
private static final String EMPTY_REFRESH_TOKEN = "none"; | ||
private static final String REFRESH_TOKEN_KEY = "refreshToken"; | ||
private static final String TOKEN_PREFIX = "Bearer "; | ||
|
||
private final AuthService authService; | ||
private final TokenService tokenService; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 👍 |
||
|
||
@PostMapping("/reissue") | ||
public ResponseEntity<ReissueAccessTokenResponse> reissueAccessToken( | ||
@CookieValue(value = REFRESH_TOKEN_KEY, defaultValue = EMPTY_REFRESH_TOKEN) final String refreshToken, | ||
@RequestHeader(HttpHeaders.AUTHORIZATION) final String authorization | ||
) { | ||
if (refreshToken.equals(EMPTY_REFRESH_TOKEN)) { | ||
throw new AuthorizationException.RefreshTokenNotFoundException(); | ||
} | ||
final String accessToken = authorization.split(TOKEN_PREFIX)[1]; | ||
final ReissueAccessTokenResponse response = | ||
authService.reissueAccessTokenByRefreshToken(refreshToken, accessToken); | ||
tokenService.validateRefreshToken(refreshToken); | ||
final String accessToken = tokenService.extractAccessToken(authorization); | ||
final ReissueAccessTokenResponse response = tokenService.reissueAccessTokenByRefreshToken(refreshToken, | ||
accessToken); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
reissue 메서드 내에서 validate, extract 를 함께 하도록 하지 않고 validate와 extract를 public으로 두고 controller 에서 따로 호출하도록 하신 이유가 있을까요? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 지금은 사라졌지만 닉네임 변경 시 리프레시 토큰을 받도록 하면 다른 곳에서도 검증이 필요해서 public 메서드로 만들어두었습니다! |
||
|
||
return ResponseEntity.ok(response); | ||
} | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -7,7 +7,6 @@ | |
import org.springframework.stereotype.Service; | ||
import org.springframework.transaction.annotation.Transactional; | ||
import shook.shook.auth.application.TokenProvider; | ||
import shook.shook.auth.application.dto.TokenPair; | ||
import shook.shook.auth.exception.AuthorizationException; | ||
import shook.shook.auth.repository.InMemoryTokenPairRepository; | ||
import shook.shook.auth.ui.argumentresolver.MemberInfo; | ||
|
@@ -62,7 +61,7 @@ public Member findByIdAndNicknameThrowIfNotExist(final Long id, final Nickname n | |
|
||
@Transactional | ||
public void deleteById(final Long id, final MemberInfo memberInfo) { | ||
final Member member = getMemberIfValidRequest(id, memberInfo); | ||
final Member member = getMemberIfValidRequest(id, memberInfo.getMemberId()); | ||
|
||
final List<KillingPartLike> membersExistLikes = likeRepository.findAllByMemberAndIsDeleted(member, false); | ||
|
||
|
@@ -71,8 +70,7 @@ public void deleteById(final Long id, final MemberInfo memberInfo) { | |
memberRepository.delete(member); | ||
} | ||
|
||
private Member getMemberIfValidRequest(final Long memberId, final MemberInfo memberInfo) { | ||
final long requestMemberId = memberInfo.getMemberId(); | ||
private Member getMemberIfValidRequest(final Long memberId, final Long requestMemberId) { | ||
final Member requestMember = findById(requestMemberId); | ||
final Member targetMember = findById(memberId); | ||
validateMemberAuthentication(requestMember, targetMember); | ||
|
@@ -99,28 +97,20 @@ private Member findById(final Long id) { | |
} | ||
|
||
@Transactional | ||
public TokenPair updateNickname(final Long memberId, final MemberInfo memberInfo, | ||
final NicknameUpdateRequest request) { | ||
final Member member = getMemberIfValidRequest(memberId, memberInfo); | ||
public boolean updateNickname(final Long memberId, final Long requestMemberId, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 💬 위의 There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 현재 memberInfo 의 id 만 쓰이고 있는 상태라 id 만 넘겨주는 걸로 통일했습니다 😄 There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
final NicknameUpdateRequest request) { | ||
final Member member = getMemberIfValidRequest(memberId, requestMemberId); | ||
final Nickname nickname = new Nickname(request.getNickname()); | ||
|
||
if (member.hasSameNickname(nickname)) { | ||
return null; | ||
return false; | ||
} | ||
|
||
validateDuplicateNickname(nickname); | ||
member.updateNickname(nickname.getValue()); | ||
memberRepository.save(member); | ||
|
||
return reissueTokenPair(member.getId(), member.getNickname()); | ||
} | ||
|
||
private TokenPair reissueTokenPair(final Long memberId, final String nickname) { | ||
final String reissuedAccessToken = tokenProvider.createAccessToken(memberId, nickname); | ||
final String reissuedRefreshToken = tokenProvider.createRefreshToken(memberId, nickname); | ||
inMemoryTokenPairRepository.addOrUpdateTokenPair(reissuedRefreshToken, reissuedAccessToken); | ||
|
||
return new TokenPair(reissuedAccessToken, reissuedRefreshToken); | ||
return true; | ||
} | ||
|
||
private void validateDuplicateNickname(final Nickname nickname) { | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 이제 사용되지 않는 There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 매의 눈... 이거 왜 남아 있었을까요 🤦🏻♀️ |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
package shook.shook.member.application.dto; | ||
|
||
import io.swagger.v3.oas.annotations.media.Schema; | ||
import lombok.AccessLevel; | ||
import lombok.AllArgsConstructor; | ||
import lombok.Getter; | ||
import lombok.NoArgsConstructor; | ||
|
||
@Schema(description = "닉네임 변경 응답") | ||
@AllArgsConstructor | ||
@NoArgsConstructor(access = AccessLevel.PRIVATE) | ||
@Getter | ||
public class NicknameUpdateResponse { | ||
|
||
@Schema(description = "액세스 토큰", example = "lfahrg;aoiehflksejflakwjeglk") | ||
private String accessToken; | ||
|
||
@Schema(description = "변경된 닉네임", example = "shook") | ||
private String nickname; | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -6,13 +6,12 @@ | |
import io.swagger.v3.oas.annotations.responses.ApiResponse; | ||
import io.swagger.v3.oas.annotations.responses.ApiResponses; | ||
import io.swagger.v3.oas.annotations.tags.Tag; | ||
import jakarta.servlet.http.HttpServletResponse; | ||
import jakarta.validation.Valid; | ||
import org.springframework.http.ResponseEntity; | ||
import org.springframework.web.bind.annotation.DeleteMapping; | ||
import org.springframework.web.bind.annotation.PatchMapping; | ||
import org.springframework.web.bind.annotation.PathVariable; | ||
import org.springframework.web.bind.annotation.RequestBody; | ||
import shook.shook.auth.application.dto.ReissueAccessTokenResponse; | ||
import shook.shook.auth.ui.argumentresolver.Authenticated; | ||
import shook.shook.auth.ui.argumentresolver.MemberInfo; | ||
import shook.shook.member.application.dto.NicknameUpdateRequest; | ||
|
@@ -61,6 +60,10 @@ ResponseEntity<Void> deleteMember( | |
) | ||
@Parameters( | ||
value = { | ||
@Parameter( | ||
name = "memberInfo", | ||
hidden = true | ||
), | ||
Comment on lines
+63
to
+66
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 오홍 👍 👍 |
||
@Parameter( | ||
name = "member_id", | ||
description = "닉네임을 변경할 회원 id", | ||
|
@@ -74,10 +77,9 @@ ResponseEntity<Void> deleteMember( | |
} | ||
) | ||
@PatchMapping("/nickname") | ||
ResponseEntity<ReissueAccessTokenResponse> updateNickname( | ||
@PathVariable(name = "member_id") final Long memberId, | ||
ResponseEntity<Void> updateNickname( | ||
@Authenticated final MemberInfo memberInfo, | ||
@RequestBody final NicknameUpdateRequest request, | ||
final HttpServletResponse response | ||
@PathVariable(name = "member_id") final Long memberId, | ||
@Valid @RequestBody final NicknameUpdateRequest request | ||
); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
현재 TokenService 내부에
TokenProvider
와InMemoryTokenPairRepository
를 가지고 구성이 되어 있는데 MemberService와 AuthService는 TokenService를 의존하는 것이 아닌TokenProvider
와InMemoryTokenPairRepository
의존하고 있는데 이 부분이 전에 말했던 순환참조 때문이었나요? 그렇지 않으면 TokenService를 의존하는 것이 좋을 것 같은데 어떻게 생각하시나요?