Skip to content

Commit

Permalink
Merge pull request #72 from onetime-with-members/feature/#71/refresh-…
Browse files Browse the repository at this point in the history
…limit

[feat] : 리프레쉬 토큰 제한 개수를 지정한다
  • Loading branch information
bbbang105 authored Oct 8, 2024
2 parents de74f52 + 3fc1c2e commit f6cfbd8
Show file tree
Hide file tree
Showing 3 changed files with 41 additions and 30 deletions.
2 changes: 1 addition & 1 deletion src/main/java/side/onetime/exception/TokenErrorResult.java
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ public enum TokenErrorResult implements BaseErrorCode {
_INVALID_TOKEN(HttpStatus.UNAUTHORIZED, "401", "유효하지 않은 토큰입니다."),
_INVALID_REFRESH_TOKEN(HttpStatus.UNAUTHORIZED, "401", "유효하지 않은 리프레쉬 토큰입니다."),
_EXPIRED_TOKEN(HttpStatus.UNAUTHORIZED, "401", "만료된 토큰입니다."),
_NOT_FOUND_REFRESH_TOKEN(HttpStatus.NOT_FOUND, "404", "리프레쉬 토큰을 찾을 수 없습니다.")
_NOT_FOUND_REFRESH_TOKEN(HttpStatus.UNAUTHORIZED, "401", "리프레쉬 토큰을 찾을 수 없습니다.")
;

private final HttpStatus httpStatus;
Expand Down
37 changes: 22 additions & 15 deletions src/main/java/side/onetime/repository/RefreshTokenRepository.java
Original file line number Diff line number Diff line change
@@ -1,42 +1,49 @@
package side.onetime.repository;


import lombok.RequiredArgsConstructor;
import org.springframework.beans.factory.annotation.Value;

import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.ValueOperations;
import org.springframework.stereotype.Repository;
import side.onetime.domain.RefreshToken;

import java.util.List;
import java.util.Objects;
import java.util.Optional;
import java.util.UUID;
import java.util.concurrent.TimeUnit;


@Repository
@RequiredArgsConstructor
public class RefreshTokenRepository {
@Value("${jwt.refresh-token.expiration-time}")
private long REFRESH_TOKEN_EXPIRATION_TIME; // 리프레쉬 토큰 유효기간

private final RedisTemplate redisTemplate;
private static final int REFRESH_TOKEN_LIMIT = 5; // 최대 5개로 제한

private final RedisTemplate<String, String> redisTemplate;

// RefreshToken 리스트에 새로운 토큰을 추가
public void save(final RefreshToken refreshToken) {
ValueOperations<Long, String> valueOperations = redisTemplate.opsForValue();
valueOperations.set(refreshToken.getUserId(), refreshToken.getRefreshToken());
redisTemplate.expire(refreshToken.getRefreshToken(), REFRESH_TOKEN_EXPIRATION_TIME, TimeUnit.MILLISECONDS);
String key = "refreshToken:" + refreshToken.getUserId();
// 맨 앞에 추가
redisTemplate.opsForList().leftPush(key, refreshToken.getRefreshToken());

// 가장 오래된 리프레쉬 토큰을 삭제
redisTemplate.opsForList().trim(key, 0, REFRESH_TOKEN_LIMIT - 1);

// 만료 시간 설정 (전체 리스트의 키에 적용)
redisTemplate.expire(key, REFRESH_TOKEN_EXPIRATION_TIME, TimeUnit.MILLISECONDS);
}

public Optional<RefreshToken> findByUserId(final Long userId) {
ValueOperations<UUID, String> valueOperations = redisTemplate.opsForValue();
String refreshToken = valueOperations.get(userId);
// 유저 ID로 RefreshToken 리스트 조회
public Optional<List<String>> findByUserId(final Long userId) {
String key = "refreshToken:" + userId;

List<String> refreshTokens = redisTemplate.opsForList().range(key, 0, -1);

if (Objects.isNull(refreshToken)) {
if (Objects.isNull(refreshTokens) || refreshTokens.isEmpty()) {
return Optional.empty();
}

return Optional.of(new RefreshToken(userId, refreshToken));
return Optional.of(refreshTokens);
}
}
}
32 changes: 18 additions & 14 deletions src/main/java/side/onetime/service/TokenService.java
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package side.onetime.service;

import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import side.onetime.domain.RefreshToken;
Expand All @@ -10,6 +11,9 @@
import side.onetime.repository.RefreshTokenRepository;
import side.onetime.util.JwtUtil;

import java.util.List;

@Slf4j
@Service
@RequiredArgsConstructor
public class TokenService {
Expand All @@ -26,24 +30,24 @@ public class TokenService {
// 액세스 & 리프레쉬 토큰 재발행 메서드
public TokenDto.ReissueTokenResponse reissueToken(TokenDto.ReissueTokenRequest reissueTokenRequest) {
String refreshToken = reissueTokenRequest.getRefreshToken();
jwtUtil.validateTokenExpiration(refreshToken);

Long userId = jwtUtil.getUserIdFromToken(refreshToken);
RefreshToken existRefreshToken = refreshTokenRepository.findByUserId(userId)
List<String> existRefreshTokens = refreshTokenRepository.findByUserId(userId)
.orElseThrow(() -> new TokenException(TokenErrorResult._NOT_FOUND_REFRESH_TOKEN));
String newAccessToken;

if (!existRefreshToken.getRefreshToken().equals(refreshToken)) {
// 리프레쉬 토큰이 다른 경우
throw new TokenException(TokenErrorResult._INVALID_REFRESH_TOKEN); // 401 에러를 던져 재로그인을 요청
} else {
// 액세스 토큰 재발급
newAccessToken = jwtUtil.generateAccessToken(userId, ACCESS_TOKEN_EXPIRATION_TIME);

if (!existRefreshTokens.contains(refreshToken)) {
// RefreshToken이 존재하지 않으면 예외 발생
throw new TokenException(TokenErrorResult._NOT_FOUND_REFRESH_TOKEN);
}

// 새로운 리프레쉬 토큰 Redis 저장
RefreshToken newRefreshToken = new RefreshToken(userId, jwtUtil.generateRefreshToken(userId, REFRESH_TOKEN_EXPIRATION_TIME));
refreshTokenRepository.save(newRefreshToken);
// 새로운 AccessToken 생성
String newAccessToken = jwtUtil.generateAccessToken(userId, ACCESS_TOKEN_EXPIRATION_TIME);

// 새로운 RefreshToken 생성 및 저장
String newRefreshToken = jwtUtil.generateRefreshToken(userId, REFRESH_TOKEN_EXPIRATION_TIME);
refreshTokenRepository.save(new RefreshToken(userId, newRefreshToken));

return TokenDto.ReissueTokenResponse.of(newAccessToken, newRefreshToken.getRefreshToken());
log.info("토큰 재발행에 성공하였습니다.");
return TokenDto.ReissueTokenResponse.of(newAccessToken, newRefreshToken);
}
}

0 comments on commit f6cfbd8

Please sign in to comment.