-
Notifications
You must be signed in to change notification settings - Fork 8
refactor: 문자열 토큰을 Token 클래스로 #297
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
Changes from all commits
66e47ed
f27233c
71a1ba0
af06df8
3f94b96
d33933e
dbf5d72
b09c584
dd91537
29ffd67
afd7db5
0794878
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 |
|---|---|---|
| @@ -1,5 +1,12 @@ | ||
| package com.example.solidconnection.auth.dto; | ||
|
|
||
| import com.example.solidconnection.auth.service.AccessToken; | ||
|
|
||
| public record ReissueResponse( | ||
| String accessToken) { | ||
| String accessToken | ||
| ) { | ||
|
|
||
| public static ReissueResponse from(AccessToken accessToken) { | ||
| return new ReissueResponse(accessToken.token()); | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,7 +1,14 @@ | ||
| package com.example.solidconnection.auth.dto; | ||
|
|
||
| import com.example.solidconnection.auth.service.AccessToken; | ||
| import com.example.solidconnection.auth.service.RefreshToken; | ||
|
|
||
| public record SignInResponse( | ||
| String accessToken, | ||
| String refreshToken | ||
| ) { | ||
|
|
||
| public static SignInResponse of(AccessToken accessToken, RefreshToken refreshToken) { | ||
| return new SignInResponse(accessToken.token(), refreshToken.token()); | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,11 @@ | ||
| package com.example.solidconnection.auth.service; | ||
|
|
||
| public record AccessToken( | ||
| Subject subject, | ||
| String token | ||
| ) { | ||
|
|
||
| public AccessToken(String subject, String token) { | ||
| this(new Subject(subject), token); | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,35 +1,30 @@ | ||
| package com.example.solidconnection.auth.service; | ||
|
|
||
|
|
||
| import com.example.solidconnection.auth.dto.ReissueRequest; | ||
| import com.example.solidconnection.auth.dto.ReissueResponse; | ||
| import com.example.solidconnection.config.security.JwtProperties; | ||
| import com.example.solidconnection.custom.exception.CustomException; | ||
| import com.example.solidconnection.siteuser.domain.SiteUser; | ||
| import lombok.RequiredArgsConstructor; | ||
| import org.springframework.stereotype.Service; | ||
| import org.springframework.transaction.annotation.Transactional; | ||
|
|
||
| import java.time.LocalDate; | ||
| import java.util.Objects; | ||
| import java.util.Optional; | ||
|
|
||
| import static com.example.solidconnection.custom.exception.ErrorCode.REFRESH_TOKEN_EXPIRED; | ||
| import static com.example.solidconnection.util.JwtUtils.parseSubject; | ||
|
|
||
| @RequiredArgsConstructor | ||
| @Service | ||
| public class AuthService { | ||
|
|
||
| private final AuthTokenProvider authTokenProvider; | ||
| private final JwtProperties jwtProperties; | ||
|
|
||
| /* | ||
| * 로그아웃 한다. | ||
| * - 엑세스 토큰을 블랙리스트에 추가한다. | ||
| * */ | ||
| public void signOut(String accessToken) { | ||
| authTokenProvider.generateAndSaveBlackListToken(accessToken); | ||
| public void signOut(String token) { | ||
| AccessToken accessToken = authTokenProvider.toAccessToken(token); | ||
| authTokenProvider.addToBlacklist(accessToken); | ||
| } | ||
|
|
||
| /* | ||
|
|
@@ -45,19 +40,18 @@ public void quit(SiteUser siteUser) { | |
|
|
||
| /* | ||
| * 액세스 토큰을 재발급한다. | ||
| * - 요청된 리프레시 토큰과 동일한 subject 의 토큰이 저장되어 있으며 값이 일치할 경우, 액세스 토큰을 재발급한다. | ||
| * - 그렇지 않으면 예외를 반환한다. | ||
| * - 유효한 리프레시토큰이면, 액세스 토큰을 재발급한다. | ||
| * - 그렇지 않으면 예외를 발생시킨다. | ||
| * */ | ||
| public ReissueResponse reissue(ReissueRequest reissueRequest) { | ||
| // 리프레시 토큰 확인 | ||
| String requestedRefreshToken = reissueRequest.refreshToken(); | ||
|
Comment on lines
45
to
48
Contributor
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. 토큰 객체를 만들었는데 결국 바디로 String refreshToken을 받아서 사용하고 있는게 쪼끔 일관성이 떨어지는 거 같다는 생각이 들었는데 나중 pr에서 클라이언트는 그대로 작성해놓고보니 좀 비효율적인 거 같다는 생각도 드네요 이건 😅 |
||
| String subject = parseSubject(requestedRefreshToken, jwtProperties.secret()); | ||
| Optional<String> savedRefreshToken = authTokenProvider.findRefreshToken(subject); | ||
| if (!Objects.equals(requestedRefreshToken, savedRefreshToken.orElse(null))) { | ||
| if (!authTokenProvider.isValidRefreshToken(requestedRefreshToken)) { | ||
| throw new CustomException(REFRESH_TOKEN_EXPIRED); | ||
| } | ||
| // 액세스 토큰 재발급 | ||
| String newAccessToken = authTokenProvider.generateAccessToken(subject); | ||
| return new ReissueResponse(newAccessToken); | ||
| Subject subject = authTokenProvider.parseSubject(requestedRefreshToken); | ||
| AccessToken newAccessToken = authTokenProvider.generateAccessToken(subject); | ||
| return ReissueResponse.from(newAccessToken); | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -3,49 +3,68 @@ | |
| import com.example.solidconnection.auth.domain.TokenType; | ||
| import com.example.solidconnection.config.security.JwtProperties; | ||
| import com.example.solidconnection.siteuser.domain.SiteUser; | ||
| import com.example.solidconnection.util.JwtUtils; | ||
| import org.springframework.data.redis.core.RedisTemplate; | ||
| import org.springframework.stereotype.Component; | ||
|
|
||
| import java.util.Optional; | ||
| import java.util.Objects; | ||
|
|
||
| @Component | ||
| public class AuthTokenProvider extends TokenProvider { | ||
| public class AuthTokenProvider extends TokenProvider implements BlacklistChecker { | ||
|
|
||
| public AuthTokenProvider(JwtProperties jwtProperties, RedisTemplate<String, String> redisTemplate) { | ||
| super(jwtProperties, redisTemplate); | ||
| } | ||
|
|
||
| public String generateAccessToken(SiteUser siteUser) { | ||
| String subject = getSubject(siteUser); | ||
| return generateToken(subject, TokenType.ACCESS); | ||
| public AccessToken generateAccessToken(Subject subject) { | ||
| String token = generateToken(subject.value(), TokenType.ACCESS); | ||
| return new AccessToken(subject, token); | ||
| } | ||
|
|
||
| public String generateAccessToken(String subject) { | ||
| return generateToken(subject, TokenType.ACCESS); | ||
| public RefreshToken generateAndSaveRefreshToken(Subject subject) { | ||
| String token = generateToken(subject.value(), TokenType.REFRESH); | ||
| saveToken(token, TokenType.REFRESH); | ||
| return new RefreshToken(subject, token); | ||
| } | ||
|
|
||
| public String generateAndSaveRefreshToken(SiteUser siteUser) { | ||
| String subject = getSubject(siteUser); | ||
| String refreshToken = generateToken(subject, TokenType.REFRESH); | ||
| return saveToken(refreshToken, TokenType.REFRESH); | ||
| /* | ||
| * 액세스 토큰을 블랙리스트에 저장한다. | ||
| * - key = BLACKLIST:{accessToken} | ||
| * - value = "signOut" -> key 의 존재만 확인하므로, value 에는 무슨 값이 들어가도 상관없다. | ||
| * */ | ||
| public void addToBlacklist(AccessToken accessToken) { | ||
| String blackListKey = TokenType.BLACKLIST.addPrefix(accessToken.token()); | ||
| redisTemplate.opsForValue().set(blackListKey, "signOut"); | ||
|
Comment on lines
+35
to
+37
Collaborator
Author
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 String generateAndSaveBlackListToken(String accessToken) { | ||
| String blackListToken = generateToken(accessToken, TokenType.BLACKLIST); | ||
| return saveToken(blackListToken, TokenType.BLACKLIST); | ||
| /* | ||
| * 유효한 리프레시 토큰인지 확인한다. | ||
| * - 요청된 토큰과 같은 subject 의 리프레시 토큰을 조회한다. | ||
| * - 조회된 리프레시 토큰과 요청된 토큰이 같은지 비교한다. | ||
| * */ | ||
| public boolean isValidRefreshToken(String requestedRefreshToken) { | ||
| String subject = JwtUtils.parseSubject(requestedRefreshToken, jwtProperties.secret()); | ||
| String refreshTokenKey = TokenType.REFRESH.addPrefix(subject); | ||
| String foundRefreshToken = redisTemplate.opsForValue().get(refreshTokenKey); | ||
| return Objects.equals(requestedRefreshToken, foundRefreshToken); | ||
| } | ||
|
|
||
| public Optional<String> findRefreshToken(String subject) { | ||
| String refreshTokenKey = TokenType.REFRESH.addPrefix(subject); | ||
| return Optional.ofNullable(redisTemplate.opsForValue().get(refreshTokenKey)); | ||
| @Override | ||
| public boolean isTokenBlacklisted(String accessToken) { | ||
| String blackListTokenKey = TokenType.BLACKLIST.addPrefix(accessToken); | ||
| return redisTemplate.hasKey(blackListTokenKey); | ||
| } | ||
|
|
||
| public Subject parseSubject(String token) { | ||
| String subject = JwtUtils.parseSubject(token, jwtProperties.secret()); | ||
| return new Subject(subject); | ||
| } | ||
|
|
||
| public Optional<String> findBlackListToken(String subject) { | ||
| String blackListTokenKey = TokenType.BLACKLIST.addPrefix(subject); | ||
| return Optional.ofNullable(redisTemplate.opsForValue().get(blackListTokenKey)); | ||
| public Subject toSubject(SiteUser siteUser) { | ||
| return new Subject(siteUser.getId().toString()); | ||
| } | ||
|
|
||
| private String getSubject(SiteUser siteUser) { | ||
| return siteUser.getId().toString(); | ||
| public AccessToken toAccessToken(String token) { | ||
| return new AccessToken(parseSubject(token), token); | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,6 @@ | ||
| package com.example.solidconnection.auth.service; | ||
|
|
||
| public interface BlacklistChecker { | ||
|
|
||
| boolean isTokenBlacklisted(String token); | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,11 @@ | ||
| package com.example.solidconnection.auth.service; | ||
|
|
||
| public record RefreshToken( | ||
| Subject subject, | ||
| String token | ||
| ) { | ||
|
|
||
| RefreshToken(String subject, String token) { | ||
| this(new Subject(subject), token); | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,6 @@ | ||
| package com.example.solidconnection.auth.service; | ||
|
|
||
| public record Subject( | ||
| String value | ||
| ) { | ||
| } |
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.
현재 로그아웃시에 refreshToken은 무효화가 안되고 있는 것으로 보았는데 맞을까요?
Uh oh!
There was an error while loading. Please reload this page.
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.
로그아웃할 때 리프레시 토큰 무효화 해야겠네요…. 이걸 놓치다니😞
새로 이슈 만들었습니다 (#298)
탈퇴 시 토큰을 무효화해야하는 이슈 (#99) 과 함께 다음 PR에서 해보겠습니다!