JWT(2. 구조)

Walker·2021년 6월 6일
0

JWT

목록 보기
2/2

지난 JWT편에 이어서 이번 글에서는 JWT의 구조와 구성 요소들을
알아보고 이것들이 어떻게 사용되는지 알아보자!

사실 JWT 구조에 관한 글은 Velopert님의 글 이상의 깊이를 쓸 자신은 없고
나는 내 이해를 바탕으로 최대한 쉽게 그리고 추가적 내용을 설명해보고자 한다.
(시간이 없으신 분은 이 글을 강력 추천! - https://velopert.com/2389)

먼저 JWT의 구조를 간략하게 구분하자면

  1. 해당 JWT의 형식에 대한 정보를 담고 있는 Header
  2. 해당 JWT 사용에 필요한 정보를 담고 있는 Payload
  3. 해당 JWT의 유효성을 증명할 Signature

3가지로 구성되어있는데 하나하나 살펴보자.


첫번째 Header는 해당 JWT의 Signature가
어떤 알고리즘으로 해싱(Hashing)되었는지를 나타내는 alg,
그리고 토큰의 타입을 나타내는 typ(보통 JWT)으로 구성되어있다.
(기본적으로 JWT(Json)이기 때문에 key : value 구조이며
통상적으로 사용되는 key들의 이름은 3자리로 사용한다.)

여기에서 해싱(Hashing)에 대해서 짚고 넘어가자면
해시 함수를 이용하여 기존의 Data(글, 금액, 이름) 등을
해시 코드(1jlh2n3ljn24)로 변환하는 과정이라고 할 수 있다.

출처 : https://brunch.co.kr/@sangjinkang/32

이러한 해싱암호화와는 조금 다른데

암호화한 Data를 대칭키나 비대칭키 등으로 다시 이전의
원 Data로 복원 할 수 있는 암호화와 달리

해싱은 해시함수를 통해 만든 해시 코드만으로는 다시 원 Data로 돌릴 수 없다.
(변조여부만 확인 가능 But 비밀키(개인키)를 사용해 해싱한 경우는 복원 가능)

이러한 JWT 해싱에 사용되는 알고리즘은 크게 2가지 인데
바로 HS256(HMAC SHA256), RSA 방식이다.

찾아본 글(https://hwannny.tistory.com/72)에 따르면
HS256은 대칭키를 사용하는 것으로 추측(?) 되므로
대칭키로 사용할 Secret Key를 안전하게 양쪽에서 관리 할 수 있다면 추천하고
그렇지 않다면 비대칭키 방식인 RSA방식을 사용하라고 주장한다.

대칭키, 비대칭키(공개키, 개인키)에 대한 이해는
얄코님 영상을 보면 이해가 편하다. (설명을 정~말 잘하심)
(https://www.youtube.com/watch?v=H6lpFRpyl14)


두번째 Payload는 실질적으로 JWT 사용에 필요한 정보를 담고 있는
글로 치자면 본문에 가까운 부분이다.
위의 예시에서 보다시피 유저정보, 권한, JWT 정보(만료일, 시작유효일) 등
원하는 정보를 한쌍의 Key/Value로 담으면 되고
이러한 한쌍을 JWT에서는 클레임(Claim)이라고 부른다.

{   // 등록된 클레임
    "iss": "walker.com",
    "exp": "1485270000000",
    
    // 공개 클레임
    "https://velog.io/@walker": true,
    
    // 비공개 클레임
    "userId": "11028373727102",
    "username": "walker"
    "authorities" : [
    	"Admin", "User"
    ]
}

클레임은 크게 3가지로 구분된다.

  1. 등록된(Registered) 클레임
  2. 공개(Public) 클레임
  3. 비공개(Private) 클레임

먼저 "등록된 클레임"의 경우는 실제 서비스에 활용되기 위한 정보라기 보다는
JWT에 대한 정보를 담고 있는 기본 정보에 가까우며 보통 3자리 문자열이 Key가 된다.
아래에 많이 사용하는 "등록된 클레임" 설명 (출처 : https://velopert.com/2389)

다음으로 "공개 클레임"은 아래와 같이 Key 값으로 URI를 사용하는 경우가 많은데
이는 URI의 중복되지 않는 Unique함을 활용하는 것이다.
실제로 어떻게 사용되는지를 찾아보았는데 딱히 예시가 없어서
구글 로그인이나 소셜 로그인을 할 때
어떤 사이트에서 활용하는 것인지에 대한 정보를 나타내는 것이 아닐까 추측해본다.

{
   "https://velog.io/@walker": true
}

마지막으로 "비공개 클레임"은 서버와 클라이언트 간의 약속한
임의의 정보를 담는 부분이다.
실질적으로는 여기에 서비스에 필요한 정보(유저정보, 권한 등)를
담는 것이 가장 적절하게 보이며
임의이기 때문에 Key 글자수 제한은 없다.(중복에 유의!)

여기에서 주의해야 할 점은 Header/Payload 정보 모두
암호화 되어 전송되는 것이 아닌 그저 인코딩되어서 보내진다는 점이다.
고로 민감 정보(Password, Secret Key, 주민등록번호 등)를
JWT에 싣는 것은 적당하지 않은 것 같다.
(인코딩된 JWT정보를 얻는다면, 그냥 디코딩하면 다시 원 정보를 알 수 있다!)

JWT에서 보통 사용되는 인코딩 방식은 BASE 64 방식으로
원문에 비해서 30% 정도 길이가 늘어나고, 인코딩 디코딩 과정이 필요하지만
ASCII 방식에 비해 안정적으로 Data를 주고 받을 수 있어 사용된다고 한다.
(출처 : https://blue-boy.tistory.com/227?category=846101)


세번째 Signature는 위에서 언급한 것처럼
사실상 정보가 공개된 것과 마찬가지(디코딩만 하면 되므로) 이므로 중요해진다.
정보를 알더라도 JWT 자체가 유효성하지 않다면 인증을 받아
서비스에 접근 할 수 없기 때문에 이를 판단하는 기준이 되어주는 것이다.

Signature는 다음과 같은 과정으로 만들어진다.

  1. Header와 PayloadBASE 64 방식으로 인코딩한 후 합친다.
    (인코딩한 Header + "." + 인코딩한 Payload)
  2. 둘을 합친 값을 서버의 Secret Key를 활용하여 해싱한다.
  3. 해싱한 값(HEX 형태 - BASE 16)을 BASE 64 형태로 변환한다.

그러면 실제 만들어진 Signature가 어떻게 JWT를 검증하는지 생각해보자.
(참고 : https://tansfil.tistory.com/58)

만약 해커 A사용자 B의 JWT를 탈취하여 A의 계정으로 접속하고자 한다고 하자.
Payload 정보는 디코딩하면 알 수 있기 때문에 해커 A는 이를 파악하고
그중 계정 정보를 사용자 B에서 자신의 계정 A로만 바꿔서
Payload를 바꾸는 것은 가능
하다.

하지만 Signature 값은 사용자 B의 계정 정보를 가지고 만들었기 때문에
JWT를 Secret Key를 통해 원문화 하는 과정에서 사용자 B의 정보가 아님이 드러나
변조된 JWT의 인증은 거부된다.


사실 이러한 일련의 과정들은
jjwt와 같은 라이브러리를 통해 설정 값을 통해 처리하는 경우가 많으므로
내부 동작까지 이해하는 것은 필수는 아니지만
문제가 생긴다면 원인 파악에 좀 더 수월 할 것이라는 생각이 든다.

그럼 다음 편에서는 방금 말한 jjwt와 같은 라이브러리로 실제 구현을 해보자!

profile
I walk slowly, but I never walk backward. -Abraham Lincoln-

0개의 댓글