JWT(JSON Web Token)
- 웹 애플리케이션의 인증과 권한 부여에 널리 사용되는 방식
JWT 의 기본 구조
Header
{
"alg": "HS256", // 서명 암호화 알고리즘
"typ": "JWT" // 토큰의 타입
}
- 토큰의 타입과 사용된 암호와 알고리즘을 지정
Payload
- claim 이라 불리는 토큰에 담을 정보가 들어감
Registered Claim | Public Claims | Private Claims |
미리 정의된 클레임(권장되는 클레임 집합) iss(발급자)/sub(제목)/exp(만료시간) 등 |
사용자 정의 클레임으로, 공개적으로 정의 | 당사자들 간에 정보를 공유하기 위해 생성된 맞춤 클레임 |
Signature
- 헤더와 페이로드를 검증하기 위해 사용
→ Header, Payload, Signature 각 부분은 Base64URL로 인코딩되어 URL에서 안전하게 사용할 수 있는 문자열로 변환된다.
(.으로 구분)
→ 이러한 JWT의 구조가 토큰 자체에 필요한 모든 정보를 포함하므로 별도의 저장소 없이 손쉽게 인증 정보를 전달하고 검증할 수 있다.
쿠키, 세션, 토큰
쿠키 (Cookie)
- 클라이언트 브라우저에 저장되는 작은 텍스트 파일
- key-value 형태로 데이터 저장
- 서버가 클라이언트에 데이터를 저장하기 위한 수단
세션 (Session)
- 서버 측에서 관리하는 사용자 인증 정보
- 서버의 메모리나 DB 에 저장
- 클라이언트는 세션 ID 만 보유
- 세션 ID 는 보통 쿠키를 통해 클라이언트에 전달된다.
토큰 (Token)
- 클라이언트가 서버에 인증정보를 증명하기 위해 전송하는 암호화된 문자열
- JWT 는 자체적으로 필요한 정보를 포함하고 있어
- 서버에서 별도 저장소 없이 검증이 가능하다.(stateless 한 인증 방식 제공)
Access Token & Refresh Token
- Access token과 Refresh token은 보안과 사용자 경험의 균형을 위해 존재한다.
Access Token
- 사용자 인증 정보를 담고 있는 JWT
- 실제 리소스에 접근할 때 사용되는 토큰
- 비교적 짧은 유효 기간(보통 15분 ~ 2시간)을 가진다.
- 탈취 피해를 최소화 하기 위해 짧은 유효기간이 설정된다.
Refresh Token
- Access Token을 새로 발급받기 위한 토큰
- 더 긴 유효 기간(보통 몇 주 ~ 1달)을 가진다.
- Access token 이 만료되었을 때 새로운 Access token을 발급받는 용도
- 사용자가 매번 로그인하지 않아도 되게 해준다.
토큰 갱신 프로세스
토큰 저장 위치와 안전성
Access Token
- 메모리나 짧은 수명의 HttpOnly 쿠키에 저장하는 것이 안전하다.
- 브라우저 메모리에 저장하면 XSS 공격에 취약할 수 있지만, 페이지 새로고침 시 손실된다
* XSS 공격 : XSS(Cross-Site Scripting) 공격자가 악성 스크립트를 웹 페이지에 삽입하여 사용자의 브라우저에서 실행되게 하는 공격
Refresh Token
- HttpOnly, Secure 플래그가 설정된 쿠키에 저장하는 것이 가장 안전하다.
- 서버 측 데이터베이스에도 저장하여 필요 시 폐기할 수 있게 해야한다.
토큰 관리 방법
1. localStorage/sessionStorage
// 저장
localStorage.setItem('accessToken', token);
// 조회
const token = localStorage.getItem('accessToken');
- 장점 : 구현이 간단하고 영구 저장이 가능하다.
- 단점 : XSS 공격에 취약해 보안상 위험이 있다.
- JavaScript 로 접근이 가능하다.
- Access Token 저장은 권장되지 않는다.
2. 메모리 단에서 관리 (Context API, Zustand, Tanstack Query)
// Context API
const AuthContext = createContext();
const AuthProvider = ({ children }) => {
const [accessToken, setAccessToken] = useState(null);
return (
<AuthContext.Provider value={{ accessToken, setAccessToken }}>
{children}
</AuthContext.Provider>
);
};
// Zustand
const useAuthStore = create((set) => ({
accessToken: null,
setAccessToken: (token) => set({ accessToken: token }),
}));
// TanStack Query
const { data: accessToken } = useQuery(['auth'], getAccessToken);
- 장점 : XSS 공격으로 부터 상대적으로 안전하고, 컴포넌트 간 공유가 용이하다.
- 단점 : 새로고침 시 상태가 초기화되어 데이터가 손실된다.
- Access Token 저장에 적합하다.
3. 쿠키 (HttpOnly, Secure)
// 서버 측 설정
res.cookie('refreshToken', token, {
httpOnly: true,
secure: true,
sameSite: 'strict'
});
- HttpOnly : JavaScript 접근 차단이 가능하고 XSS 공격으로부터 보호한다.
- Secure : HTTPS 프로토콜에서만 쿠키를 전송하고 암호하된 통신만 허용한다.
- Refresh Token 저장에 적합하다.
HttpOnly 와 Secure 옵션
옵션들을 사용하면 쿠키의 보안을 크게 향상시킬 수 있다.
HttpOnly는 클라이언트 측 스크립트의 쿠키 접근을 차단하고, Secure는 암호화된 연결을 통해서만 쿠키를 전송하도록 보장한다.
HttpOnly
- JavaScript 를 통한 쿠키 접근을 방지한다.
- XSS 공격으로부터 쿠키를 보호한다.
Secure
- HTTPS 연결을 통해서만 쿠키가 전송되도록 한다.
- 중간자 공격으로부터 보호한다.
권장되는 토큰 관리 전략
1. Access Token: 메모리 관리 (Context API, Zustand, TanStack Query 중 선택)
- Access Token은 빈번히 사용되므로 메모리에서 빠른 접근이 가능하도록
2. Refresh Token: httpOnly 쿠키로 관리
- Refresh Token은 보안이 중요하므로 httpOnly 쿠키로 안전하게 보관
3. 프로젝트 특성에 따라 관리
- SPA: Context API나 Zustand 사용 권장
- SSR: 쿠키 기반 관리 권장
- 복잡한 상태 관리 필요: TanStack Query 고려
'Web' 카테고리의 다른 글
프론트엔드 에러 모니터링 (0) | 2025.01.18 |
---|---|
Unit Test(with Jest) (0) | 2025.01.18 |