[Spring] JWT + Redis를 활용한 로그아웃 구현 (Jwt BlackList)

2024. 10. 6. 22:23·Back-end/Spring
반응형

프로젝트를 진행하면서, 사용자의 인증 여부를 확인하기 위해 JWT 토큰 기반의 방식을 선택했다.

보안과 JWT를 공부하면서 refresh 토큰을 제대로 사용해본 적이 없어 2024년 여름방학 때 확실하게 공부해야겠다는 생각이 들었다.
처음 프로젝트 때 카카오 소셜 로그인을 사용하면서 access token 만으로 인증 방식을 진행했지만, access token을 탈취당했을 상황의 문제점에 대해 고려하면서 refresh token을 도입하여 보완하고, 로그아웃 이후의 탈취상황을 고려해 블랙리스트(Blacklist)라는 개념을 찾게 되어 사용해보고자 도입해봤다.

 

Refresh Token 로직

  1. 클라이언트 로그인 요청
  2. 서버에서 access token, refresh token 생성 및 반환
    access token의 유효기간을 짧게, refresh token을 길게 가져간다.
    이때, refresh token을 redis에 저장한다.
  3. 인증이 필요한 요청시 기존과 동일하게 access token을 header에 담아서 요청한다.

refresh token을 Redis에 저장하므로써 독립적으로 유효기간을 설정해서 관리할 수 있고 유저 식별자를 키-값으로 저장하여 쉽게 저장할 수 있다는 장점을 얻었다.

 

이후 access token이 만료됐다면, refresh token으로 access token 재발급을 요청한다.

또 refresh token이 만료됐다면, 로그인을 재요청한다.

 

JWT BlackList 사용

Jwt BlackList를 사용하게 되는 경우, 로그아웃 기능을 적용할 수 있다.

 

1. 사용자가 access token을 바디에 담아서 로그아웃 요청을 보낸다.

// 로그아웃
@Transactional
public void logout(TokenDTO tokenDTO) {

    String accessToken = tokenDTO.getAccessToken();
    String email = jwtProvider.getEmailFromAccessToken(accessToken);

    log.info("access token: {}", accessToken);
    log.info("email : {}", email);

    // redis에서 refresh Token 제거
    redisService.deleteRefreshToken(email);

    // 해당 엑세스 토큰의 남은 유효시간을 얻어서 Access Token blacklist에 등록하여 만료시키기
    Long tokenExpiration = jwtProvider.getTokenExpiration(accessToken);
    redisService.setBlackList(accessToken, "logout", tokenExpiration);
}

 

액세스 토큰에서 email 값을 추출하여 redisService에서 이를 통해 refreshToken을 제거한 뒤, 남아있는 유효시간을 가져와서JwtBlackList를 생성하여 redis에 삽입한다.

 

2. 이후, API 요청을 보낼 때마다 서버에서 Jwt 토큰 유효성 검사를 진행할 때, 해당 토큰이 redis 내 BlackList로 등록되어있다면 로그아웃 처리된 토큰이므로 재발급하라는 응답을 보낸다.

```JwtProvider.java

...

public boolean validate(String token) {
    try {
        JWT.require(algorithm).build().verify(token);
        if (redisService.hasKeyBlackList(token)) { // JWT BlackList
            return false;
        }
        return true;
    } catch (JWTVerificationException e) {
        return false;
    }
}

 

 

 

RedisService.java

 

import lombok.RequiredArgsConstructor;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;

import java.util.concurrent.TimeUnit;

@Service
@RequiredArgsConstructor
public class RedisService {

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

    @Value("${jwt.refresh-token.expiration}")
    private Long refreshTokenExpiration;

    public void saveRefreshToken(String key, String value) {
        redisTemplate.opsForValue().set(key, value, refreshTokenExpiration, TimeUnit.HOURS);
    }

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

    public void deleteRefreshToken(String key) {
        redisTemplate.delete(key);
    }

    public void setBlackList(String key, String o, Long milliSeconds) {
        redisBlackListTemplate.opsForValue().set(key, o, milliSeconds, TimeUnit.HOURS);
    }

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

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

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

 

refresh token을 저장하기 위한 RedisTemplate 외에, JWT BlackList를 저장하기 위한 RedisTemplate를 생성한다.

 

 

 

참고

https://velog.io/@boo105/Redis-%EB%A5%BC-%ED%86%B5%ED%95%9C-JWT-Blacklist-%EA%B5%AC%ED%98%84

 

Redis 를 통한 JWT Blacklist 구현

Redis, Jwt, Logout

velog.io

 

반응형

'Back-end > Spring' 카테고리의 다른 글

[Spring] @RequestPart vs @RequestParam vs @RequestBody  (1) 2024.10.11
[Spring] Spring Boot에서 Google Meet API 적용해보기  (11) 2024.10.06
[Spring] Query Parameter vs Path Variable  (2) 2024.10.02
[Spring] Security + JWT + Redis를 활용한 로그인 구현 (2)  (0) 2024.08.30
[Spring] @NotNull vs @Column(nullable = false)  (1) 2024.08.19
'Back-end/Spring' 카테고리의 다른 글
  • [Spring] @RequestPart vs @RequestParam vs @RequestBody
  • [Spring] Spring Boot에서 Google Meet API 적용해보기
  • [Spring] Query Parameter vs Path Variable
  • [Spring] Security + JWT + Redis를 활용한 로그인 구현 (2)
류건
류건
개발 일지
  • 류건
    건's Dev
    류건
  • 전체
    오늘
    어제
    • 분류 전체보기 (94)
      • Back-end (55)
        • Spring (30)
        • Nest.js (3)
        • Next.js (2)
        • Node.js (3)
      • Infra & Cloud (20)
        • Cloud Computing (6)
        • Docker (3)
        • AWS (7)
      • Java (2)
      • Computer Science (12)
        • Computer Network (0)
        • Operating System (0)
        • 정보 보호와 시스템 보안 (12)
      • 회고록 (1)
        • 우아한테크코스 (3)
  • 블로그 메뉴

    • 홈
    • 태그
    • 방명록
  • 링크

  • 공지사항

  • 인기 글

  • 태그

    보안
    어노테이션
    CI/CD
    Spring Boot
    정보보호
    티스토리챌린지
    Docker
    JWT
    public key
    ssl
    오블완
    WebClient
    aws
    express.js
    https
    ddl-auto
    CORS
    CD
    Webflux
    Lock
    db
    JPA
    node.js
    Nest.js
    Kafka
    Spring
    nginx
    EC2
    고가용성
    Github Actions
  • 최근 댓글

  • 최근 글

  • 반응형
  • hELLO· Designed By정상우.v4.10.0
류건
[Spring] JWT + Redis를 활용한 로그아웃 구현 (Jwt BlackList)
상단으로

티스토리툴바