본문 바로가기

개발기술/보안

JWT Refresh Token Strategy

Authentication Strategies Comparison

Authentication Strategy Description Use Case
Session-Based Authentication Stores user session in the server (stateful) Monolithic apps, Spring Security with sessions
JWT-Based Authentication
(Without Refresh Token)
Uses a single JWT for authentication, but requires re-login when expired Simple stateless APIs
JWT + Refresh Token
(This Strategy)
Uses a short-lived access token + a long-lived refresh token for session renewal Microservices, scalable REST APIs
OAuth 2.0 Authorization Code Flow Uses authorization servers (e.g., Google, Facebook) with refresh tokens Third-party authentication

* OAuth (Open Authorization) is an industry-standard protocol

 

 

How Refresh Tokens Work (Flow)

Step 1: User Logs In : The client sends username/password to the authentication server.

  • The server validates credentials and returns: 
    • Access Token (Short-lived, e.g., 15 min) Refresh Token (Long-lived, e.g., 7 days)

Step 2: Using the Access Token :

  • The client includes the access token in API requests and The server validates 

Step 3: Access Token Expires :

  • When the access token expires, the client gets a 401 Unauthorized response.
    • Case 1: Client Does Nothing Until the Access Token Expires ( User Experience Impact)
    • Case 2: Client Proactively Refreshes Before Expiry ( No User Experience Impact)
      • The client keep track of when the access token expires and refresh it before it actually expires.

Step 4: Client Requests a New Access Token Using the Refresh Token

  • The client sends the refresh token to the authentication server.
  • If the refresh token is valid, the server issues a new access token.

Step 5: Client Uses the New Access Token

  • The client resumes API requests using the new access token.

 

Implementing Refresh Token

  • refresh tokens need to be stored in a database (or another persistent storage like Redis) instead of keeping them in memory.
    • To Immediately revokr yorkn
    • to minimize unnecessary storage growth when using black list
    • To prevent  replay attacksBetter database performance ✔ Blacklisting is useful for long-
import lombok.RequiredArgsConstructor;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.stereotype.Service;
import java.time.Instant;
import java.util.Optional;
import java.util.UUID;

@Service
@RequiredArgsConstructor
public class AuthService {

    private final AuthenticationManager authenticationManager;
    private final UserDetailsService userDetailsService;
    private final JwtUtil jwtUtil;
    private final RefreshTokenRepository refreshTokenRepository;
    private final UserRepository userRepository;

    public AuthResponse authenticate(LoginRequest request) {
        authenticationManager.authenticate(
                new UsernamePasswordAuthenticationToken(request.getEmail(), request.getPassword())
        );

        UserDetails userDetails = userDetailsService.loadUserByUsername(request.getEmail());
        String accessToken = jwtUtil.generateAccessToken(userDetails.getUsername());
        String refreshToken = createRefreshToken(userDetails.getUsername());

        return new AuthResponse(accessToken, refreshToken);
    }

    private String createRefreshToken(String username) {
        UserEntity user = userRepository.findByEmail(username)
                .orElseThrow(() -> new RuntimeException("User not found"));

        RefreshToken refreshToken = new RefreshToken();
        refreshToken.setToken(UUID.randomUUID().toString());
        refreshToken.setUser(user);
        refreshToken.setExpiryDate(Instant.now().plusSeconds(7 * 24 * 60 * 60)); // 7 days

        refreshTokenRepository.save(refreshToken);
        return refreshToken.getToken();
    }

    public AuthResponse refreshToken(RefreshTokenRequest request) {
        Optional<RefreshToken> storedToken = refreshTokenRepository.findByToken(request.getRefreshToken());

        if (storedToken.isEmpty() || storedToken.get().getExpiryDate().isBefore(Instant.now())) {
            throw new RuntimeException("Invalid or expired refresh token");
        }

        String username = storedToken.get().getUser().getEmail();
        String newAccessToken = jwtUtil.generateAccessToken(username);

        return new AuthResponse(newAccessToken, request.getRefreshToken());
    }

    public void logout(String username) {
        UserEntity user = userRepository.findByEmail(username)
                .orElseThrow(() -> new RuntimeException("User not found"));

        refreshTokenRepository.deleteByUser(user);
    }
}

'개발기술 > 보안' 카테고리의 다른 글

브라우저 보안정책  (0) 2025.02.26
네트워크 접근제어(NAC) ; NAT, 방화벽  (0) 2025.02.25
보안의 개념과 기술  (0) 2025.01.19
HTTPS 의 TLS 동작과정  (0) 2025.01.18
Security JWT,세션, 쿠키방식  (0) 2025.01.15