String refreshToken = Jwts.builder()
.setExpiration(new Date(System.currentTimeMillis() + expiration_refresh))
// .signWith(SignatureAlgorithm.HS256, secretKey) // 지원 중단
.signWith(getKey(secretKey), SignatureAlgorithm.HS256)
.compact();
Cookie cookie = new Cookie("refresh_token", tokenDTO.getRefresh());
cookie.setHttpOnly(true);
cookie.setMaxAge(3600 * 24 * 7);
cookie.setPath("/");
response.addCookie(cookie);
- Refresh Token DB에 저장 ( 서버 )
// Refresh Token은 DB에 저장
Token token = new Token();
token.setUser(optionalUser.get());
token.setToken(tokenDTO.getRefresh());
tokenRepository.save(token);
Access Token 검증
-> 유효 O
-> 인증
Access Token 검증
-> 유효 X
-> 401에러 발생 (HttpServletResponse.SC_UNAUTHORIZED)
-> 클라이언트에서 401에러 발생 시 Refresh Token를 통한 Access Token 재발급 API 호출
refresh token을 통해 access token을 재발급 하는 과정에서 DB에 많은 부하가 걸린다. 이는 성능저하로 이어진다.
-> Redis + docker를 사용해볼 예정.
- 따라서 이제까지 했던 Refresh Token DB 저장 로직은 삭제
- 도커 실행 (
이름 : test_redis,
포트포워딩 (호스트 포트:컨테이너 포트) : 외부에서 6379포트로 접근하면 6379포트로 포워딩이 된다.
-d : 컨테이너를 데몬으로 실행 (백그라운드로 실행)
)
docker stop {container_name} - 중지
docker start {container_name} - 시작
docker restart {container_name} - 재시작
Redis 데이터베이스의 설정 정보를 담고있는 RedisConfig 생성
package com.example.bulls.Config;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.StringRedisSerializer;
@Configuration
public class RedisConfigure {
@Value("${spring.data.redis.host}")
private String host;
@Value("${spring.data.redis.port}")
private int port;
@Bean
public RedisConnectionFactory redisConnectionFactory() {
return new LettuceConnectionFactory(host, port);
}
@Bean
public RedisTemplate<String, String> redisTemplate() {
// redisTemplate를 받아와서 set, get, delete를 사용
RedisTemplate<String, String> redisTemplate = new RedisTemplate<>();
/**
* setKeySerializer, setValueSerializer 설정
* redis-cli을 통해 직접 데이터를 조회 시 알아볼 수 없는 형태로 출력되는 것을 방지
*/
redisTemplate.setConnectionFactory(redisConnectionFactory());
redisTemplate.setKeySerializer(new StringRedisSerializer());
redisTemplate.setValueSerializer(new StringRedisSerializer());
return redisTemplate;
}
}
String refreshToken = Jwts.builder()
.setSubject(authentication.getName())
.setExpiration(new Date(System.currentTimeMillis() + expiration_refresh))
// .signWith(SignatureAlgorithm.HS256, secretKey) // 지원 중단
.signWith(getKey(secretKey), SignatureAlgorithm.HS256)
.compact();
@PostMapping("/reissue/accesstoken")
public ResponseEntity<Boolean> reissueRefreshToken(HttpServletRequest request) {
Cookie[] cookies = request.getCookies();
String refresh_token = "";
for (Cookie cookie : cookies) {
if ("refresh_token".equals(cookie.getName())) {
refresh_token = cookie.getValue();
}
}
String uid = jwtProvider.getUid(refresh_token);
Optional<User> optionalUser = userRepository.findByUid(uid);
// redis에 있는 RT와 Cookie의 RT가 같을 때
if (refresh_token.equals(redisService.get(uid))) {
if ("ACCESS".equals(jwtProvider.validateToken(refresh_token))) { // RT가 유효할 시
CustomUserDetails customUserDetails = new CustomUserDetails(optionalUser.get().getUid(), optionalUser.get().getPassword(), optionalUser.get().getNickname(), Arrays.asList(new SimpleGrantedAuthority(optionalUser.get().getRoles())));
Authentication authentication = new UsernamePasswordAuthenticationToken(customUserDetails, "", customUserDetails.getAuthorities());
TokenDTO tokenDTO = jwtProvider.createToken(authentication);
HttpHeaders headers = new HttpHeaders();
headers.set(HttpHeaders.AUTHORIZATION, "Bearer " + tokenDTO.getAccess());
// log.info("새로 뽑았다 AT => " + tokenDTO.getAccess());
return ResponseEntity.ok().headers(headers).body(true);
}
}
return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(false); // 상태코드: 400
}
Token과 관련된 기능을 하는 서비스가 하나뿐이라 Service단을 따로 만들지는 않고 컨트롤러에 전부 구현했음
