JWT를 이해하기 전 인증 방식에 대한 이해가 필요
인증? 인가?
인증(Authentication): 사용자의 신원을 검증하는 프로세스(ex. 로그인)
인가(Authorization): 인증 이후 프로세스, 인증된 유저가 어떠한 자원에 접근할 수 있는지 확인하는 절차 (ex. 관리자 권한이 필요한 관리자 페이지)
웹 인증
- 서버-클라이언트 구조, HTTP 프로토콜을 이용해 통신한다.
- 비연결성(Connectionless): 서버와 클라이언트가 연결되어 있지 않음(서버의 비용이 증가하기 때문), 리소스 절약을 위해 서버는 하나의 요청-하나의 응답을 한 후 연결을 끊는다.
- 무상태(Stateless): 서버가 클라이언트의 이전 상태를 저장하지 않는다.(서버의 비용/부담을 증가시키기 때문)
- 위와 같은 특성 때문에 사용자가 로그인으로 인증 받아도 이후 요청에 대해서 인증된 상태를 유지할 수 없다. 서버에 사용자가 로그인을 했다는 정보를 저장하지 않아 매 요청마다 반복적으로 접근 인가를 받아야 하는 것.
=> 그럼 어떻게 로그인을 한 후 재 로그인을 하지 않아도 계속 로그인 상태를 유지하는 걸까?? 인증 방식인 쿠키/세션/JWT를 통해!
쿠키(Cookie)
- 쿠키에 사용자의 인증 정보를 담아 인증하는 것
1. 클라이언트가 서버에 첫 인증 요청을 보내면(로그인), 서버에서 쿠키에 사용자 인증 정보를 담아 응답한다.
2. 서버에서 받은 쿠키를 클라이언트에 저장하고, 이 이후 인증/인가 요청 시 매번 서버로 요청한다.- 장점: 사용자의 인증 정보를 클라이언트에서 관리하기 때문에 서버 부하가 적다. 인증 상태를 서버가 관리하지 않고 쿠키의 요청을 받을 때 처리하기 때문에 Stateless 하다.
- 단점: 클라이언트가 쿠키에 담긴 인증 정보를 쉽게 위조할 수 있다(CSRF/XSS 공격). 쿠키의 크기가 커진다면 네트워크 부하가 심해지기 때문에 쿠키의 데이터 크기가 제한적이다.
세션(Session)
- 세션으로 사용자의 인증 정보를 관리하는 방식
1. 클라이언트가 서버에 첫 인증 요청을 보내면(로그인), 서버에 인증 정보를 저장하고 클라이언트별 유일한 SessionID를 부여한다.
2. 클라이언트는 서버에서 받은 SessionID로 서버에게 인증/인가를 요청해, SessionID에 해당하는 인증 정보로 처리- 장점: 사용자의 로그인 정보를 주고 받지 않기 때문에 상대적으로 안전하다. 사용자별 고유한 SessionID를 발급받기 때문에 요청이 들어올 때마다 회원DB를 찾지 않아도 됨
- 단점: 사용자 식별을 위한 SessionID를 생성하고 서버에 저장해야 함. 서버 Session 저장소를 사용하기 때문에 요청이 많아질수록 서버 부하가 심해짐.
유저를 인증하고 '식별'하기 위한 Token 기반 인증으로, JSON 객체에 인증에 필요한 정보들을 담고 비밀키로 서명한다. 이 Token에 사용자의 권한 정보나 서비스를 사용하기 위한 정보를 포함한다.
Session 방식의 경우 쿠키 등을 통해 사용자를 식별하고 Sever에 session을 저장하지만, JWT는 Token을 클라이언트에 저장하고 요청시 HTTP 헤더에 Token을 첨부하는 것으로 데이터 요청/응답이 가능하다.
HEADER.PAYLOAD.SIGNATURE
1. HEADER:
해시 알고리즘과 토큰의 타입을 정의한다.
- Kid:서명 시 사용하는 키(Public/Private Key)를 식별하는 값
- typ: Token 유형
- alg: 서명 암호화 알고리즘 (ex. HS256, RS256... etc.)
2. PAYLOAD
Token에서 사용할 정보의 조각들인 Claim(Key/Value)이 담겨있음, 즉 전달하는 데이터를 포함.
저장되는 정보에 따라 등록 클레임(Registered Claims), 공개 클레임(Public Claims), 비공개 클레임(Private Cliams)으로 나누어진다.
- iss(issuer): 토큰 발급자
- sub(subject): 토큰 제목
- iat(issued at): 토큰 발급 시간
- exp(expiration): 토큰 만료 시간
- roles: 권한
이 외에도 커스텀 Claim을 만들어도 된다.
(주의할 점: Payload는 Base64 인코딩 되었기 때문에 누구나 디코딩하여 열람이 가능함. 그러니 password같은 민감한 요소들은 담으면 안 됨)
3. SIGNATURE
'Header+Payload'를 서버가 갖고 있는 유일한 key(Secret Key)와 헤더에서 정의한 알고리즘(alg)으로 암호화한다.
Header와 Payload는 단순히 인코딩된 값으로 제 3자가 복호화/조작할 수 있다. Signature(서명)은 서버 측에서 관리하는 Secret Key가 유출되지 않는 이상 복호화 할 수 없어 보안상 안전하고 토큰의 위변조 여부를 확인하는데 사용된다.
=> 이렇게 이루어진 JWT Token을 클라이언트가 서버로 요청함과 동시에 함께 전달한다. 서버에서는 이 Token을 가지고 있는 Secret Key를 가지고 signature를 복화하 한 다음 일치한 지 식별한다.
<<로그인 전>>
- 사용자가 로그인을 하며 서버에 요청을 보냄
- 서버는 secret key를 사용해 json객체를 암호화 한 JWT Token을 발급
- JWT를 header에 담아 클라이언트에게 전송
<<로그인 후>>
- 발급받은 JWT Token을 클라이언트 로컬에 저장
- API 호출을 할 때마다 header에 JWT Token을 함께 전송
- 서버는 해당 Token을 복호화하여 식별한 후, 인증이 되면 응답을 보냄
장점
토큰 자체가 인증된 정보이기 때문에 세션 저장소처럼 별도의 인증 저장소가 필요하지 않다.
세션처럼 클라이언트의 상태를 서버에 저장하지 않아도 된다.
signature를 secret key로 암호화하여 데이터 보완성이 높아짐.단점
쿠키/세션 방식과 다르게 base64 encoding을 통한 정보 전달이므로 데이터 전달량이 많다. 또한 Header/Payload는 암호화가 되어있지 않아 민감 정보(ex. password)를 저장할 수 없다.
토큰이 탈취당하면 만료될 때까지 대처가 불가능하다. (JWT를 발급해 전달한 후의 상황은 클라이언트가 관리하기 때문에 탈취된 상황을 서버가 알게 되어도 대처할 방법이 없다. 이는 토큰 만료 시간을 짧게 두는 것으로 최소한의 보안성을 보장할 순 있다. 또 이 짧은 유효 시간을 보완하는 방법으로 Access Token과 Refresh Token을 나누어 관리하는 방법이 있다.)