Skip to content

Commit

Permalink
Merge pull request #43 from Seasoning-Today/cancel-refresh-token-rota…
Browse files Browse the repository at this point in the history
…tion

Refresh Token Rotation 적용 해지
  • Loading branch information
csct3434 authored Jan 30, 2024
2 parents 3285eda + 4a8b5e1 commit 46f4cb3
Show file tree
Hide file tree
Showing 5 changed files with 24 additions and 110 deletions.

This file was deleted.

Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
package today.seasoning.seasoning.common.token.domain;

import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonInclude.Include;
import lombok.AllArgsConstructor;
import lombok.Getter;

@Getter
@JsonInclude(Include.NON_NULL)
@AllArgsConstructor
public class TokenInfo {

Expand Down
Original file line number Diff line number Diff line change
@@ -1,62 +1,23 @@
package today.seasoning.seasoning.common.token.service;

import java.util.concurrent.TimeUnit;
import lombok.RequiredArgsConstructor;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import today.seasoning.seasoning.common.token.domain.TokenProperties;
import today.seasoning.seasoning.common.exception.CustomException;
import today.seasoning.seasoning.common.token.domain.TokenInfo;
import today.seasoning.seasoning.common.util.JwtUtil;
import today.seasoning.seasoning.common.util.TsidUtil;

@Service
@Transactional
@RequiredArgsConstructor
public class RefreshTokenService {

private final TokenProperties tokenProperties;
private final RedisTemplate<String, String> redisTemplate;

// 리프레시 토큰 저장
public void save(String refreshToken, long userId) {
Long expirationTimeMillis = tokenProperties.getRefreshTokenExpirationTimeMillis();

redisTemplate.opsForValue()
.set(refreshToken, TsidUtil.toString(userId), expirationTimeMillis, TimeUnit.MILLISECONDS);
}

// 리프레시 토큰을 통한 토큰 재발급
public TokenInfo refresh(String oldRefreshToken) {
// 토큰 유효성 검증
validateToken(oldRefreshToken);

// 토큰 보유 사용자 아이디 조회
String userId = findUserId(oldRefreshToken);

// 토큰 재발급
TokenInfo tokenInfo = JwtUtil.refreshToken(TsidUtil.toLong(userId), oldRefreshToken);

// 기존 리프레시 토큰 삭제 후 새로운 리프레시 토큰 저장
redisTemplate.delete(oldRefreshToken);
redisTemplate.opsForValue().set(tokenInfo.getRefreshToken(), userId);

return tokenInfo;
}

private void validateToken(String refreshToken) {
// 리프레시 토큰을 통한 액세스 토큰 재발급
public TokenInfo refresh(String refreshToken) {
if (!JwtUtil.validate(refreshToken)) {
throw new CustomException(HttpStatus.UNAUTHORIZED, "Invalid Token");
}
}

private String findUserId(String refreshToken) {
String userId = redisTemplate.opsForValue().get(refreshToken);
if (userId == null) {
throw new CustomException(HttpStatus.UNAUTHORIZED, "Invalid Token");
}
return userId;
return JwtUtil.refreshToken(refreshToken);
}
}
46 changes: 18 additions & 28 deletions src/main/java/today/seasoning/seasoning/common/util/JwtUtil.java
Original file line number Diff line number Diff line change
Expand Up @@ -27,48 +27,38 @@ private JwtUtil(TokenProperties tokenProperties) {

// 새로운 액세스 토큰 및 리프레시 토큰 생성
public static TokenInfo createToken(long userId) {
String accessToken = generateAccessToken(userId);
String refreshToken = generateRefreshToken();
return new TokenInfo(accessToken, refreshToken);
return new TokenInfo(generateAccessToken(userId), generateRefreshToken(userId));
}

// 리프레시 토큰을 통한 액세스 토큰 및 리프레시 토큰 재발급
// 리프레시 토큰 탈취 피해를 줄이기 위해 리프레시 토큰도 재생성 (만료시간은 유지)
public static TokenInfo refreshToken(long userId, String refreshToken) {
// 리프레시 토큰을 통한 액세스 토큰 재발급
public static TokenInfo refreshToken(String refreshToken) {
Claims claims = getClaims(refreshToken);
Date refreshTokenExpirationDate = claims.getExpiration();

String accessToken = generateAccessToken(userId);
String newRefreshToken = regenerateRefreshToken(refreshTokenExpirationDate.getTime());
return new TokenInfo(accessToken, newRefreshToken);
Long userId = TsidUtil.toLong(claims.get("uid", String.class));
return new TokenInfo(generateAccessToken(userId), null);
}

// 액세스 토큰 생성
private static String generateAccessToken(Long userId) {
return generateToken(userId, System.currentTimeMillis() + tokenProperties.getAccessTokenExpirationTimeMillis());
}

// 리프레시 토큰 생성
private static String generateRefreshToken() {
return generateToken(null, System.currentTimeMillis() + tokenProperties.getRefreshTokenExpirationTimeMillis());
}
ClaimsBuilder claimsBuilder = Jwts.claims();
claimsBuilder.subject(TsidUtil.toString(userId));

// 리프레시 토큰 재발급 (만료시간은 유지)
private static String regenerateRefreshToken(long expirationTimeMillis) {
return generateToken(null, expirationTimeMillis);
return Jwts.builder()
.claims(claimsBuilder.build())
.issuedAt(new Date())
.expiration(new Date(System.currentTimeMillis() + tokenProperties.getAccessTokenExpirationTimeMillis()))
.signWith(secretKey, SIG.HS256)
.compact();
}

private static String generateToken(Long userId, long expirationTimeMillis) {
// 리프레시 토큰 생성
private static String generateRefreshToken(long userId) {
ClaimsBuilder claimsBuilder = Jwts.claims();
// 리프레시 토큰은 subject 설정 X
if (userId != null) {
claimsBuilder.subject(TsidUtil.toString(userId));
}
claimsBuilder.add("uid", TsidUtil.toString(userId));

return Jwts.builder()
.claims(claimsBuilder.build())
.issuedAt(new Date())
.expiration(new Date(expirationTimeMillis))
.expiration(new Date(System.currentTimeMillis() + tokenProperties.getRefreshTokenExpirationTimeMillis()))
.signWith(secretKey, SIG.HS256)
.compact();
}
Expand All @@ -94,4 +84,4 @@ public static boolean validate(String token) {
}
return true;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@
import org.springframework.transaction.annotation.Transactional;
import today.seasoning.seasoning.common.enums.LoginType;
import today.seasoning.seasoning.common.token.domain.TokenInfo;
import today.seasoning.seasoning.common.token.service.RefreshTokenService;
import today.seasoning.seasoning.common.util.JwtUtil;
import today.seasoning.seasoning.user.domain.User;
import today.seasoning.seasoning.user.domain.UserRepository;
Expand All @@ -25,7 +24,6 @@ public class KakaoLoginService {

private final ExchangeKakaoAccessToken exchangeKakaoAccessToken;
private final FetchKakaoUserProfile fetchKakaoUserProfile;
private final RefreshTokenService refreshTokenService;
private final UserRepository userRepository;

@Transactional
Expand All @@ -42,9 +40,6 @@ public LoginResult handleKakaoLogin(String authorizationCode) {
// 토큰 발급
TokenInfo tokenInfo = JwtUtil.createToken(loginInfo.getUser().getId());

// 리프레시 토큰 저장
refreshTokenService.save(tokenInfo.getRefreshToken(), loginInfo.getUser().getId());

return new LoginResult(tokenInfo, loginInfo.isFirstLogin());
}

Expand Down

0 comments on commit 46f4cb3

Please sign in to comment.