Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
b61bed8
refactor: Token VO 패키지 이동
nayonsoso Aug 23, 2025
4b3d811
refactor: Token VO의 구성을 단순화
nayonsoso Aug 23, 2025
332be93
test: 변경된 토큰 객체를 테스트 코드에 반영
nayonsoso Aug 23, 2025
3a4cc07
refactor: 토큰 '저장소'와 '제공자' 클래스 분리
nayonsoso Aug 23, 2025
7bf9d9d
refactor: TokenProvider가 jwt 의존하지 않도록 메서드 시그니처 변경
nayonsoso Aug 23, 2025
5a1ee42
test: TokenStorage와 TokenProvider 변경사항 테스트코드에 반영
nayonsoso Aug 23, 2025
1319eae
refactor: SignUpToken VO 생성, Token VO가 공통 인터페이스 구현하도록
nayonsoso Aug 23, 2025
15709d6
refactor: signup 관련 클래스 하나의 패키지로 위치
nayonsoso Aug 23, 2025
0aa559a
refactor: signin 관련 클래스 하나의 패키지로 위치
nayonsoso Aug 23, 2025
25e94f5
refactor: 토큰 관련 변수를 환경 변수로 관리
nayonsoso Aug 23, 2025
57ba58c
refactor: 토큰 생성 시, TokenType을 사용하지 않도록
nayonsoso Aug 23, 2025
5b75f99
refactor: parseSubject함수의 반환 타입이 Subject가 되도록
nayonsoso Aug 23, 2025
d348386
refactor: TokenStorage의 함수들이 값 객체를 인자로 갖도록
nayonsoso Aug 24, 2025
bad0187
test: 메서드 시그니처 변경 테스트 코드에 반영
nayonsoso Aug 24, 2025
a5e05ca
test: 변수 분리로 중복 코드 제거
nayonsoso Aug 24, 2025
bb371a4
refactor: TokenValue를 사용하지 않고, TokenProperties를 사용하도록
nayonsoso Aug 24, 2025
cef0864
refactor: 로그아웃 시, 액세스 토큰을 새로 만드는 부분 제거
nayonsoso Aug 24, 2025
a75f144
chore: 주석 보강
nayonsoso Aug 24, 2025
baecd6a
refactor: 회원가입 토큰 검증 시, 서버에 저장된 값과 동일한지 검증 추가
nayonsoso Aug 25, 2025
7a2acdf
refactor: 회원가입 토큰 검증 시, subject 존재 검증 추가
nayonsoso Aug 25, 2025
b085289
refactor: subject 추출 시, subject가 없으면 예외 발생하도록
nayonsoso Aug 25, 2025
66f4cb4
refactor: blacklist token 저장 시 TTL 설정되도록
nayonsoso Aug 25, 2025
21542c6
refactor: duration 사용으로 시간 단위 통일
nayonsoso Aug 25, 2025
97d3075
Merge remote-tracking branch 'origin/develop' into refactor/adjust-to…
nayonsoso Aug 30, 2025
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 @@ -25,8 +25,9 @@
import org.springframework.web.client.RestTemplate;

/*
* 애플 인증을 위한 OAuth2 클라이언트
* https://developer.apple.com/documentation/signinwithapplerestapi/generate_and_validate_tokens
* - 애플 인증을 위한 OAuth2 클라이언트
* - https://developer.apple.com/documentation/signinwithapplerestapi/generate_and_validate_tokens
* - OAuthClient 인터페이스를 사용하는 전략 패턴으로 구현됨
* */
@Component
@RequiredArgsConstructor
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,10 +23,11 @@
import org.springframework.web.util.UriComponentsBuilder;

/*
* 카카오 인증을 위한 OAuth2 클라이언트
* https://developers.kakao.com/docs/latest/ko/kakaologin/rest-api#request-code
* https://developers.kakao.com/docs/latest/ko/kakaologin/rest-api#request-token
* https://developers.kakao.com/docs/latest/ko/kakaologin/rest-api#req-user-info
* - 카카오 인증을 위한 OAuth2 클라이언트
* - https://developers.kakao.com/docs/latest/ko/kakaologin/rest-api#request-code
* - https://developers.kakao.com/docs/latest/ko/kakaologin/rest-api#request-token
* - https://developers.kakao.com/docs/latest/ko/kakaologin/rest-api#req-user-info
* - OAuthClient 인터페이스를 사용하는 전략 패턴으로 구현됨
* */
@Component
@RequiredArgsConstructor
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,10 @@
import com.example.solidconnection.auth.dto.oauth.OAuthResponse;
import com.example.solidconnection.auth.dto.oauth.OAuthSignInResponse;
import com.example.solidconnection.auth.service.AuthService;
import com.example.solidconnection.auth.service.EmailSignInService;
import com.example.solidconnection.auth.service.EmailSignUpTokenProvider;
import com.example.solidconnection.auth.service.SignUpService;
import com.example.solidconnection.auth.service.oauth.OAuthService;
import com.example.solidconnection.auth.service.signin.EmailSignInService;
import com.example.solidconnection.auth.service.signup.EmailSignUpTokenProvider;
import com.example.solidconnection.auth.service.signup.SignUpService;
import com.example.solidconnection.common.exception.CustomException;
import com.example.solidconnection.common.exception.ErrorCode;
import com.example.solidconnection.common.resolver.AuthorizedUser;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,12 @@
import static com.example.solidconnection.common.exception.ErrorCode.REFRESH_TOKEN_NOT_EXISTS;

import com.example.solidconnection.auth.controller.config.RefreshTokenCookieProperties;
import com.example.solidconnection.auth.domain.TokenType;
import com.example.solidconnection.auth.token.config.TokenProperties;
import com.example.solidconnection.common.exception.CustomException;
import jakarta.servlet.http.Cookie;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import java.time.Duration;
import java.util.Arrays;
import lombok.RequiredArgsConstructor;
import org.springframework.boot.web.server.Cookie.SameSite;
Expand All @@ -23,15 +24,17 @@ public class RefreshTokenCookieManager {
private static final String PATH = "/";

private final RefreshTokenCookieProperties properties;
private final TokenProperties tokenProperties;

public void setCookie(HttpServletResponse response, String refreshToken) {
long maxAge = convertExpireTimeToCookieMaxAge(TokenType.REFRESH.getExpireTime());
setRefreshTokenCookie(response, refreshToken, maxAge);
Duration tokenExpireTime = tokenProperties.refresh().expireTime();
long cookieMaxAge = convertExpireTimeToCookieMaxAge(tokenExpireTime);
setRefreshTokenCookie(response, refreshToken, cookieMaxAge);
}

private long convertExpireTimeToCookieMaxAge(long milliSeconds) {
private long convertExpireTimeToCookieMaxAge(Duration tokenExpireTime) {
// jwt의 expireTime 단위인 millisecond를 cookie의 maxAge 단위인 second로 변환
return milliSeconds / 1000;
return tokenExpireTime.toSeconds();
}

public void deleteCookie(HttpServletResponse response) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package com.example.solidconnection.auth.domain;

public record AccessToken(
String token
) implements Token {

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package com.example.solidconnection.auth.domain;

public record RefreshToken(
String token
) implements Token {

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package com.example.solidconnection.auth.domain;

public record SignUpToken(
String token
) implements Token {

}
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package com.example.solidconnection.auth.service;
package com.example.solidconnection.auth.domain;

public record Subject(
String value
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package com.example.solidconnection.auth.domain;

public interface Token {

String token();
}

This file was deleted.

Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
package com.example.solidconnection.auth.dto;

import com.example.solidconnection.auth.service.AccessToken;
import com.example.solidconnection.auth.domain.AccessToken;

public record ReissueResponse(
String accessToken
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
package com.example.solidconnection.auth.dto;

import com.example.solidconnection.auth.service.AccessToken;
import com.example.solidconnection.auth.service.RefreshToken;
import com.example.solidconnection.auth.domain.AccessToken;
import com.example.solidconnection.auth.domain.RefreshToken;

public record SignInResponse(
String accessToken,
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import static com.example.solidconnection.common.exception.ErrorCode.REFRESH_TOKEN_EXPIRED;
import static com.example.solidconnection.common.exception.ErrorCode.USER_NOT_FOUND;

import com.example.solidconnection.auth.domain.AccessToken;
import com.example.solidconnection.auth.dto.ReissueResponse;
import com.example.solidconnection.auth.token.TokenBlackListService;
import com.example.solidconnection.common.exception.CustomException;
Expand All @@ -26,11 +27,9 @@ public class AuthService {
* - 엑세스 토큰을 블랙리스트에 추가한다.
* - 리프레시 토큰을 삭제한다.
* */
public void signOut(String token) {
SiteUser siteUser = authTokenProvider.parseSiteUser(token);
AccessToken accessToken = authTokenProvider.generateAccessToken(siteUser);
authTokenProvider.deleteRefreshTokenByAccessToken(accessToken);
public void signOut(String accessToken) {
tokenBlackListService.addToBlacklist(accessToken);
authTokenProvider.deleteRefreshTokenByAccessToken(accessToken);
Comment on lines -29 to +32
Copy link
Collaborator Author

@nayonsoso nayonsoso Aug 24, 2025

Choose a reason for hiding this comment

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

로그아웃 시 불필요하게 엑세스 토큰을 생성하는 부분이 있었습니다.🐒

🔽 지난번에 규혁님이 말씀하신 이슈도 이 때문이었습니다.
image

이번 PR에서 이 부분도 수정했습니다.

}

/*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,17 @@

import static com.example.solidconnection.common.exception.ErrorCode.USER_NOT_FOUND;

import com.example.solidconnection.auth.domain.TokenType;
import com.example.solidconnection.auth.domain.AccessToken;
import com.example.solidconnection.auth.domain.RefreshToken;
import com.example.solidconnection.auth.domain.Subject;
import com.example.solidconnection.auth.token.config.TokenProperties;
import com.example.solidconnection.common.exception.CustomException;
import com.example.solidconnection.siteuser.domain.Role;
import com.example.solidconnection.siteuser.domain.SiteUser;
import com.example.solidconnection.siteuser.repository.SiteUserRepository;
import java.util.Map;
import java.util.Objects;
import lombok.RequiredArgsConstructor;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;

@Component
Expand All @@ -19,26 +21,30 @@ public class AuthTokenProvider {

private static final String ROLE_CLAIM_KEY = "role";

private final RedisTemplate<String, String> redisTemplate;
private final TokenProvider tokenProvider;
private final TokenStorage tokenStorage;
private final SiteUserRepository siteUserRepository;
private final TokenProperties tokenProperties;

public AccessToken generateAccessToken(SiteUser siteUser) {
Subject subject = toSubject(siteUser);
Role role = siteUser.getRole();
String token = tokenProvider.generateToken(
subject.value(),
subject,
Map.of(ROLE_CLAIM_KEY, role.name()),
TokenType.ACCESS
tokenProperties.access().expireTime()
);
return new AccessToken(subject, role, token);
return new AccessToken(token);
}

public RefreshToken generateAndSaveRefreshToken(SiteUser siteUser) {
Subject subject = toSubject(siteUser);
String token = tokenProvider.generateToken(subject.value(), TokenType.REFRESH);
tokenProvider.saveToken(token, TokenType.REFRESH);
return new RefreshToken(subject, token);
String token = tokenProvider.generateToken(
subject,
tokenProperties.refresh().expireTime()
);
RefreshToken refreshToken = new RefreshToken(token);
return tokenStorage.saveToken(subject, refreshToken);
}

/*
Expand All @@ -47,21 +53,20 @@ public RefreshToken generateAndSaveRefreshToken(SiteUser siteUser) {
* - 조회된 리프레시 토큰과 요청된 토큰이 같은지 비교한다.
* */
public boolean isValidRefreshToken(String requestedRefreshToken) {
String subject = tokenProvider.parseSubject(requestedRefreshToken);
String refreshTokenKey = TokenType.REFRESH.addPrefix(subject);
String foundRefreshToken = redisTemplate.opsForValue().get(refreshTokenKey);
return Objects.equals(requestedRefreshToken, foundRefreshToken);
Subject subject = tokenProvider.parseSubject(requestedRefreshToken);
return tokenStorage.findToken(subject, RefreshToken.class)
.map(foundRefreshToken -> Objects.equals(foundRefreshToken, requestedRefreshToken))
.orElse(false);
}

public void deleteRefreshTokenByAccessToken(AccessToken accessToken) {
String subject = accessToken.subject().value();
String refreshTokenKey = TokenType.REFRESH.addPrefix(subject);
redisTemplate.delete(refreshTokenKey);
public void deleteRefreshTokenByAccessToken(String accessToken) {
Subject subject = tokenProvider.parseSubject(accessToken);
tokenStorage.deleteToken(subject, RefreshToken.class);
}

public SiteUser parseSiteUser(String token) {
String subject = tokenProvider.parseSubject(token);
long siteUserId = Long.parseLong(subject);
Subject subject = tokenProvider.parseSubject(token);
long siteUserId = Long.parseLong(subject.value());
return siteUserRepository.findById(siteUserId)
.orElseThrow(() -> new CustomException(USER_NOT_FOUND));
}
Expand Down

This file was deleted.

This file was deleted.

Loading
Loading