이번 글에서는 JWT 구조와 간단한 알고리즘 설명, 마지막으로 JWT 생성 과정에 대해 설명할 예정입니다. JWT 홈페이지를 참고하였고 추가 설명을 작성하였습니다.
JWT(JSON WEB TOKEN)는 쿠키와 세션 방식을 거치는 과정 속에서 나타난 하나의 인터넷 인증 표준 포맷입니다.
말 그대로 인증에 필요한 정보(JSON 객체)들을 토큰에 담아 전송하는 방식입니다.
JSON 객체란?
"속성-값" 쌍 또는 "키-값 쌍"으로 이루어진 데이터 오브젝트를 전달하기 위해 인간이 읽을 수 있는 텍스트를 사용하는 개방형 표준 포맷
이 정보는 디지털 서명이 되어있기 때문에 안전하게 인증을 시도할 수 있습니다.
(서명에 쓰이는 알고리즘은 HMAC 알고리즘,RSA(공개 / 개인 키 쌍 사용) 등의 알고리즘이 있습니다.)
그러나 JWT의 중점은 암호화가 아닌 "서명된 토큰"입니다.
즉 공개/개인 키를 쌍을 사용해서 토큰에 서명하면, 서명된 토큰은 개인 키를 보유한 서버만이 정상적인 토큰인지 인증할 수 있다는 것입니다.
결국 JWT는 그 안에 포함된 클레임(요구사항, 정보)의 무결성을 보존할 수 있게 하는 것에 중점을 둡니다.
JWT 구조
JWT는 Header, Payload(정보), Signature 세 부분이 각 점(.)으로 분리된 Base64URL 문자열입니다.
ex) xxxxx.yyyyy.zzzzz
Header
첫번째 파트인 헤더에는 사용된 서명 알고리즘 유형과 타입(토큰 유형)으로 구성됩니다.
{
"alg" : "HS256",
"typ" : "JWT",
}
그 후 위 JSON은 Base64Url로 인코딩되어 JWT 구조의 첫번째 부분을 형성합니다.
+) Base64는 암호화/복호화가 가능한 알고리즘입니다. 즉, 인코딩한 암호를 디코딩할 수 있는 것이지요.
<--> 해싱(암.복호화가 안되어 비밀번호를 찾지 못하고 초기화해야하는 알고리즘)
Payload
클레임(정보)을 포함하는 부분입니다. 클레임(요구사항, 정보)은 엔티티(일반적으로 사용자) 및 추가 데이터에 대한 설명을 포함합니다.
세가지 종류의 클레임이 있습니다. registered, public, private 클레임
1. Registered claims
홈페이지에는 다음과 같이 설명합니다.
해당 클레임은 필수는 아니지만 "유용하고 상호 운용 가능"한 클레임을 제공하기 위해 권장되는 미리 정의된 클레임 집합입니다.
예를 들면,
iss : json토큰을 만든 발행자,
exp : 만료시간 (expiration time),
sub : 주제 (subject),
aud : 청중 (audience)
등이 있습니다. (그 외 참고)
토큰의 만료시간을 정해놓았을 때, 해당 만료시간이 지난 토큰이 포함되어있는 요청은 유효하지 않은 토큰으로 인식하여 reject됩니다.
2. Public claims
JWT를 사용하는 사용자가 정의할 수 있는 public 클레임입니다. 공개용 정보를 위하 사용됩니다.
충돌을 방지하기 위해 URI 포맷을 이용해야 합니다.
IANA JSON Web Token Registry에서 정의하거나 충돌 방지 네임스페이스를 포함하는 URI로 정의해야 합니다.
{
"https://thisisprogrammingworld.tistory.com": true
}
3. Private claims
JWT를 사용하는데에 동의한 서버와 클라이언트 사이에 정보를 공유하기 위해 지정한 정보를 저장하는 커스텀 클레임입니다.
(당연히) registered claims나 public claims가 아닌 클레임입니다.
해당 클레임에 유저 아이디나 유저 네임 같은 것이 들어갈 수 있습니다.
페이로드는 base64 인코딩만 진행할 뿐, 암호화가 되어있지 않기 때문에 노출되어도 괜찮으면서 유저를 특정할 수 있는 정보만 담아야 합니다.
{
"sub": "1234567890", // registered claim
"name": "John Doe",
"admin": true, // private claim
"userId" : 1244342
}
그 후 위 JSON은 Base64Url로 인코딩되어 JWT 구조의 두번째 부분을 형성합니다.
또한 아래 경고문이 적혀있습니다.
Do note that for signed tokens this information, though protected against tampering, is readable by anyone. Do not put secret information in the payload or header elements of a JWT unless it is encrypted.
변조로부터 보호되지만 누구나 읽을 수 있기 때문에, 암호화되지 않는다면
JWT의 페이로드나 헤더에 민감 정보를 넣으면 안됩니다.
Signature
서명 파트를 생성하기 위해서 인코딩된 헤더, 인코딩된 페이로드, 나만 알고 있는 개인 키와
(헤더에 명시한) 서명 알고리즘을 사용해서 서명을 합니다.
만약 HMAC SHA256 알고리즘을 사용한다고 예를 들면, 서명은 아래 코드를 통해 형성됩니다.
HMACSHA256(
base64UrlEncode(header) + "." +
base64UrlEncode(payload),
secret // 서버만 알고 있는 비밀 키
)
signature는 서버에 있는 개인키로만 암호화를 풀 수 있기 때문에 다른 클라이언트는 임의로 signature를 복호화할 수 없습니다.
해당 서명을 통해
메시지가 변조되지 않았는지?
(개인 키로 서명된 토큰의 경우) JWT를 보낸 사용자가 누구인지?
등을 검증할 수 있습니다.
JWT 사용 과정
조금 더 깊이 들어가서 로그인하는 상황을 봅시다.
클라이언트가 정보 'moonz, 1234'를 서버에 보내면서 로그인 요청을 합니다.
그리고 인증이 완료되면?
서버는 (원래는 세션을 만들었겠지만) JSON 웹 토큰 (JWT)를 생성하는데..
<과정>
1. 먼저 Header를 만듭니다. 서명 알고리즘 타입을 적습니다. ex) HS256 (HMAC 방식의 SHA256 암호화)
2. Payload를 만듭니다. ex) username : moonz
3. Signature를 만듭니다. ex) base64(헤더) + "." + base64(페이로드) + secretKey (서버만 알고있는 키값) 를 HS256으로 암호화
※ 해당 글에서 서버의 개인 키는 "secretKey" 로 부르겠습니다.
HMAC 알고리즘
위는 HMAC 방식으로 SHA256 즉, 복호화할 수 없는 값을 생성하는 과정 (해쉬화)인데,
HMAC 방식이 무엇일까요?
간단합니다. 시크릿 키를 포함해서 암호화하는 방식을 HMAC 방식이라 합니다.
위 처럼 "secretKey"라는 시크릿 키를 포함해서 암호화하는 방식을 HMAC 방식이라 부르는 것입니다.
그럼 RSA 방식을 쓰게 되면 어떻게 될까요?
RSA 알고리즘
RSA는 공개키 암호 알고리즘입니다. 공개키와 개인키가 한 쌍을 이루며, 공개키로 암호화한 내용은 개인키로만, 개인키로 암호화한 내용은 공개키로만 해독할 수 있습니다.
RSA 암호화를 이용하게되면 아래와 같이 진행됩니다.1) Header와 페이로드 생성 : 알고리즘 타입을 RSA로 표기하고 페이로드에 필요 정보를 입력합니다.
2) Signature 생성 : Header + "." + Payload를 더해준 후 개인키로 암호화하여 서명합니다.
.
.
클라이언트가 서버에게 요청 시에는,
클라이언트로부터 전달된 JWT 중 (base64 디코딩 된) Signature를 서버의 공개키로 해독하여 검증합니다.
사진속 우측 아래 Signature 파트를 보면 (인코딩된 헤더+ "." + 인코딩된 페이로드, 공개키, 개인키)를 RSA 암호화한 상태임을 알 수 있습니다.
4. 각각의 Header, PayLoad, Signature를 base64로 인코딩한 후 점(.)으로 접목합니다.
5. 클라이언트에게 전달하면, 클라이언트는 이 정보를 로컬 스토리지에 저장해둡니다.
6. 그후.. 클라이언트는 개인정보를 요청하는데 이때 JWT를 헤더에 넣어 함께 전송합니다.
7. 서버는 해당 JWT가 유효한지 검증해야하는데, 어떻게 할까요? Signature 정보(HS256으로 암호화된 정보)를 이용합니다.
7-1. 클라이언트로부터 전달된 JWT를 base64 디코딩합니다.
7-2. base64 디코딩된 헤더 + "." + 페이로드에 서버가 갖고있는 개인 키(secretKey)를 더해서 HS256으로 암호화하여 Signature를 생성합니다.
7-3. 암호화한 클라이언트의 Signature 값이 서버가 7-1에서 생성한 Singature 값과 동일하면 유효한 것으로 판단합니다.
8. 그후 클라이언트의 요청에 대해 (Payload의 정보를 이용해서) 로직을 처리하고 데이터를 응답합니다.
그럼 실제 JWT를 보며 얘기해볼게요.
1, 2, 3은 각각 Header, Payload, Signature 입니다.
Signature인 "SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c" 는 결국 아래 과정을 거쳐 생성된 값입니다. (슈도코드)
String encodedHeaderPayload = Base64("eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9") + "." +
Base64("eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ");
lowSignature = HMACSHA256(encodedHeaderPayload, secretKey);
Base64(lowSignature) // 위모습은 base64로 한번 더 인코딩하는 것을 체크하지 않은 모습입니다.
그리고 최종 좌측의 모습은 아래 코드를 통해 만들어집니다.
String jwt = Base64(헤더) + "." + Base64(본문) + "." + Base64(lowSignature);
jwt 생성 과정은 위처럼 코드로 직접 구성해도 되고, Java JWT라는 라이브러리를 사용하여 자동으로 생성되도록 할 수 있습니다.
JWT는 언제 사용되는가?
JWT 공식 홈페이지에서는 아래 2가지를 설명하고 있습니다.
1. Authorization (인가, 권한 부여)
가장 흔한 시나리오입니다. 사용자가 일단 로그인을 하면 서버가 JWT를 생성하여 주고, 그 후 클라이언트의 요청에는 JWT가 포함되고, 이를 통해 라우트, 서비스, 자원에 접근할 수 있도록 허용합니다.
JWT를 사용하면 적은 오버헤드, 그리고 다른 도메인에 쉽게 교차가능하기 때문에 오늘날 단순한 로그인은 일반적으로 JWT를 사용한다고 합니다.
2. Information Exchange (정보 교환)
JWT는 비밀스럽게 사용자간 정보를 전달할 수 있는 방법입니다.
공개/개인 키쌍을 사용하기 때문에 서로를 확신할 수 있고, 추가적으로 헤더와 페이로드를 이용해서 서명이 계산되기 떄문에??? 내용이 변조되지 않았음을 증명할 수 있게됩니다.
JWT 마무리
편리성
JSON 파서는 객체에 직접 매핑되기 때문에 대부분의 프로그래밍 언어에서 공통적이어서, 이는 자동 객체 매핑 기능을 갖고 있지 않는 XML 기반 표준보다 더 편리합니다.
- 또한 JWT를 이용하게되면 클라이언트의 각 요청마다 여러 서버들 각각에 접속해야할 때 jwt 검증만 하면 됩니다.
각 서버들은 개인키만 가지고 있으면 되는 것입니다. 세션을 이용할 때의 세션 저장소가 따로 필요없지요.
목적
위에서 언급했듯이 JWT는 암호화의 목적이 있는게 아닌 서명에 목적이 있습니다. 전달된 데이터, 요청이 유효한지 검증하는 것에 목적이 있죠.
함께 읽기 좋은 글 : https://brunch.co.kr/@jinyoungchoi95/1
참고 :
- https://mangkyu.tistory.com/56
- https://www.youtube.com/watch?v=JY6qEnGRXic&list=PL93mKxaRDidERCyMaobSLkvSPzYtIk0Ah&index=18
- https://jwt.io/introduction
'지식 > Web' 카테고리의 다른 글
웹 스토리지(Web Storage)란? (0) | 2021.11.23 |
---|---|
동기/비동기(Sync/Async), Blocking/Non-Blocking (0) | 2021.09.17 |
서버 사이드 랜더링(SSR)과 클라이언트 사이드 랜더링(CSR) (0) | 2021.08.15 |
[HTML] 자동재생되는 유튜브 영상 넣기(음소거) (0) | 2021.07.16 |