
JWT
1. JWT란?
존맛탱
- JSON Web Token의 약자로, JS 객체(
key:value)형태의 자료구조를 가지고 웹 토큰으로 사용할 수 있다는 의미이다.
- JWT는 웹 표준으로, 두 개체에서 JSON 객체를 사용하여 가볍고 자가수용적(Self-Contained) 인 방식으로 정보를 주고 받는다.
자가수용적? (Self-contained)
필요한 정보를 자기스스로 모두 가지고 있다는 의미이다.
- 토큰에 대한 정보(header),
- 토큰으로 전달하는 내용(payload),
- 토큰이 인증되었다는 서명(signature)
에 대한 모든정보를 가지고 있다.
2. 장단점
JWT는 토큰이기에, 앞서 설명했던 일반적인 토큰의 장단점과 동일하다.
정리할 겸 다시 한번 언급해보겠다.
장점
- HTTP 프로토콜의 stateless 한 특성을 그대로 유지하면서 로그인 상태유지를 가능하게 하는 기술이다.
- User Authentication에서 session DB를 활용할 필요가 없다.
- DB를 사용하지 않으니, 특정 DB/특정 서버에 의존하지 않아도 인증을 할 수 있다.
- 서버의 확장성이 높고, 대량의 트래픽이 발생해도 session에 비해 대처하기 수월하다.
단점
- stateful한 방식인 세션방식보다 비교적으로 많은 양의 데이터가 반복적으로 전송되면서, 즉 토큰을 자주 주고받으면서 인증을 계속 거치기 때문에 네트워크 성능저하가 올 수 있다.
- 암호화가 되지 않았기 때문에 데이터 노출로 인한 보안적인 문제가 존재한다.
→ 이 단점을 보완하기 위해서 데이터 압축 및 서명을 하는 JWT를 사용하게 된다.
3. 작동방식
로그인
- 유저가 로그인하면, DB에서 session ID를 생성하여 저장하는 방식이 아니다.
- 대신 서버는 유저의 ID를 가져와 서명(signature)을 해주고, 서명된 정보를 hashing된 문자열 형태로 내보낸다.
→ DB를 건드리는 것 대신에, 정보를 서명하고 전달하는 것이 끝이다.
요청 (Request)
- 클라이언트가 서명된 토큰을 서버에 보낸다.
- 서버는 토큰을 받으면 해당 서명이 조작되지는 않았는지, 유효한지를 확인한다.
- 토큰이 유효하다면, 서버는 클라이언트를 자신의 유저로 인정하고, 이후의 작업을 수행한다.
4. 구성요소
.을 구분자로 하여 3가지의 문자열로 구성된다.
aaaa.bbbbbb.ccc 형태의 구조로
- 헤더(Header)
- 내용(Payload)
- 서명(Signature)로 구성된다.
- 해당문자열, 즉 JWT는 길이제한이 없어서 세션ID보다 훨씬 길게 둘 수 있다.
jwt.io
해당 페이지를 들어가보면 JWT를 생성, 인코딩/디코딩/서명을 테스트 해볼 수 있다.
보이는 바와 같이 이것이 JWT의 형태이다.
두 가지 key값을 갖는 객체로 구성되어 있음.
{
"typ" : "JWT",
"alg" : "HS256"
}
typ : type. 토큰의 타입을 지정한다. JWT이므로 value값은 "JWT"이다.
alg : algorithm. 해싱 알고리즘을 지정한다.
- HMAC, SHA256, RSA 등이 있으며, 토큰을 검증하는 signature에 사용된다.
2) 정보(Payload)
페이로드에 들어가는 정보 하나를 클레임(claim)이라고 부르며, 이는 key: value 한 쌍의 형태이다.
토큰에 여러 개의 클레임을 넣을 수 있으나, 너무 많아질 경우 토큰의 길이가 많이 길어질 수 있다.
클레임에는 3가지의 종류가 존재한다.
- Registered Claim (등록 클레임)
서비스에 필요한 정보들이 아닌, 토큰에 대한 정보들을 담기 위해 이미 이름이 정해진 클레임을 말한다.
등록된 클레임을 사용할지 말지의 여부는 선택적으로 고를 수 있다.
IANA에서 등록된 클레임에 대한 정보를 확인할 수 있다. 아래는 자주 사용되는 클레임들이다.
| 이름 | 설명 |
|---|
| iss | (issuer) 토큰 발급자 |
| sub | (subject) 토큰 제목 |
| aud | (audience) 토큰 대상자 |
| exp | (expiration) 토큰의 만료시간. Date 형식으로, 언제나 현재 시간보다 이후로 설정되어 있어야 한다. |
| nbf | (Not Before) 토큰의 활성화 날짜와 비슷한 개념. Date 형식으로, 이 날짜가 지나기 전까지는 토큰이 처리되지 않는다. |
| iat | (issued at) 토큰의 발급시간. Date형식으로, 이 값을 사용하여 토큰의 발급으로부터 얼마나 되었는지 알 수 있음. |
| jti | JWT의 고유 식별자. 주로 중복적인 처리를 방지하기 위하여 사용됨. 일회용 토큰에 사용하면 유용함. |
- Public Claim (공개 클레임)
사용자가 마음대로 정의하여 사용하는 클레임으로, Registered Claim과 충돌이 발생하면 안되기 때문에 주로 URI 형식으로 이름을 짓는다고 한다.{
"https://www.jwt_practice.com/auth/userId" : 1234,
}
- Private Claim (비공개 클레임)
양 측(클라이언트↔서버)간의 합의 하에 사용되는 클레임 이름.
공개 클레임과는 달리 이름이 중복이 되어 충돌이 될 수 있으니 사용할 때 유의해야 한다.
3) 서명(Signature)
헤더의 인코딩, 정보의 인코딩 값을 합친 후 주어진 비밀키로 해쉬를 하여 생성한다.
이렇게 만든 해쉬를 base64 형태로 나타내게 한다.
즉, header와 payload가 변조 되었는지를 확인하는 역할을 한다.
Access Token과 Refresh Token
출처
1. Access Token
서버 API를 직접 요청할 때 사용하는 토큰
2. Refresh Token
Access Token이 만료 되었을 때 이를 재발급 할 목적으로 사용
3. 분리하는 이유?
클라이언트↔서버 API통신 중 토큰이 탈취 당할 수 있을 수 있음.
- API에 직접 접근하는 Access Token은 짧은 만료주기로 하고 (ex. 5분)
- 위 Access Token을 재발급하는 Refresh Token의 만료주기는 길게 함 (ex. 1달)
이렇게 진행 할 경우 Access Token이 탈취 당해도 만료 주기가 짧으므로 피해를 줄일 수 있음.
Refresh Token 조차 탈취 당한다면?
- DB에 각 사용자에게 1:1 mapping되는 Access Token, Refresh Token 쌍을 저장
- 정상적인 사용자는 기존의 Access Token으로 접근, 서버 측에서는 DB에 저장된 Access Token과 비교하여 검증
- 공격자가 Refresh Token으로 새 Access Token을 생성하고 서버측에 전송.
서버는 DB에 저장된 Access Token과 공격자의 Access Token이 다른 것을 확인.
- 만약 DB내 Access Token이 만료되지 않았는데 새 Access Token이 전달 되었다면 Refresh Token이 탈취 당했다고 가정하고 두 토큰(Access, Refresh)을 DB에서 삭제.
(새로운 Access Token이 필요없는 상황인데 새로운 토큰이 전달 되었으므로)
- 정상적인 사용자의 토큰 또한 만료되었으니 다시 로그인 해야함.
하지만 공격자의 토큰도 만료되었으니 공격자는 정상적인 사용자의 리소스에 접근 불가능.
- 핵심적인 것은 토큰은 그냥 문자열 뿐이기 때문에 서버나 클라이언트에서 마음대로 만료시키는 것이 불가능함.
- 그렇기 때문에 NoSQL형태의 DB를 활용하여 유효기간이 지나기 전까지 토큰을 보관하여 비교하는 것.
그럼 둘 다 탈취 당하면?
출처