728x90

Spring Boot에서 Redis를 이용한 Refresh Token 처리 방법은 다음과 같습니다.

  1. 의존성 추가
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
  1. Redis 설정
spring.redis.host=localhost
spring.redis.port=6379
spring.redis.password=
spring.redis.database=0
spring.redis.jedis.pool.max-active=8
spring.redis.jedis.pool.max-wait=-1
spring.redis.jedis.pool.max-idle=8
spring.redis.jedis.pool.min-idle=0
  1. Refresh Token 생성 후 Redis에 저장
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;
import java.time.Duration;
import java.util.UUID;

@Component
public class RefreshTokenService {

    @Autowired
    private RedisTemplate<String, String> redisTemplate;

    private final String PREFIX_REFRESH_TOKEN = "refresh_token:";

    // Refresh Token 만료시간 (1주일)
    private final Duration REFRESH_TOKEN_EXPIRATION = Duration.ofDays(7);

    public String createRefreshToken() {
        String refreshToken = PREFIX_REFRESH_TOKEN + UUID.randomUUID().toString();
        redisTemplate.opsForValue().set(refreshToken, "", REFRESH_TOKEN_EXPIRATION);
        return refreshToken;
    }
}
  1. 1. Access Token 만료 시, Refresh Token을 이용하여 새로운 Access Token을 생성.
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;
import java.time.Duration;
import java.util.Date;

@Component
public class AccessTokenService {

    @Value("${jwt.secret}")
    private String jwtSecret;

    @Autowired
    private RedisTemplate<String, String> redisTemplate;

    private final String PREFIX_REFRESH_TOKEN = "refresh_token:";

    // Access Token 만료시간 (1시간)
    private final Duration ACCESS_TOKEN_EXPIRATION = Duration.ofHours(1);

    public String createAccessToken(String refreshToken) throws Exception {
        if (!redisTemplate.hasKey(refreshToken)) {
            throw new Exception("Invalid Refresh Token");
        }

        String subject = ""; // Access Token의 subject
        Date now = new Date();
        Date expiredAt = new Date(now.getTime() + ACCESS_TOKEN_EXPIRATION.toMillis());

        redisTemplate.expire(refreshToken, Duration.ZERO);

        String accessToken = Jwts.builder()
                .setSubject(subject)
                .setIssuedAt(now)
                .setExpiration(expiredAt)
                .signWith(SignatureAlgorithm.HS256, jwtSecret)
                .compact();

        return accessToken;
    }
}

예시 코드는 Redis에 Refresh Token만 저장하고, Access Token은 JWT로 생성합니다.

이외에도 Redis를 이용하여 Access Token까지 저장할 수 있습니다.

  1. Redis에 Access Token과 Refresh Token을 저장합니다.
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;
import java.time.Duration;
import java.util.UUID;

@Component
public class TokenService {

    @Autowired
    private RedisTemplate<String, String> redisTemplate;

    private final String PREFIX_ACCESS_TOKEN = "access_token:";
    private final String PREFIX_REFRESH_TOKEN = "refresh_token:";

    // Access Token 만료시간 (1시간)
    private final Duration ACCESS_TOKEN_EXPIRATION = Duration.ofHours(1);

    // Refresh Token 만료시간 (1주일)
    private final Duration REFRESH_TOKEN_EXPIRATION = Duration.ofDays(7);

    public String createAccessToken() {
        String accessToken = PREFIX_ACCESS_TOKEN + UUID.randomUUID().toString();
        redisTemplate.opsForValue().set(accessToken, "", ACCESS_TOKEN_EXPIRATION);
        return accessToken;
    }

    public String createRefreshToken() {
        String refreshToken = PREFIX_REFRESH_TOKEN + UUID.randomUUID().toString();
        redisTemplate.opsForValue().set(refreshToken, "", REFRESH_TOKEN_EXPIRATION);
        return refreshToken;
    }

    public void deleteToken(String accessToken) {
        String refreshToken = accessTokenToRefreshToken(accessToken);
        redisTemplate.delete(accessToken, refreshToken);
    }

    public String accessTokenToRefreshToken(String accessToken) {
        return accessToken.replace(PREFIX_ACCESS_TOKEN, PREFIX_REFRESH_TOKEN);
    }

    public boolean isValidRefreshToken(String refreshToken) {
        return redisTemplate.hasKey(refreshToken);
    }

    public boolean isValidAccessToken(String accessToken) {
        return redisTemplate.hasKey(accessToken);
    }
}
  1. Access Token의 만료시간을 Redis에서 확인합니다.
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import java.util.Date;

@Component
public class JwtService {

    @Value("${jwt.secret}")
    private String jwtSecret;

    @Autowired
    private TokenService tokenService;

    // Access Token 만료시간 (1시간)
    private final long ACCESS_TOKEN_EXPIRATION_TIME = 3600000L;

    public String createAccessToken() {
        String accessToken = tokenService.createAccessToken();

        String subject = ""; // Access Token의 subject
        Date now = new Date();
        Date expiredAt = new Date(now.getTime() + ACCESS_TOKEN_EXPIRATION_TIME);

        String refreshToken = tokenService.createRefreshToken();

        // Redis에 Access Token과 Refresh Token 저장
        RedisTemplate<String, String> redisTemplate = tokenService.getRedisTemplate();
        redisTemplate.opsForValue().set(accessToken, refreshToken);
        redisTemplate.expire(accessToken, ACCESS_TOKEN_EXPIRATION_TIME);
        redisTemplate.opsForValue().set(refreshToken, accessToken);
        redisTemplate.expire(refreshToken, tokenService.getREFRESH_TOKEN_EXPIRATION_TIME());

        String jwt = Jwts.builder()
                .setSubject(subject)
                .setIssuedAt(now)
                .setExpiration(expiredAt)
                .signWith(SignatureAlgorithm.HS256, jwtSecret)
                .compact();

        return jwt;
    }

    public String refreshToken(String refreshToken) throws Exception {
        if (!tokenService.isValidRefreshToken(refreshToken)) {
            throw new Exception("Invalid Refresh Token");
        }

        String accessToken = tokenService.accessTokenToRefreshToken(refreshToken);
        if (!tokenService.isValidAccessToken(accessToken)) {
            throw new Exception("Invalid Access Token");
        }

        // Access Token의 만료시간을 Redis에서 확인
        RedisTemplate<String, String> redisTemplate = tokenService.getRedisTemplate();
        long expiration = redisTemplate.getExpire(accessToken);

        if (expiration < 0) {
            throw new Exception("Expired Access Token");
        }

        String newAccessToken = createAccessToken();

        // 기존 Access Token을 삭제하고 새로운 Access Token과 연결된 Refresh Token 저장
        redisTemplate.delete(accessToken);
        redisTemplate.opsForValue().set(newAccessToken, refreshToken);
        redisTemplate.expire(newAccessToken, ACCESS_TOKEN_EXPIRATION_TIME);
        redisTemplate.opsForValue().set(refreshToken, newAccessToken);
        redisTemplate.expire(refreshToken, tokenService.getREFRESH_TOKEN_EXPIRATION_TIME());

        return newAccessToken;
    }

}

위의 코드에서는 Redis에 저장된 Access Token의 만료시간을 확인하여 만료된 Access Token인 경우 새로운 Access Token을 생성합니다. 그리고 기존 Access Token을 삭제하고 새로운 Access Token과 연결된 Refresh Token을 Redis에 저장합니다.

위 코드를 참고하여 Redis를 이용하여 Access Token과 Refresh Token을 저장하고, Access Token의 만료시간을 확인하는 코드를 작성해보세요.


내저장소 바로가기 luxury515

728x90
  1. Redis를 Spring Framework 적용.

Spring Data Redis를 사용하여 Redis를 Spring Framework에 통합할 수 있습니다. 이를 위해 먼저 pom.xml 또는 build.gradle 파일에 필요한 의존성을 추가하고, Redis 구성을 설정해야 합니다.

pom.xml 파일에 아래와 같이 의존성을 추가할 수 있습니다.

<dependency>
    <groupId>org.springframework.data</groupId>
    <artifactId>spring-data-redis</artifactId>
    <version>2.6.0</version>
</dependency>

Redis 구성은 RedisConnectionFactory, RedisTemplate 및 RedisCacheManager를 포함하는 구성 클래스를 작성하여 구현할 수 있습니다. 이 구성 클래스는 Redis 호스트 및 포트와 같은 Redis 구성을 정의하고 Spring Framework에서 Redis를 사용할 수 있도록 합니다.

  1. 중복 요청을 방지할 Redis 키 생성

Redis를 사용하여 중복 요청을 방지하려면 각 요청에 대한 고유한 식별자를 생성해야 합니다. 이 식별자를 Redis 키로 사용하여 중복 요청을 방지합니다.

Spring Framework에서는 요청을 처리하는 데 사용되는 컨트롤러 메서드에 @RequestMapping 애너테이션을 추가할 수 있습니다. 이 애너테이션에는 요청 경로와 HTTP 메서드가 정의됩니다. 컨트롤러 메서드에서는 이 정보를 사용하여 고유한 요청 식별자를 생성할 수 있습니다.

다음과 같은 요청 식별자를 생성할 수 있습니다.

String requestId = request.getMethod() + ":" + request.getRequestURI();
  1. Redis에 중복 요청 키 저장

Redis에 중복 요청을 방지하기 위한 키를 저장합니다. 이를 위해 RedisTemplate을 사용하여 Redis에 키-값 쌍을 저장합니다.

ValueOperations<String, String> ops = redisTemplate.opsForValue();
Boolean success = ops.setIfAbsent(requestId, "true", Duration.ofSeconds(60));
if (!success) {
    throw new DuplicateRequestException("Duplicate request");
}

위 코드에서는 Redis에 requestId 키가 없으면 "true" 값을 저장하고, 만약 이미 존재한다면 DuplicateRequestException을 발생시킵니다. 이렇게 함으로써, 같은 요청이 중복으로 수신되는 것을 방지할 수 있습니다.

  1. key 만료 시키기.

Redis에 저장된 중복 요청 키를 만료시킴으로써 Redis의 메모리를 최적화할 수 있습니다. 이를 위해 RedisTemplate을 사용하여 키의 만료 시간을 설정합니다.

ops.expire(requestId, Duration.ofSeconds(60));

내저장소 바로가기 luxury515

+ Recent posts