프로젝트 JWT 정리

seokseungmin·2024년 11월 6일

Today I Learned

목록 보기
13/20

📌 1. Access 토큰을 응답 헤더에 넣은 이유

설명 내용:

  • JavaScript 접근 차단: Access 토큰을 응답 헤더에 포함시킴으로써, 클라이언트 측 JavaScript가 토큰에 직접 접근하지 못하게 하여 XSS 공격으로부터 보호.

확인 및 추가 설명:

  • 보안 강화: Access 토큰을 응답 헤더에 포함시키면, 클라이언트 측에서 토큰을 노출시키지 않고 Authorization 헤더를 통해 안전하게 전송할 수 있습니다.
  • 관리의 명확성: 클라이언트는 Access 토큰을 필요할 때마다 Authorization 헤더에 포함시켜 요청하므로, 토큰 관리가 보다 명확해집니다.

📌 2. Refresh 토큰을 쿠키에 저장한 이유

설명 내용:

  • 자동 전송: Refresh 토큰을 쿠키에 저장하면, 브라우저가 자동으로 서버에 전송하므로 클라이언트가 별도로 토큰을 관리할 필요가 없음.

확인 및 추가 설명:

  • 편의성: 쿠키를 사용하면, 클라이언트 애플리케이션이 Refresh 토큰을 별도로 저장하거나 관리할 필요가 없어 개발 편의성이 향상됩니다.
  • 보안 고려: 단, 쿠키에 저장할 때 HttpOnly 속성을 설정하지 않으면, 클라이언트 측 스크립트에서 쿠키에 접근할 수 있어 XSS 공격에 취약해집니다.

📌 3. HttpOnly 설정 미적용의 문제점 및 개선 방안

설명 내용:

  • HttpOnly 설정 필요성: 쿠키에 저장된 Refresh 토큰이 JavaScript를 통해 접근되지 않도록 HttpOnly 설정을 적용해야 함.

확인 및 추가 설명:

  • 보안 강화: HttpOnly 속성을 설정하면, 클라이언트 측 스크립트가 쿠키에 접근할 수 없어 XSS 공격으로부터 Refresh 토큰을 보호할 수 있습니다.
  • 추가 설정:
    • Secure 속성: HTTPS를 통해서만 쿠키가 전송되도록 설정하여 MITM(Man-in-the-Middle) 공격을 방지합니다.
    • SameSite 속성: Strict 또는 Lax로 설정하여 CSRF(Cross-Site Request Forgery) 공격을 방지합니다.

수정된 CookieUtil.createCookie 예시:

package com.zerobase.user.util;

import jakarta.servlet.http.Cookie;
import org.springframework.stereotype.Component;

@Component
public class CookieUtil {

    /**
     * HTTPOnly 쿠키 생성
     * @param name 쿠키 이름
     * @param value 쿠키 값
     * @return 설정된 쿠키 객체
     */
    public Cookie createCookie(String name, String value) {
        Cookie cookie = new Cookie(name, value);
        cookie.setHttpOnly(true); // HTTPOnly 설정
        cookie.setSecure(true);    // HTTPS를 사용하는 경우 Secure 설정
        cookie.setPath("/");       // 쿠키가 유효한 경로 설정
        cookie.setMaxAge(86400);   // 쿠키의 유효 기간 설정 (예: 1일)
        cookie.setSameSite("Strict"); // SameSite 설정 (Strict, Lax, None)
        return cookie;
    }
}

📌 4. Refresh 토큰의 긴 만료 시간과 보안 고려사항

설명 내용:

  • 긴 만료 시간의 이유: Refresh 토큰의 긴 생명 주기는 사용자 경험을 향상시키고, 서버의 인증 요청 부담을 줄이기 위함.
  • 보안 고려사항: Refresh 토큰이 탈취될 경우, 긴 기간 동안 악용될 수 있으므로, 토큰 무효화재발급 시 기존 토큰 삭제가 필요함.

확인 및 추가 설명:

  • Refresh Token Rotation: Refresh 토큰을 재발급할 때마다 기존 토큰을 삭제하고 새로운 토큰을 발급받는 방식은 보안성을 크게 향상시킵니다. 이는 Refresh Token의 재사용을 방지하여 탈취 시 피해를 최소화합니다.
  • 서버 측 관리: Refresh 토큰을 데이터베이스에 저장하고, 재발급 시 기존 토큰을 삭제함으로써 서버 측에서 토큰의 유효성을 철저히 관리할 수 있습니다.

📌 5. Refresh 토큰 탈취 시 발생 가능한 문제

설명 내용:

  • Access 토큰 재발급 악용: 탈취된 Refresh 토큰을 통해 새로운 Access 토큰과 Refresh 토큰을 발급받을 수 있어, 공격자가 장기간 동안 사용자 계정을 악용할 수 있음.
  • 완전한 세션 탈취: Refresh 토큰의 긴 만료 시간으로 인해, 공격자가 오랜 기간 동안 접근 권한을 유지할 수 있음.

확인 및 추가 설명:

  • 리스크 관리: Refresh 토큰의 탈취는 심각한 보안 문제로, 이를 방지하기 위해 HttpOnly 쿠키 설정, Refresh Token Rotation, 서버 측 토큰 검증 등이 필수적입니다.

📌 6. 현재 구현된 코드 분석 및 설명

Access 토큰과 Refresh 토큰의 전송 방식

  • Access 토큰: 응답 헤더(access 헤더)에 포함시켜 전송함으로써, 클라이언트 측 JavaScript가 직접 접근할 수 없도록 함.
  • Refresh 토큰: 쿠키에 저장하여 브라우저가 자동으로 서버에 전송하도록 설정함. 그러나 현재 구현에서는 HttpOnly 속성이 설정되지 않아 보안 취약점이 존재함.

Refresh 토큰 재발급 로직

  • 재발급 시 기존 토큰 삭제: Refresh 토큰을 재발급할 때, 기존의 Refresh 토큰을 데이터베이스에서 삭제하고 새로운 토큰을 생성하여 저장함으로써 재사용을 방지하고 서버 측에서 토큰의 유효성을 관리함.

사용자 설명:
1. Access 토큰을 응답 헤더에 넣은 이유: JavaScript 접근 방지 및 XSS 공격 보호.
2. Refresh 토큰을 쿠키에 저장한 이유: 브라우저 자동 전송 및 클라이언트의 토큰 관리 불필요.
3. HttpOnly 설정 권장: 쿠키에 저장 시 JavaScript 접근 방지.
4. Refresh 토큰 재발급 시 기존 토큰 삭제: 재사용 방지 및 서버 측 토큰 관리 주도권 확보.

코드 분석:

  • Access 토큰: 응답 헤더에 설정 (response.setHeader("access", access);)
  • Refresh 토큰: 쿠키에 설정 (response.addCookie(cookieUtil.createCookie("refresh", refresh));)
  • Refresh 토큰 재발급 시 기존 토큰 삭제: ReissueService 클래스에서 refreshRepository.deleteByRefresh(oldRefreshToken);로 기존 토큰 삭제 후 새로운 토큰 저장

📌 7. 추가적인 보안 강화 방안

1. HttpOnly 설정 강화

  • 필수 적용: Refresh 토큰을 쿠키에 저장할 때 반드시 HttpOnly 속성을 설정하여 JavaScript를 통한 접근을 차단해야 합니다.
  • Secure 속성: HTTPS 환경에서만 쿠키가 전송되도록 Secure 속성도 설정하는 것이 좋습니다.

2. Refresh Token의 만료 시간 관리 개선

  • Date 타입 사용: Refresh 토큰의 만료 시간을 String 대신 Date 타입으로 저장하여 시간 비교 및 관리의 용이성을 높입니다.
  • 자동 삭제 스케줄링: 주기적으로 만료된 Refresh 토큰을 데이터베이스에서 삭제하는 스케줄링 작업을 구현하여 데이터베이스를 깔끔하게 유지합니다.

예시:

@Service
public class TokenCleanupService {

    private final RefreshRepository refreshRepository;

    @Autowired
    public TokenCleanupService(RefreshRepository refreshRepository) {
        this.refreshRepository = refreshRepository;
    }

    @Scheduled(cron = "0 0 * * * ?") // 매 시간 정각에 실행
    public void removeExpiredTokens() {
        refreshRepository.deleteByExpirationBefore(new Date());
    }
}

3. Refresh Token 재발급 시 기존 토큰 삭제 및 새로운 토큰 저장

  • 현재 구현: ReissueService에서 기존 Refresh 토큰을 삭제하고 새로운 토큰을 저장하는 로직이 잘 구현되어 있음.
  • 보안성 확보: 이를 통해 Refresh 토큰의 재사용을 방지하고, 서버 측에서 토큰의 유효성을 철저히 관리할 수 있습니다.

4. 보안 헤더 설정 강화

  • Content Security Policy (CSP): XSS 공격을 방지하기 위해 CSP를 설정합니다.
  • X-Content-Type-Options, X-Frame-Options: 다양한 보안 헤더를 설정하여 애플리케이션의 보안을 강화합니다.

5. HTTPS 사용 보장

  • 전체 통신 암호화: 모든 통신이 HTTPS를 통해 이루어지도록 서버 설정을 강화하여, 중간자 공격(MITM)을 방지합니다.

6. 토큰 유효성 검증 강화

  • Issuer 및 Audience 클레임 설정: JWT 생성 시 issuer(iss)와 audience(aud) 클레임을 설정하고, 서버에서 이를 검증하여 토큰의 신뢰성을 강화합니다.

📌 8. 결론

JWT 기반 인증 시스템의 핵심 보안 원칙을 잘 반영하고 있습니다. 특히, Access 토큰을 응답 헤더에 넣는 이유Refresh 토큰을 쿠키에 저장하는 이유, 그리고 Refresh 토큰의 재발급 시 기존 토큰을 삭제하여 재사용을 방지하는 접근 방식은 보안적으로 적절한 선택입니다.

추가적으로 고려할 사항:

  • HttpOnly 속성 설정: Refresh 토큰을 저장하는 쿠키에 반드시 HttpOnly 속성을 설정하여, XSS 공격으로부터 토큰을 보호해야 합니다.
  • Refresh Token 만료 시간 관리: Date 타입으로 만료 시간을 관리하고, 만료된 토큰을 주기적으로 삭제하는 로직을 구현해야 합니다.
  • 보안 헤더 및 HTTPS: 다양한 보안 헤더를 설정하고, 모든 통신이 HTTPS를 통해 이루어지도록 서버를 설정하여 전반적인 보안성을 강화해야 합니다.

요약:

  • Access 토큰응답 헤더에 포함시켜 XSS 공격으로부터 보호.
  • Refresh 토큰HTTPOnly 쿠키에 저장하여 자동 전송XSS 공격으로부터 보호.
  • Refresh Token Rotation을 통해 기존 토큰을 삭제하고 새로운 토큰을 발급받아 재사용 방지서버 측 토큰 관리 주도권 확보.
  • 추가적인 보안 강화를 통해 시스템의 전반적인 보안성을 높이자.

profile

0개의 댓글