New Bulls 프로젝트 4

chaean·2024년 7월 4일

프로젝트 - Bulls

목록 보기
11/11

2024.06.27

1. Refresh Token 발급

String refreshToken = Jwts.builder()
                .setExpiration(new Date(System.currentTimeMillis() + expiration_refresh))
//                .signWith(SignatureAlgorithm.HS256, secretKey) // 지원 중단
                .signWith(getKey(secretKey), SignatureAlgorithm.HS256)
                .compact();
  • Refresh Token 쿠키에 저장 ( 클라이언트 )
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);
Refresh Token을 통한 Access Token 재발급 로직

Access Token 검증
-> 유효 O
-> 인증

Access Token 검증
-> 유효 X
-> 401에러 발생 (HttpServletResponse.SC_UNAUTHORIZED)
-> 클라이언트에서 401에러 발생 시 Refresh Token를 통한 Access Token 재발급 API 호출

2024.06.29

Refresh Token은 어디에 ?? < DB / Redis >

  • refresh token을 통해 access token을 재발급 하는 과정에서 DB에 많은 부하가 걸린다. 이는 성능저하로 이어진다.
    -> Redis + docker를 사용해볼 예정.

    - 따라서 이제까지 했던 Refresh Token DB 저장 로직은 삭제

1. docker pull redis

  • redis의 이미지 다운

2. docker run --name test_redis -p 6379:6379 -d redis

- 도커 실행 (
이름 : test_redis, 
포트포워딩 (호스트 포트:컨테이너 포트) : 외부에서 6379포트로 접근하면 6379포트로 포워딩이 된다.
-d : 컨테이너를 데몬으로 실행 (백그라운드로 실행)
)
docker stop {container_name} - 중지
docker start {container_name} - 시작
docker restart {container_name} - 재시작

2024.07.03

1. RedisConfig 생성

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;
    }

}

2. RT에 userId (subject) 추가

String refreshToken = Jwts.builder()
                .setSubject(authentication.getName())
                .setExpiration(new Date(System.currentTimeMillis() + expiration_refresh))
//                .signWith(SignatureAlgorithm.HS256, secretKey) // 지원 중단
                .signWith(getKey(secretKey), SignatureAlgorithm.HS256)
                .compact();

3. Refresh Token으로 Access Token을 재발급하는 API 구현

    @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단을 따로 만들지는 않고 컨트롤러에 전부 구현했음

  • 이후 Axios Interceptors를 이용해 401에러를 만나면 재발급 API를 호출하여 갱신하는 방식으로 설정!!

전체 플로우

처음 그려보는거라 맞게 그린건지 모르겠다 ㅎㅎ..
profile
백엔드 개발자

0개의 댓글