[개인 프로젝트] Refresh Token 을 Redis 에 저장해보자

sy·2023년 8월 1일
1
post-custom-banner

현재 상황
Refresh Token을 MySQL에 저장하고 있다.
(Access Token은 30분 유효, Refresh Token은 1개월 유효)

문제
Refresh Token으로 Access Token을 요청할때마다 데이터베이스에 접근해야한다. 요청이 많아질 수록 데이터베이스 부하가 증가한다.

나의 경우 실제로 서비스하는 애플리케이션이 아니기 때문에 MySQL에 저장해도 문제가 없지만 실제 서비스에서는 성능 이슈가 생길만 하다.

📌 그럼 난 왜 Redis 가 아니라 MySQL을 선택했을까?
현재 나의 프로젝트는 MySQL을 사용한다. 그래서 일부로 MySQL에 저장하였고 일부만 Redis로 변경할 때 어떤 부분을 수정하고 어떤 부분이 스프링부트에서 간편한지 알아보기 위해서다.

1. Redis 설치

build.gradle

implementation 'org.springframework.boot:spring-boot-starter-data-redis'

사용하는 로컬에 redis가 없다면 설치하기 (mac)

// 설치
brew install redis
// 버전 확인
redis-server --version
// 백그라운드 실행
brew services start redis

2. Redis 세팅

application.properties

spring.redis.host=localhost
spring.redis.port=6379

3. Refresh Token Domain 변경

변경 전

@Getter
@NoArgsConstructor
@Entity
public class RefreshToken extends BaseTimeEntity {
    @Id
    @Column(name = "refresh_token_id")
    private String id;
    
    @ManyToOne(fetch = FetchType.LAZY)
    @OnDelete(action = OnDeleteAction.CASCADE)
    @JoinColumn(name = "member_id")
    private Member member;

    @Column(name = "expiration_date")
    private LocalDateTime expirationDate;

    @Builder
    public RefreshToken(String id, Member member, LocalDateTime expirationDate) {
        this.id = id;
        this.member = member;
        this.expirationDate = expirationDate; 
    }
}

변경 후

import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;
import org.springframework.data.annotation.Id;
import org.springframework.data.redis.core.RedisHash;
import org.springframework.data.redis.core.TimeToLive;

@Getter
@NoArgsConstructor
@RedisHash(value = "refresh_token")
public class RefreshToken extends BaseTimeEntity {
    @Id
    private String id;
    private Member member;
    @TimeToLive
    private long expirationTime;

    @Builder
    public RefreshToken(String id, Member member, long expirationTime) {
        this.id = id;
        this.member = member;
        this.expirationTime = expirationTime;
    }
}
  1. import javax.persistence.Id -> org.springframework.data.annotation.Id 변경하였다.
  2. Member의 조인 부분이 뺐다.
  3. @TimeToLive를 사용하기 위해 LocalDateTime expirationDate -> long expirationTime 으로 변경하였다.

4. 테스트 코드 작성

@SpringBootTest
class RefreshTokenRepositoryTest {
    @Autowired
    private RefreshTokenRepository refreshTokenRepository;
    @Autowired
    private MemberRepository memberRepository;

    @Test
    void save() {
		// given
        String token = UUID.randomUUID().toString();
		Member member = createMember(10);
        LocalDateTime expirationDate = LocalDateTime.now().plusMonths(1);
        long expirationTime = Timestamp.valueOf(expirationDate).getTime();

        RefreshToken refreshToken = new RefreshToken(token, member, expirationTime);

        // when
        RefreshToken result = refreshTokenRepository.save(refreshToken);

        // then
        Assertions.assertThat(result.getId()).isEqualTo(token);
    }
    
    private Member createMember(int i) {
        return Member.builder()
                .email("test" + i + "@test.com")
                .password(passwordEncoder.encode("1234"))
                .nickname("test" + i)
                .profilePath(defaultProfilePath)
                .role(Role.ROLE_MEMBER)
                .social(null)
                .emailConfirmation(true)
                .build();
    }

}

에러!

Description:

The bean 'refreshTokenRepository', defined in com.realtimechat.client.repository.RefreshTokenRepository defined in @EnableRedisRepositories declared on RedisRepositoriesRegistrar.EnableRedisRepositoriesConfiguration, could not be registered. A bean with that name has already been defined in com.realtimechat.client.repository.RefreshTokenRepository defined in @EnableJpaRepositories declared on JpaRepositoriesRegistrar.EnableJpaRepositoriesConfiguration and overriding is disabled.

Action:

Consider renaming one of the beans or enabling overriding by setting spring.main.allow-bean-definition-overriding=true

위와 같은 에러가 나오고 있고 spring.main.allow-bean-definition-overriding=true 을 적어보라고 하니까 적어본다!

원인

@EnableRedisRepositories와 @EnableJpaRepositories 어노테이션이 refreshTokenRepository 라는 이름으로 Redis와 JPA용 Repository를 등록하려고 하여 에러가 발생하고 있다.

해결 방법

  1. spring.main.allow-bean-definition-overriding=true
  2. RefreshTokenRepository 이름 변경

해결

spring.main.allow-bean-definition-overriding=true 적어주면 바로 해결되지만 빈을 오버라이딩하는 방법은 좋은 방법이 아니니 다른 방법을 찾아보았다. RedisRefreshTokenRepository 라고 이름을 바꾸어 설정해주어도 계속 같은 에러가 났다. 하지만 허무하게 찾은 원인은..
RefreshToken 도메인에 extends BaseTimeEntity를 안빼주어서 나던 에러였다.😭 extends BaseTimeEntity를 빼고 나니 overriding=true를 할 필요도 없었고 굳이 RefreshTokenRepository -> RedisRefreshTokenRepository 이름 변경을 할 필요도 없었다.

도메인 RefreshToken 변경

@Getter
@NoArgsConstructor
@RedisHash(value = "refresh_token")
public class RefreshToken {
    @Id
    private String id;
    private Member member;
    private LocalDateTime expirationDate;

    @Builder
    public RefreshToken(String id, Member member, LocalDateTime expirationDate) {
        this.id = id;
        this.member = member;
        this.expirationDate = expirationDate; 
    }
}

RefreshTokenRepositoryTest save() 테스트 성공!

Redis 데이터 확인

검색을 해보면 테스트 코드에서 저장했던 데이터가 잘 들어갔음을 알 수 있다.

post-custom-banner

1개의 댓글

comment-user-thumbnail
2023년 8월 1일

좋은 글 감사합니다.

답글 달기