프론트엔드 개발
JWT 기술분석: Access Token과 Refresh Token 전략
쫌수
2025. 1. 17. 17:50
목차
- JWT(JSON Web Token) 개요
- Access Token vs Refresh Token
- 토큰 관리 전략
- 보안 고려사항
- 구현 모범 사례
1. JWT(JSON Web Token) 개요
1.1 JWT란?
JWT(JSON Web Token)는 당사자 간에 정보를 JSON 객체로 안전하게 전송하기 위한 독립적인 방식을 정의하는 개방형 표준(RFC 7519)입니다. 이 정보는 디지털 서명되어 있으므로 신뢰할 수 있습니다.
1.2 JWT의 구조
JWT는 다음과 같은 세 부분으로 구성됩니다:
- Header: 토큰 유형과 사용된 서명 알고리즘
- Payload: 클레임(claim)들을 포함하는 실제 데이터
- Signature: 토큰의 유효성을 검증하기 위한 서명
예시:
header.payload.signature
2. Access Token vs Refresh Token
2.1 Access Token
- 정의: 보호된 리소스에 접근하기 위한 credentials을 담고 있는 토큰
- 특징:
- 비교적 짧은 유효기간 (보통 15분~2시간)
- 모든 API 요청에 포함되어 전송
- 탈취 위험을 줄이기 위해 짧은 수명 유지
2.2 Refresh Token
- 정의: 새로운 access token을 발급받기 위한 토큰
- 특징:
- 긴 유효기간 (보통 2주~수개월)
- 서버 측에서 안전하게 저장 관리
- Access token이 만료되었을 때만 사용
3. 토큰 관리 전략
3.1 토큰 저장 전략
클라이언트 측:
// Access Token: 메모리에 저장
let accessToken = response.accessToken;
// Refresh Token: HttpOnly 쿠키에 저장
document.cookie = `refreshToken=${response.refreshToken}; HttpOnly; Secure; SameSite=Strict`;
서버 측:
// Refresh Token 저장 예시 (Redis 사용)
await redis.set(
`refresh_token:${userId}`,
refreshToken,
'EX',
60 * 60 * 24 * 14 // 14일 유효기간
);
3.2 토큰 갱신 전략
Silent Refresh
- Access Token 만료 직전에 자동으로 갱신
- 백그라운드에서 사용자 경험 방해 없이 처리
Rotation 전략
- Refresh Token 사용 시마다 새로운 Refresh Token 발급
- 토큰 탈취 시 피해 최소화
// Refresh Token Rotation 구현 예시
async function rotateTokens(oldRefreshToken) {
const { userId } = verifyRefreshToken(oldRefreshToken);
// 기존 Refresh Token 무효화
await invalidateRefreshToken(oldRefreshToken);
// 새로운 토큰 쌍 생성
const accessToken = generateAccessToken(userId);
const refreshToken = generateRefreshToken(userId);
// 새 Refresh Token 저장
await storeRefreshToken(userId, refreshToken);
return { accessToken, refreshToken };
}
4. 보안 고려사항
4.1 토큰 보안
- Access Token은 메모리에만 저장
- Refresh Token은 HttpOnly 쿠키로 관리
- 모든 통신은 HTTPS 사용
- 적절한 만료 시간 설정
4.2 취약점 대응
XSS (Cross-Site Scripting)
- HttpOnly 쿠키 사용
- Content Security Policy (CSP) 적용
CSRF (Cross-Site Request Forgery)
- SameSite 쿠키 속성 사용
- CSRF 토큰 추가 구현
Token Theft
- Refresh Token Rotation
- 토큰 무효화 메커니즘 구현
5. 구현 모범 사례
5.1 토큰 설계
// Access Token Payload
{
"sub": "1234567890", // Subject (User ID)
"name": "John Doe", // 사용자 이름
"roles": ["user"], // 권한
"iat": 1516239022, // 발급 시각
"exp": 1516239922 // 만료 시각
}
// Refresh Token Payload
{
"sub": "1234567890", // Subject (User ID)
"iat": 1516239022,
"exp": 1517239022,
"jti": "unique-token-id" // 토큰 고유 식별자
}
5.2 에러 처리
async function handleTokenError(error) {
if (error.name === 'TokenExpiredError') {
try {
const newTokens = await refreshTokens();
return retryRequestWithNewToken(newTokens.accessToken);
} catch (refreshError) {
// Refresh Token도 만료된 경우
logout();
redirectToLogin();
}
}
if (error.name === 'JsonWebTokenError') {
// 유효하지 않은 토큰
logout();
redirectToLogin();
}
}
5.3 보안 설정 예시
// JWT 옵션 설정
const jwtOptions = {
algorithm: 'RS256', // 비대칭 암호화 사용
expiresIn: '1h', // Access Token 만료시간
audience: 'https://api.example.com',
issuer: 'https://auth.example.com'
};
// 쿠키 설정
const cookieOptions = {
httpOnly: true,
secure: true,
sameSite: 'strict',
path: '/refresh',
maxAge: 14 * 24 * 60 * 60 * 1000 // 14일
};
결론
JWT 기반의 인증 시스템을 구현할 때는 다음 사항을 고려해야 합니다:
- Access Token과 Refresh Token의 역할 분리
- 적절한 토큰 저장 방식 선택
- 보안 위협에 대한 대비
- 효율적인 토큰 갱신 메커니즘 구현
- 체계적인 에러 처리
이러한 요소들을 적절히 조합하여 구현하면, 안전하고 사용자 친화적인 인증 시스템을 구축할 수 있습니다.