개인 프로젝트/modu

[daon] 6일차 개발 회고록

highlev 2026. 5. 29. 17:11

개발 일지 (redis로 토큰 관리하기)

1. redis로 refresh token 관리하기

로그인 시에, userId와 세션 아이디를 기반으로 refresh_token을 만들어서, 레디스에 저장을 한다.

세션 아이디를 같이 받는 이유? -> 다중 로그인을 가능하게 하기 위함

 

이렇게 하지 않으면, user 김씨가 기기 A와 기기 B에 로그인을 한 상태에서, 기기 B에서 로그아웃을 하게 되면 기기 A에서도 로그아웃이 되어버린다.

그걸 방지하기 위해서, 레디스에 refresh token을 redis에 저장을 하고, access_token의 갱신이 필요할 때마다 redis에서 유저 id를 기반와 세션 id 기반으로 찾아낸다.

최종적으로, refresh_token은 다음과 같이 저장이 된다.

key   = refresh:1:abc-123
value = eyJhbGciOiJIUzI1NiJ9...
TTL   = refresh token 만료 시간

코드 뜯어보기

로그인

String sessionId = UUID.randomUUID().toString();
        String refreshToken = jwtTokenProvider.createRefreshToken(user.getId(), user.getUserName(), sessionId);

        refreshTokenRepository.save(
                user.getId(),
                sessionId,
                refreshToken,
                jwtTokenProvider.getRefreshTokenExpirationMillis()
        );

 

세션 ID는 랜덤으로 만들어서, jwt 토큰을 생성한다.

그리고 이 토큰을 레디스에 저장을 한다.

로그아웃

jwtTokenProvider.validateRefreshToken(refreshToken);

            Long userId = jwtTokenProvider.getUserId(refreshToken);
            String sessionId = jwtTokenProvider.getSessionId(refreshToken);

            String savedRefreshToken = refreshTokenRepository.find(userId, sessionId);

            if (savedRefreshToken != null && savedRefreshToken.equals(refreshToken)) {
                refreshTokenRepository.delete(userId, sessionId);
            }

리프레시 토큰에서 userId와 sessionId를 파싱해와서 그걸 기반으로, 제거할 refresh_token을 찾아서 삭제한다.


2. redis로 black list 관리하기

black list가 무엇인가

black list의 존재 이유는, 로그아웃에 있다.

사용자가 로그아웃을 한다면, 그때 가지고 있던 access_token은 더이상 효력이 없어져야 한다.

효력이 없어지지 않는다면, 로그아웃을 했음에도 불구하고, 이 access_token을 이용해서 자유롭게 인증이 필요한 api를 요청을 할 수 있게 된다.

근데 지금 가지고 있는 access_token을 어떻게 무력화할까?

 

access_token은 알다시피, 서버쪽에 저장되지 않는 값이다. 로그인을 하고 나면, 클라이언트쪽에서 저장을 해서 인증이 필요한 api를 호출할 때마다 사용하는 방식이다.

 

그렇다면 이렇게 서버쪽에서 관리하지 않는 access_token을 무력화하기 위해선 어떤 방법을 사용해야할까?

 

redis에다가, 로그아웃 처리된 access_token을 저장하면 해결된다.

access_token은 30분 정도의 유효기간을 가지고 있기 때문에, 이 access_token이 유효한 기간동안 redis에서 이 access_token을 black list처리를 하면, 로그아웃이 됐을 때 이 토큰을 가지고 인증을 할 수가 없게 된다.

jwtAuthenticationFilter에서, 서버로 인증이 필요한 요청이 들어올 때, authentication 헤더에 토큰이 있다면, 이 토큰이 black list에 등록되어있는지 redis에서 찾은 다음에, black list에 등록이 되어있는 access_token이라면 인증 실패 처리를 한다.

코드 뜯어보기

로그아웃

@Transactional
    public void logout(HttpServletRequest request) {
    	// access_token이 헤더에 있는지 확인
        String accessToken = resolveAccessToken(request);

        if (accessToken != null) {
            try {
                jwtTokenProvider.validateAccessToken(accessToken);

                long remainingExpirationMillis =
                        jwtTokenProvider.getRemainingExpirationMillis(accessToken);

                accessTokenBlacklistRepository.save(
                        accessToken,
                        remainingExpirationMillis
                );
            } catch (BusinessException e) {
                // 이미 만료되었거나 유효하지 않은 access token은 blacklist에 넣을 필요가 없으므로 무시
            }
        }

        String refreshToken = getRefreshToken(request)
                .orElse(null);

        if (refreshToken == null) {
            return;
        }

        try {
            jwtTokenProvider.validateRefreshToken(refreshToken);

            Long userId = jwtTokenProvider.getUserId(refreshToken);
            String sessionId = jwtTokenProvider.getSessionId(refreshToken);

            String savedRefreshToken = refreshTokenRepository.find(userId, sessionId);

            if (savedRefreshToken != null && savedRefreshToken.equals(refreshToken)) {
                refreshTokenRepository.delete(userId, sessionId);
            }
        } catch (BusinessException e) {
            // 이미 만료되었거나 유효하지 않은 refresh token이면
            // Redis에서 삭제할 수 없으므로 무시하고 로그아웃 응답은 정상 처리
        }
    }

 

  • access_token
    • access_token이 남아있다면 토큰을 검증해서 유효기간을 구해온다.
    • 유효기간 만큼 레디스에 black list로 등록해놓는다.
  • refresh_token
    • refresh_token은 이미 레디스에서 관리하고 있다.
    • refresh_token은 그냥 레디스에서 삭제만 하면 된다.

black list 명단에 access_token 저장 및 확인

  • access_token을 black list 명단에 저장
public void save(String accessToken, long expirationMillis) {
        if (accessToken == null || accessToken.isBlank()) {
            return;
        }

        if (expirationMillis <= 0) {
            return;
        }

        redisTemplate.opsForValue().set(
                key(accessToken),
                "logout",
                Duration.ofMillis(expirationMillis)
        );
    }

요청이 들어왔을 때, black list에 속하는 access_token인지 확인

public boolean exists(String accessToken) {
        if (accessToken == null || accessToken.isBlank()) {
            return false;
        }

        return Boolean.TRUE.equals(redisTemplate.hasKey(key(accessToken)));
    }

    private String key(String accessToken) {
        return PREFIX + accessToken;
    }

 

'개인 프로젝트 > modu' 카테고리의 다른 글

[daon] 8일차 개발 회고록  (0) 2026.06.10
[daon] 7일차 개발 회고록  (0) 2026.06.07
[daon] 5일차 개발 회고록  (1) 2026.05.25
[daon] 3일차 개발 회고록  (1) 2026.05.12
[daon] 2일차 개발 회고록  (0) 2026.05.06