JWT 인증 방식에서, access token이나 refresh token을 유효 기간만큼만 Redis에 저장하여 사용하는 경우가 많다.
다음 두 가지를 만족하도록 구현할 것이다.
1. 토큰을 Redis에 저장하고, 유효 기간만큼 TTL(Time To Live)을 지정하기
2. 로그인할 때는 토큰을 Redis에 저장하고, 로그아웃할 때는 저장된 토큰을 삭제하기Redis 연동에 관한 것 위주로만 다루고, 인증 관련 로직은 모두 생략할 것이다!
먼저 redis 서버를 실행시킨다. (redis_version 7.0.0)
직접 redis 서버를 설치해서 실행해도 되지만, docker를 사용해서 실행하였다.
$ docker run -d -p 6379:6379 redis
💡 redis-cli 실행 방법
$ docker exec -it [컨테이너 id] redis-cli
컨테이너 id
: 위에서 실행시킨 redis 컨테이너의 id
implementation 'org.springframework.boot:spring-boot-starter-data-redis'
spring.redis.host=127.0.0.1
spring.redis.port=6379
🔖 redis의 기본 포트는 6379번이다.
spring boot 2.0 이상부터는 auto-configuration으로 빈(redisConnectionFactory, RedisTemplate, StringTemplate)들이 자동으로 생성되기 때문에 위와 같이 properties 파일(혹은 yml 파일)에 설정만 해주면 된다!
import lombok.Builder;
import lombok.Getter;
import org.springframework.data.annotation.Id;
import org.springframework.data.redis.core.RedisHash;
@Getter
@RedisHash("token")
public class Token {
@Id
Long user_id; // 회원의 id
String token; // 토큰 값
@TimeToLive
Integer expiration;
@Builder
public SocialToken(Long user_id, String token, Integer expiration) {
this.user_id = user_id;
this.token = token;
this.expiration = expiration;
}
}
@RedisHash
: Redis의 Hash
자료구조(Key-Value Map). JPA의 @Entity
에 해당하는 애노테이션이라고 할 수 있다. 애노테이션의 value 값은 Key의 prefix가 된다.value:@Id
형태로 형성된다. (여기서는 value:{user_id})Set
자료구조가 생성되며, 그 안에는 @Id 값
들이 저장된다.@Id
: key를 식별할 때 사용하는 고유한 값. @RedisHash와 결합해서 key를 생성한다.@Indexed
: CRUD Repository를 사용할 때 jpa의 findBy필드명 처럼 사용하기 위해서 필요한 애노테이션.@TimeToLive
: 유효시간(초 단위). 유효 시간이 지나면 자동으로 삭제된다.@TimeToLive(unit = TimeUnit.MILLISECONDS)
옵션으로 단위를 변경할 수 있다.import org.springframework.data.repository.CrudRepository;
public interface TokenRedisRepository extends CrudRepository<Token, Long> {
}
JpaRepository
사용법과 매우 유사하다.CrudRepository
를 상속받아 기본적으로 save(), findById(), findAll(), delete() 등의 메소드를 사용할 수 있다.@Indexed
애노테이션을 필드에 붙이고 findBy…
메소드를 여기에 선언하면 된다.
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@Service
@RequiredArgsConstructor
@Slf4j
public class RedisService {
private final TokenRedisRepository tokenRedisRepository;
// 토큰 저장
@Transactional
public void saveToken(Long userId, TokenResponse token) {
try {
tokenRedisRepository.save(SocialToken.builder()
.user_id(userId)
.token(token.getAccessToken())
.expiration(token.getExpiresIn())
.build());
} catch (Exception e) {
log.error(e.getMessage());
}
}
// 토큰 조회
public String getToken(Long userId) {
try {
return tokenRedisRepository.findById(userId)
.orElseThrow(IllegalArgumentException::new) // 토큰이 존재하지 않은 경우
.getToken();
} catch (Exception e) {
log.error(e.getMessage());
}
}
// 토큰 삭제
@Transactional
public void deleteToken(Token token) {
try {
tokenRedisRepository.delete(token);
} catch (Exception e) {
log.error(e.getMessage());
}
}
}
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.ToString;
@NoArgsConstructor
@Getter
@ToString
public class TokenResponse {
@JsonProperty("token_type")
String tokenType;
@JsonProperty("access_token")
String accessToken;
@JsonProperty("refresh_token")
String refreshToken;
@JsonProperty("expires_in")
Integer expiresIn;
@JsonProperty("refresh_token_expires_in")
Integer refreshTokenExpiresIn;
String scope;
}
로그인 시에는 발급받은 토큰을 Redis에 저장한다.
// 회원 ID과 token을 받아오는 로직 (생략)
// 로그인 로직 (생략)
redisService.saveSocialToken(userId, token);
로그아웃 시에는 토큰을 삭제한다.
// 회원 ID를 받아오는 로직 (생략)
// 로그아웃 로직 (생략)
Token token = redisService.getToken(userId);
redisService.deleteToken(token);
🔖 참조
https://bcp0109.tistory.com/328
https://backtony.github.io/spring/redis/2021-08-29-spring-redis-1/