Skip to content

Commit

Permalink
fix: 리프레시 토큰 탈취 대응
Browse files Browse the repository at this point in the history
  • Loading branch information
SangWoon123 committed Oct 14, 2024
1 parent 3412992 commit 95a5294
Show file tree
Hide file tree
Showing 4 changed files with 44 additions and 8 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,20 @@
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Repository;

import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.Optional;
import java.util.concurrent.TimeUnit;

import static org.hibernate.validator.internal.engine.messageinterpolation.el.RootResolver.FORMATTER;

@Repository
@RequiredArgsConstructor
public class TokenInfoCacheRepository {
private final RedisTemplate<String, Object> redisTemplate;
private static final int MAXIMUM_REFRESH_TOKEN_EXPIRES_IN_DAY = 30;
private static final String ISSUE_TIME_KEY_PREFIX = "token:issueTime:"; // Redis에 저장할 키 prefix
private static final DateTimeFormatter FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");

/**
* 캐시에 토큰 저장
Expand Down Expand Up @@ -40,11 +46,23 @@ public void delete(final String key) {


/**
* 캐시로부터 유저정보 가져오기
*
* @param infoKey 캐시로 부터 가져올 키, refresh-token String
* 발급 시간을 Redis에 저장합니다.
* @param userCode 유저 코드
* @param issueTime 발급 시간
*/
public void saveIssueTime(String userCode, LocalDateTime issueTime) {
String key = ISSUE_TIME_KEY_PREFIX + userCode;
redisTemplate.opsForValue().set(key, issueTime.format(FORMATTER), 24, TimeUnit.HOURS);
}

/**
* Redis에서 발급 시간을 조회합니다.
* @param userCode 유저 코드
* @return 발급 시간 (없으면 null)
*/
public Optional<String> getUserInfo(final String infoKey) {
return Optional.ofNullable(redisTemplate.opsForValue().get(infoKey).toString());
public LocalDateTime getIssueTime(String userCode) {
String key = ISSUE_TIME_KEY_PREFIX + userCode;
String issueTimeStr = (String) redisTemplate.opsForValue().get(key);
return issueTimeStr != null ? LocalDateTime.parse(issueTimeStr, FORMATTER) : null;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,14 @@
import com.tukorea.planding.domain.auth.dto.TokenResponse;
import com.tukorea.planding.domain.auth.repository.TokenInfoCacheRepository;
import com.tukorea.planding.global.config.security.jwt.JwtTokenHandler;
import com.tukorea.planding.global.error.BusinessException;
import com.tukorea.planding.global.error.ErrorCode;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;

import java.time.LocalDateTime;

@Service
@Slf4j
@RequiredArgsConstructor
Expand Down Expand Up @@ -35,15 +39,23 @@ public TokenResponse issueTokenFromTemporaryToken(final String temporaryToken) {
public TokenResponse createNewToken(final Long userId, final String userCode) {
TokenResponse tokenResponse = createTokenResponse(userId, userCode);
tokenInfoCacheRepository.save(tokenResponse.refreshToken(), userCode);
tokenInfoCacheRepository.saveIssueTime(userCode, LocalDateTime.now()); // 토큰 발급시간관리
return tokenResponse;
}

public String reIssueAccessToken(final String refreshToken) {
// TODO: fake_id,fake_userCode로 수정할것
String userCode = jwtTokenHandler.extractClaim(refreshToken, claims -> claims.get("code", String.class));
Long userId = jwtTokenHandler.extractClaim(refreshToken, claims -> claims.get("id", Long.class));

String newAccessToken = jwtTokenHandler.generateAccessToken(userId, userCode);
return newAccessToken;
// if 리프레시 토큰이 만료되지않았는데, 발급하는것이라면 에러 throw
LocalDateTime issuedAt = tokenInfoCacheRepository.getIssueTime(userCode);
if (issuedAt != null && !jwtTokenHandler.isReissueAllowed(issuedAt)) {
throw new BusinessException(ErrorCode.EXPIRED_REFRESH_TOKEN_ERROR);
}


return jwtTokenHandler.generateAccessToken(userId, userCode); // new access-token 생성
}

public TemporaryTokenResponse createTemporaryTokenResponse(final Long userId,final String userCode) {
Expand All @@ -58,4 +70,5 @@ private TokenResponse createTokenResponse(final Long userId, final String userCo
jwtTokenHandler.generateRefreshToken(userId, userCode)
);
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;

import java.time.LocalDateTime;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
Expand Down Expand Up @@ -120,5 +121,8 @@ public boolean isRefreshTokenExpired(String refreshToken) {
return expiration.before(new Date());
}


// 발급 시간과 현재 시간의 차이가 만료 시간보다 작은지 확인
public boolean isReissueAllowed(LocalDateTime issuedAt) {
return issuedAt.plusHours(jwtProperties.getRefreshExpiration()).isBefore(LocalDateTime.now());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ public enum ErrorCode {
INVALID_AUTH_TOKEN("JWT-001", "토큰이 존재하지 않습니다.", HttpStatus.UNAUTHORIZED),
EXPIRED_AUTH_TOKEN("JWT-002", "만료된 토큰 입니다.", HttpStatus.UNAUTHORIZED),
EXPIRED_REFRESH_TOKEN("JWT-002", "만료된 리프레시토큰 입니다.", HttpStatus.UNAUTHORIZED),
EXPIRED_REFRESH_TOKEN_ERROR("JWT-003", "아직 액세스 토큰을 재발급할 수 없습니다.", HttpStatus.UNAUTHORIZED),

/**
* User Error
Expand Down

0 comments on commit 95a5294

Please sign in to comment.