Springboot

🚀 Refresh Token 저장 방식 & 만료된 토큰 관리 방법

수아파파's 2025. 2. 13. 21:41
반응형

Refresh Token 저장 방식 (DB vs 클라이언트 vs Redis)
Refresh Token 보안 위험 (XSS/CSRF 공격 대응 방법)
만료된 Refresh Token 관리 & 강제 로그아웃 기능 구현 방법


1️⃣ Refresh Token 저장 방식

Refresh Token은 어디에 저장하느냐에 따라 보안성과 사용자 경험이 달라짐.
보편적으로 아래 3가지 방식을 고려할 수 있음.

🔹 1. 클라이언트(LocalStorage, Cookie) 저장 방식 (❌ 보안 취약)

장점:

  • 서버 부담이 없음 (Stateless)
  • 클라이언트가 저장하고 필요할 때 사용

단점:

  • LocalStorage에 저장 시 XSS 공격에 취약 (해커가 자바스크립트로 접근 가능)
  • 쿠키에 저장 시 CSRF 공격 위험 (쿠키 탈취 가능)
  • Refresh Token이 탈취되면 무제한 Access Token 발급 가능 → 매우 위험

📌 결론:

보안이 중요한 경우, 클라이언트에 Refresh Token을 직접 저장하는 방식은 피하는 것이 좋음 ❌


🔹 2. DB(MySQL, PostgreSQL 등) 저장 방식 (✅ 안전한 방식)

장점:

  • Refresh Token을 DB에 저장하고 관리 가능
  • 토큰을 탈취당했을 경우 강제 로그아웃(토큰 폐기) 가능

단점:

  • DB 조회 부담이 증가 (로그인 시, 토큰 갱신 시마다 DB 접근)
  • 분산 서버 환경에서는 DB 동기화 문제 발생 가능

📌 구현 방식:

CREATE TABLE refresh_tokens (
    id BIGINT AUTO_INCREMENT PRIMARY KEY,
    user_id BIGINT NOT NULL,
    token VARCHAR(512) NOT NULL,
    expires_at TIMESTAMP NOT NULL
);

📌 유효하지 않은 Refresh Token을 DB에서 삭제하면 즉시 무효화 가능!


🔹 3. Redis 캐시 저장 방식 (✅ 보안 & 성능 균형)

장점:

  • 빠른 속도로 토큰을 저장하고 조회 가능 (DB보다 성능 좋음)
  • 만료 시간이 지나면 자동 삭제 가능 (TTL 설정)
  • 강제 로그아웃 시 즉시 삭제 가능

단점:

  • Redis가 다운되면 로그인 유지 불가
  • 추가적인 Redis 인프라 운영 비용 발생

📌 구현 방식 (Spring Boot + Redis 적용 예시)

@RedisHash("refreshToken")
public class RefreshToken {
    @Id
    private String token;
    private Long userId;
    private LocalDateTime expiresAt;
}

📌 Refresh Token을 Redis에 저장하면 빠르게 조회 가능 & 일정 시간이 지나면 자동 만료됨!


2️⃣ Refresh Token 보안 위험 & 대응 방법

🔹 Refresh Token이 클라이언트에 저장될 경우 발생하는 보안 문제

  1. XSS 공격 (Cross-Site Scripting)
    • 해커가 자바스크립트를 삽입하여 LocalStorage에서 토큰을 탈취할 수 있음
    • 해결책: LocalStorage 대신 HttpOnly Cookie 사용 or 서버 저장 방식 사용
  2. CSRF 공격 (Cross-Site Request Forgery)
    • 사용자의 인증 정보를 탈취하여 공격자가 다른 사용자로 위장할 수 있음
    • 해결책: SameSite 설정을 통해 다른 사이트에서의 요청을 차단

📌 쿠키 저장 시 보안 설정 예시:

ResponseCookie cookie = ResponseCookie.from("refreshToken", refreshToken)
        .httpOnly(true)  // XSS 공격 방지
        .secure(true)    // HTTPS 환경에서만 전송
        .sameSite("Strict") // CSRF 방지
        .maxAge(Duration.ofDays(7)) // 7일간 유효
        .path("/")
        .build();

HttpOnly + Secure + SameSite 속성을 설정하면 보안 강화 가능!


3️⃣ 만료된 Refresh Token 관리 & 강제 로그아웃 구현

🔹 Refresh Token이 탈취되었을 경우 문제점

  • Refresh Token이 해커에게 탈취되면 Access Token을 무제한으로 갱신 가능
  • 로그아웃을 하더라도, Refresh Token이 살아 있으면 계속 사용할 수 있음

🔹 Refresh Token 강제 만료(로그아웃) 구현 방법

DB 저장 방식일 경우

  • 사용자가 로그아웃하면 DB에서 Refresh Token을 삭제하여 즉시 무효화
DELETE FROM refresh_tokens WHERE user_id = ?;
  • Refresh Token을 해커가 탈취했더라도 삭제된 토큰은 사용할 수 없음

Redis 저장 방식일 경우

  • Redis에서 해당 사용자의 Refresh Token 삭제
redisTemplate.delete("refreshToken:" + userId);

📌 이렇게 하면 해당 사용자는 Refresh Token이 만료되므로 재로그인 필요!


🎯 결론: 안전한 Refresh Token 저장 및 관리 방법

🔹 추천하는 저장 방식

DB 저장 (보안이 중요할 경우 추천)
Redis 저장 (보안 & 성능 균형을 원할 경우 추천)
LocalStorage 저장 (보안 취약하므로 추천하지 않음)

🔹 Refresh Token 보안 강화 방법

  • HttpOnly, Secure, SameSite 설정으로 CSRF/XSS 공격 방어
  • 만료된 Refresh Token을 즉시 폐기할 수 있도록 관리(DB, Redis 활용)
  • 로그아웃 시 Refresh Token을 삭제하여 강제 만료 처리

🔹 Refresh Token을 안전하게 관리하면?

보안 강화 + 사용자 경험 향상 (자동 로그인 유지 가능)
Access Token을 짧게 설정하여 보안성을 유지하면서도 편리한 로그인 가능

🔥 이제 보안 강화를 위해 Refresh Token을 안전하게 저장하고, 강제 로그아웃 기능도 추가해보자! 🚀

반응형