프론트엔드 개발자가 알아야 할 백엔드 JWT 인증 및 권한 관리
JWT (JSON Web Token)는 웹 애플리케이션에서 인증과 권한 부여를 위해 자주 사용되는 토큰 기반의 인증 방식이다.
팀 프로젝트에서 JWT를 이용해 인증 및 권한 관리를 구현하면서 백엔드 팀과 협업했던 경험을 돌아보려고 한다.
JWT는 클라이언트와 서버 간의 인증 정보를 안전하게 전달하기 위한 중요한 도구로, 백엔드와의 원활한 협업을 위해 반드시 이해해야 할 개념이다.
이번 포스트에선 협업 과정에서 알게 된 중요한 개념과 프론트엔드 개발자가 JWT를 사용할 때 유념해야 할 핵심 사항들을 정리해 보려고 한다.
1. JWT의 기본 개념
JWT는 세 부분으로 구성된 문자열이다: 헤더(header), 페이로드(payload), 서명(signature).
헤더(Header)
: 토큰의 타입(JWT)과 서명 알고리즘(예: HMAC SHA256)을 명시하는 부분이다.
백엔드에서 JWT를 발급할 때, 이 헤더 정보를 사용하여 어떤 서명 알고리즘이 사용되었는지 확인할 수 있다.
페이로드(Payload)
: 사용자 관련 데이터를 포함하며, 이는 일반적으로 클레임(claim)이라고 부른다.
이 데이터는 토큰을 사용하는 시스템에서 사용자 식별을 위해 사용된다.
중요한 것은 이 부분이 암호화되지 않고 인코딩된다는 점이다.
누구나 내용을 볼 수 있기 때문에 민감한 정보는 포함하지 않는 것이 좋다.
서명(Signature)
: 토큰의 무결성을 보장하기 위해 헤더와 페이로드를 조합해 비밀 키로 생성된 서명이다.
백엔드에서는 이를 통해 클라이언트가 보낸 토큰의 유효성을 검증한다.
2. JWT의 동작 방식
프로젝트에서 JWT를 이용한 인증 및 권한 관리는 다음과 같은 흐름으로 진행되었다:
- 사용자 로그인 : 사용자가 로그인하면 서버가 사용자 인증을 완료한 후 JWT를 발급한다.
- 토큰 저장 : 프론트엔드는 이 토큰을 브라우저의 로컬스토리지, 세션스토리지 또는 쿠키에 저장한다.
- API 요청 시 토큰 포함 : 프론트엔드는 이후 API 요청을 보낼 때, 일반적으로 Authorization 헤더에 Bearer 방식으로 JWT를 포함해 서버로 전송한다.
- 서버 검증 : 서버는 JWT의 서명을 검증하고, 페이로드에 포함된 정보로 사용자를 식별한다.
로컬스토리지 vs 쿠키: 초기에는 JWT를 로컬스토리지에 저장하는 방식을 고려했으나, XSS(Cross-Site Scripting) 공격에 취약하여 악성 스크립트가 토큰을 탈취할 수 있다는 보안 위험이 있었다. 이에 따라 JWT를 쿠키에 저장하는 방식으로 변경했으며, 쿠키에 저장할 때는 HttpOnly 플래그를 설정해 자바스크립트에서 쿠키에 접근하지 못하게 하고, Secure 플래그를 설정해 HTTPS 연결에서만 쿠키가 전송되도록 하여 보안을 강화했다.
3. 프론트엔드에서의 JWT 사용 시 주의사항
- 토큰 저장 위치
- 로컬스토리지/세션스토리지: 이 방법은 간편하지만, XSS (Cross-Site Scripting) 공격에 취약하다. 악성 스크립트가 브라우저에서 실행되면 토큰을 탈취할 수 있다.
- 쿠키: 쿠키에 저장할 경우, HttpOnly 플래그를 사용하여 자바스크립트로 접근하지 못하게 하고, Secure 플래그로 HTTPS 연결에서만 쿠키가 전송되도록 설정할 수 있다. 하지만 쿠키 사용은 CSRF (Cross-Site Request Forgery) 공격에 취약할 수 있으므로 CSRF 방어책도 필요하다..
- 토큰의 만료
JWT에는 exp 클레임을 통해 만료 시간이 지정된다. 프론트엔드는 토큰 만료를 관리해야 하며, 만료된 경우 리프레시 토큰을 이용해 새로운 액세스 토큰을 발급받거나 사용자를 재로그인시켜야 한다.
- 민감한 정보 노출 방지
JWT의 페이로드는 누구나 디코딩할 수 있으므로, 민감한 정보를 토큰에 포함하면 안 된다. 사용자 ID나 권한 수준은 포함할 수 있지만, 비밀번호나 금융 정보는 절대 포함하지 않는다. - JWT 토큰 유효성 검증
프론트엔드는 서버에서 JWT가 유효한지 확인하기 위해 서버에 요청을 보내야 한다. 서버에서 토큰이 만료되었거나 위변조되었는지 확인하는 과정이 필요하다.
4. JWT와 보안 관련 고려 사항
- HTTPS 사용
JWT를 전송할 때, HTTPS를 통해 암호화된 연결을 사용하는 것이 매우 중요하다. HTTP로 토큰을 전송하면 중간에 탈취될 위험이 크다. - 토큰 탈취 방지
- XSS 방어: 브라우저에서 악성 스크립트가 실행되지 않도록 철저하게 방어해야 한다. Content Security Policy(CSP)를 설정하고, 입력 검증을 강화하며, 라이브러리의 보안 업데이트를 자주 확인해야 한다.
- CSRF 방어: JWT가 쿠키에 저장된 경우, CSRF 공격을 방어하기 위한 전략이 필요하다. 예를 들어, SameSite 속성을 쿠키에 설정하여 CSRF 공격을 방지할 수 있다.
5. 리프레시 토큰 (Refresh Token)
JWT는 만료 시간이 있기 때문에, 토큰이 만료되면 사용자는 다시 로그인을 해야 한다. 이를 보완하기 위해 리프레시 토큰을 사용할 수 있다. 리프레시 토큰은 일반적으로 더 긴 만료 시간을 가지며, 액세스 토큰이 만료되었을 때 새로운 액세스 토큰을 발급받는 데 사용된다. 리프레시 토큰은 보안이 더 중요한 데이터이므로 쿠키에 저장하거나, 안전한 방식으로 서버에 전달되어야 한다.
6. JWT의 장점과 단점
- 장점
- 확장성: 서버는 상태를 유지할 필요가 없기 때문에 분산 시스템에서 유리하다. 세션을 서버에 저장하지 않고, 토큰만으로 사용자를 식별할 수 있다.
- 편리한 인증 정보 전달: JWT를 통해 여러 서비스 간 인증 정보를 쉽게 전달할 수 있다
- 단점
- 토큰 크기: JWT는 페이로드에 포함된 데이터로 인해 크기가 커질 수 있으며, 이는 네트워크 트래픽에 부담을 줄 수 있다.
- 무효화 문제: 토큰이 발급된 후에는 만료 전까지는 서버에서 쉽게 무효화할 수 없다. 이를 해결하기 위해서는 별도의 블랙리스트를 관리하거나, 짧은 만료 시간을 설정하는 방법을 사용해야 한다.
결론
JWT는 클라이언트와 서버 간의 인증 및 권한 관리를 간편하게 할 수 있는 유용한 도구이다.
하지만 보안 취약점과 관련된 문제들을 잘 이해하고 적절한 조치를 취하는 것이 중요하다.
프론트엔드 개발자는 JWT의 구조와 동작 방식을 이해하고, 안전하게 사용하기 위한 방어책을 반드시 고려해야 한다.