[스프링(spring)]jwt+redis를 활용한 로그아웃

allnight5·2023년 2월 9일
0

스프링

목록 보기
47/62

jwt를 구현했다는 전제하에 redis를 활용하여 해보겠다.
쿠키세션에 저장소가 추가된다고 했는데 결국 redis도 저장소를 써야된다.
다른곳에서 쓰던가 그 서버에 쓰던가 그 차이 인데 결국쓰긴 쓴다는 것이지 저장소를 쓰지 않는다는 말이 아니다. 기억해 두자

그러니 다른말로 하면 redis가 아닌 스프링에서 제공해주는 JpaRepository를 활용해서도 가능하고 컴퓨터에서 다른 데이터베이스를 활용해도 가능하다

private String grantType;
private String accessToken;
private String refreshToken;
private Long refreshTokenExpirationTime;

그 테이블에 저 4개의 컬럼을 가지고 있으면 redis대신에 사용이 가능하다.
key부분을 grantType으로 해주는정도?

프론트 없이 백엔드로만 하는것임..

설정부분

implementation 'org.springframework.boot:spring-boot-starter-data-redis'
implementation group: 'it.ozimov', name: 'embedded-redis', version: '0.7.1'

redis사용을 위한 의존성 부분으로
2번째는 redis를 도커나 추가없이 사용하기 위해서 넣어주는것이다.

RedisConfig클래스

@Configuration
@EnableRedisRepositories
public class RedisConfig {


    private final RedisProperties redisProperties;

    @Bean
    public RedisConnectionFactory redisConnectionFactory() {
        return new LettuceConnectionFactory(redisProperties.getHost(), redisProperties.getPort());
    }
    
    @Bean
    public RedisTemplate<String, Object> redisTemplate() {
        RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();
        redisTemplate.setConnectionFactory(redisConnectionFactory());
        redisTemplate.setKeySerializer(new StringRedisSerializer());
        redisTemplate.setValueSerializer(new StringRedisSerializer());

        return redisTemplate;
    }
}

LocalRedisConfig클래스

@Component
public class LocalRedisConfig {
    private RedisServer redisServer;
    @Value("${spring.data.redis.port}")
    private int port;
    @PostConstruct
    public void redisServer() throws IOException {
        redisServer = new RedisServer(port);
        redisServer.start();
    }

    @PreDestroy
    public void stopRedis() {
        if (redisServer != null) {
            redisServer.stop();
        }
    }

}

LocalRedisConfig클래스의 경우 도커나 종합개발 환경(IDE)에 데이터베이스를 따로 만들어주지 않고 실행할때 실행시켜주는 클래스다.

RedisUtil


@Component
@RequiredArgsConstructor
public class RedisUtil {

    private final RedisTemplate<String, Object> redisTemplate;
    private final RedisTemplate<String, Object> redisBlackListTemplate;

    public void setRefreshToken(String key, Object o, Long minutes) {
        redisTemplate.setValueSerializer(new Jackson2JsonRedisSerializer<>(o.getClass()));
        redisTemplate.opsForValue().set(key, o, minutes, TimeUnit.MINUTES);
    }

    public Object getRefreshToken(String key) {
        return redisTemplate.opsForValue().get(key);
    }

    public boolean deleteRefreshToken(String key) {
        return Boolean.TRUE.equals(redisTemplate.delete(key));
    }

    public boolean hasKey(String key) {
        return Boolean.TRUE.equals(redisTemplate.hasKey(key));
    }

    public void setBlackList(String key, Object o, Long minutes) {
        redisBlackListTemplate.setValueSerializer(new Jackson2JsonRedisSerializer<>(o.getClass()));
        redisBlackListTemplate.opsForValue().set(key, o, minutes, TimeUnit.MINUTES);
    }

    public boolean hasKeyBlackList(String key) {
        return Boolean.TRUE.equals(redisBlackListTemplate.hasKey(key));
    }

    public Object getBlackList(String key) {
        return redisBlackListTemplate.opsForValue().get(key);
    }

    public boolean deleteBlackList(String key) {
        return Boolean.TRUE.equals(redisBlackListTemplate.delete(key));
    }
}

key는 유저가 될수도 있고 access token이 될수도 있다.
넣는 사람이 무엇을 넣느냐에 따라 달라진다.

이 클래스를 통해 redis에
set을 이용해 값을 넣고 get을 이용해 가져오며
delete를 사용해 삭제하고 haskey를 사용해 있는지 확인하는
메소드들이다

로그인 되어 있을때 검사하는곳(시큐리티에서 jwt 토큰 검증(jwtFiler 클래스 정도?))


            boolean refreshToken = redisUtil.hasKeyBlackList(token);
            if(refreshToken){
                throw new IllegalArgumentException("Please Login again.");
            }

토큰 검사하고 다음 부분에 추가해주면되는데 setAuthentication(SecurityContextHolder 에 넣기전에) 바로 전에 넣어줘서
이게 로그인 된것인지 로그아웃한 것인지 알수있게 해주면 된다.

jwtUtil accessToken발급부분에 REFRESH_TOKEN추가)

    public LoginResponseDto createToken(String username, UserRoleEnum role) {
    	long ACCESS_TOKEN_TIME = 60 * 30 * 1000L;
        long REFRESH_TOKEN_EXPIRE_TIME = 2 * 24 * 60 * 60 * 1000L;
        Date date = new Date();
        //권한 가져오기
        // BEARER : 인증 타입중 하나로 JWT 또는 OAuth에 대한 토큰을 사용 (RFC 6750 문서 확인)
        String accessToken = BEARER_PREFIX + Jwts.builder()
                        .setSubject(username) // 토큰 용도
                        .claim(AUTHORIZATION_KEY, role) // payload에 들어갈 정보 조각들
                        .setExpiration(new Date(date.getTime() + ACCESS_TOKEN_TIME)) // 만료시간 설정
                        .setIssuedAt(date) // 토큰 발행일
                        .signWith(key, signatureAlgorithm) // key변수 값과 해당 알고리즘으로 sign
                        .compact(); // 토큰 생성

        String refreshToken = Jwts.builder()
                .setExpiration(new Date(date.getTime() + REFRESH_TOKEN_EXPIRE_TIME))
                .signWith(key,signatureAlgorithm)
                .compact();

        return LoginResponseDto.builder()
                .grantType(BEARER_PREFIX)
                .accessToken(accessToken)
                .refreshToken(refreshToken)
                .refreshTokenExpirationTime(REFRESH_TOKEN_EXPIRE_TIME)
                .build();
    }

ACCESS_TOKEN과 같이 넣어주지 말고 따로 해줘도 상관없다.

컨트롤러 부분

sign-in or login메소드

    public MessageResponseDto login(@RequestBody LoginRequestDto loginRequestDto, HttpServletResponse response) {
        //이름과 유저인지 관리자인지 구분한 토큰을 가져오는 부분
        LoginResponseDto msg = userService.login(loginRequestDto);
        //문자열 token에 가져온 정보를 넣어주는 부분
        String token = msg.getAccessToken();
        //헤더를 통해 토큰을 발급해 주는 부분
        response.addHeader(JwtUtil.AUTHORIZATION_HEADER, token);
        return new MessageResponseDto("로그인 되었습니다.");
    }

logout 메소드

    @DeleteMapping("/logout")
    public MessageResponseDto logout(@AuthenticationPrincipal UserDetailsImpl userDetails
    , HttpServletRequest request){
        String accessToken = jwtUtil.resolveToken(request);
        return new MessageResponseDto(userService.logout(accessToken, userDetails.getUsername()));
    }

삭제하고 넣기도 하는데 삭제가 들어가니 DeleteMapping을 써주자

service부분

    @Transactional
    public String logout(String accessToken, String username) {

//        if(redisUtil.hasKey(accessToken)){
//            redisUtil.deleteRefreshToken(username);
//        }
        if(redisUtil.getRefreshToken(accessToken) != null){
            redisUtil.deleteRefreshToken(username);
        }
        // 레디스에 accessToken 사용못하도록 등록
        Long expiration = jwtUtil.getExpiration(accessToken);
        redisUtil.setBlackList(accessToken, "logout", expiration);

        return "로그아웃 완료";
    }

취향에 따라서 has로 있는지 없는지 확인하던지 null인지 아닌지 확인하면된다.

jwtUtil에서 비교할때나 다른곳에서도 blackList를 비교해야하는지 refreshToken을 비교해야하는지 헷갈리지말자 이것때문에 말들었다가 다시만들었다...

profile
공부기록하기

0개의 댓글