Springboot

[Springboot] RefreshToken Redis에 저장하기

KJihun 2023. 7. 19. 20:23
728x90

1. redis 설치

 

2. gradle에 의존성 추가

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

 

3. application.properties 추가

spring.data.redis.database=0
spring.data.redis.host=localhost
spring.data.redis.port=6379

 

4. RedisConfig.class 추가

@Configuration
public class RedisConfig {

    @Value("${spring.data.redis.host}")
    private String host;

    @Value("${spring.data.redis.port}")
    private int port;
    
	// password는 사용 시 추가
//    @Value("${spring.data.redis.password}")
//    private int password;

    @Bean
    public RedisConnectionFactory redisConnectionFactory() {
        RedisStandaloneConfiguration redisConfiguration = new RedisStandaloneConfiguration();

        redisConfiguration.setHostName(host);
        redisConfiguration.setPort(port);
//        redisConfiguration.setPassword(String.valueOf(password));
        LettuceConnectionFactory lettuceConnectionFactory = new LettuceConnectionFactory(redisConfiguration);

        return lettuceConnectionFactory;
    }

    @Primary
    @Bean
    // 트랜잭션 지원. 하나의 로직이 실패하여 오류가 나는 경우 수행한 작업을 모두 취소
    public RedisTemplate<?, ?> redisTemplate() {
        RedisTemplate<byte[], byte[]> redisTemplate = new RedisTemplate<>();
        redisTemplate.setConnectionFactory(redisConnectionFactory());
        redisTemplate.setKeySerializer(new StringRedisSerializer());
        redisTemplate.setValueSerializer(new StringRedisSerializer());
        return redisTemplate;
    }

}

 

4. RedisService 추가

@Service
@RequiredArgsConstructor
public class RedisService {
    private final RedisTemplate redisTemplate;

    @Transactional
    public void setRefreshToken(RefreshToken refreshToken){
        ValueOperations<String, String> values = redisTemplate.opsForValue();
        values.set(refreshToken.getRefreshToken(), String.valueOf(refreshToken.getUserid()));
        redisTemplate.expire(String.valueOf(refreshToken.getUserid()), (60 * 60 * 24 * 14 + 60), TimeUnit.SECONDS);
    }

    public String getRefreshToken(String refreshToken){
        ValueOperations<String, String> values = redisTemplate.opsForValue();
        return values.get(refreshToken);
    }

    public String getRefreshToken(Long userId) {
        ValueOperations<String, String> values = redisTemplate.opsForValue();

        Set<String> keys = redisTemplate.keys("*");
        for (String key : keys) {
            String value = values.get(key);
            if (value != null && Long.parseLong(value) == userId) {
                return key;
            }
        }

        return null;
    }
}

 

5. JwtAuthenticationFilter에 아래의 코드 추가

 @Override
    public void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain, Authentication authResult) throws IOException {

        String username = ((UserDetailsImpl) authResult.getPrincipal()).getUsername();
        UserRoleEnum role = ((UserDetailsImpl) authResult.getPrincipal()).getUser().getRole();
        Long userId = ((UserDetailsImpl) authResult.getPrincipal()).getUser().getUserId();

		// 엑세스 토큰 헤더에 추가
        String accessToken = jwtUtil.createAccessToken(username, role);
        response.addHeader(JwtUtil.ACCESS_TOKEN, accessToken);

		// 리프레시 토큰 헤더에 추가
        String refreshToken = jwtUtil.createRefreshToken(username);
        response.addHeader(JwtUtil.REFRESH_TOKEN, refreshToken);
        
        // redis에 저장(중복저장 방지를 위한 if문)
        if(redisService.getRefreshToken(userId) == null) {
            redisService.setRefreshToken(new RefreshToken(refreshToken, userId));
        }

        response.setStatus(200);
        new ObjectMapper().writeValue(response.getOutputStream(), new ResultResponseDto("로그인 성공"));
    }

 

6. 토큰 만료시 client가 리프레시 토큰을 가지고 있다면 엑세스 토큰 재발급

try {
            Jwts.parserBuilder().setSigningKey(key).build().parseClaimsJws(token);
             ...
        } catch (ExpiredJwtException e) {
            if(req.getHeader(REFRESH_TOKEN).isEmpty()) {
                log.error("Expired JWT token, 만료된 JWT token 입니다.");
                throw new RuntimeException();
            } else if("redis의 리프레시 토큰과 header의 리프레시 토큰 간 검증") {
                String RefreshToken = req.getHeader(REFRESH_TOKEN);
                String newAccessToken = regenerateAccessToken(RefreshToken);
                res.addHeader(JwtUtil.ACCESS_TOKEN, newAccessToken);
                res.addHeader(JwtUtil.REFRESH_TOKEN, RefreshToken);
                log.info("토큰재발급 성공: {}", newAccessToken);
            }
       ...

'Springboot' 카테고리의 다른 글

[CS] 정규화 및 반정규화  (0) 2023.07.29
[springboot] 07-21 ~ 07-27  (0) 2023.07.27
[Springboot] DAO, DTO, VO  (0) 2023.07.18
[Springboot] CORS란?  (2) 2023.07.15
[Springboot] Handler, @ExceptionHandler, @ControllerAdvice  (1) 2023.07.11