JWT 에 대하여

김법우·2022년 12월 11일
0

security

목록 보기
3/3
post-thumbnail

들어가며

원래는 이번 포스팅에서 Spring 서버를 Client 서버로 두고, Nest.js 서버를 Authorization 서버로 사용해 OAuth 2.0 중 Authorization Code Grant 를 직접 구현해보고자 했으나, Access Token 을 JWT 를 통해 구현할 예정이었기에 먼저 자세히 알아보고 넘어가려한다! 인증/인가, OAuth 프로토콜에 대해서는 이전 포스팅의 내용을 토대로 사용한다.

JWT 에 대하여

정의

JWT(Json Web Token)은 선택적 서명 및 비선택적 암호화를 통해 데이터를 만들기 위한 인터넷 표준이다. JWT 는 페이로드에 몇몇 클레임, 표명을 처리하는 JSON 을 보관하며 비공개 시크릿키 혹은 비공개/공개 시크릿 키를 통해 서명된다.
(출처 : 위키 백과 )

JWT 를 엑세스 토큰의 용도(인가)로만 사용해보았는데, 진짜 정체는 “선택적 서명 및 비선택적 암호화를 통해 데이터를 만들기 위한 표준”이다. 정보를 JSON 객체로 안전하게 송/수신하기 위한 표준인 것이다.

클레임은 특정 리소스의 프로퍼티, 속성을 뜻한다. 즉 위의 말을 요약하면,
“JWT 는 특정 리소스의 프로퍼티를 JSON 객체로 안전하게 송/수신하기 위한 self-contained 한 방법을 정의한 표준” 이다.

JWT 는 안전하게 클레임을 저장하고, 클레임의 무결성을 보장하기 위해 비공개 시크릿키를 통해 암호화하거나 비공개/공개 대칭키를 통해 서명하고, 복호화 하는 방식을 사용한다.

용어 정리

Self-contained

자체적으로 필요한 정보를 포함하고 있다는 의미이다. 기존의 Authorization Code Grant 를 구현한다고 생각해보자. 사용자가 인증을 성공한 결과로 토큰을 발급 받았다.

Authorization 서버는 해당 토큰의 문자열값에 해당하는 권한 정보를 어딘가에 저장하고 있을 것이고, 사용자가 Client 서버로 요청을 하면 해당 요청에 포함된 토큰값으로 권한 정보를 가져와 리소스에 엑세스 가능한지를 판단 할 것이다.

[문자열을 사용한 토큰]

즉, self-contained 하지 않은 문자열 토큰을 사용하게 되면 인가 과정에서 권한 정보를 어딘가에서 가져와야한다. 하지만 self-contained 한 JWT 를 사용해 위의 플로우를 다시 구현하면,

[JWT 를 사용]

사용자가 인증을 성공하면, 사용자의 권한 정보나 식별가능한 키를 클레임으로 토큰에 담아 사용자에게 반환할 것이다. 이후에 사용자가 Client 서버로 요청을 하면 Authrozation 서버는 비대칭 키를 통해 토큰을 복호화하고 토큰의 페이로드에 포함된 정보를 바탕으로 요청을 처리할 것이다.

인증/인가 과정은 달라지지 않았지만, Authorization 서버에서 인가를 위해 수행하는 과정이 상당히 간결해졌다.

JWS (Json Web Signature)

JSON 데이터 구조를 사용하는 서명 표준으로 RFC7515 이다.

JWE (Json Web Encryption)

SON Web Encryption (JWE)는 JSON 데이터 구조를 사용하는 암호화 방법으로 RFC7516 이다.

JWT 의 원리

JWT 는 기본적으로 온점으로 연결된 3가지 섹션인 Header, Payload, Signature 로 구성된다. 서명, 암호화된 토큰 문자열의 결과는 URL-Safe 하게 구성된다. 각 파트가 어떤 역할을 하고, 어떤 데이터로 만들어지는지 알아보자!

Header 영역

{
  "algorithm": "SHA256",
  "token_type": "JWT"
}

헤더는 토큰의 타입과 암호화 알고리즘을 나타내는 JSON 객체를 URL 로 인코딩(Base64)하여 만들어진다. 즉 해당 토큰이 어떤 토큰이고, 어떻게 서명했는지를 나타내는 데이터를 구성하는 것이 Header 인 것이다.

Payload 영역

앞서말한 클레임을 포함하는 영역이다. 클레임의 용도에 따라 3가지로 구분 될 수 있다.

{
  "sub": "1234567890",
  "exp": 100000,
	...
}
  • Reserved Claim 정보 교환에 유용하도록 미리 정의된 클레임. sub, exp, iat … 등등 실제 교환하고자 하는 정보가 아닌, 토큰 자체에 대한 정보들을 포함한다.
  • Public Claim 공개 클레임은 사용자 정의 클레임으로, 공개용 정보를 위해 사용된다. 충돌을 방지하려면 IANA JSON Web Token Registry에 정의하거나 충돌 방지 네임스페이스를 포함하는 URI로 정의해야만 한다.
  • Private Claim 비공개 클레임은 정보 교환 주체간에 임의로 지정한 정보를 저장하기 위해 사용된다. 이름이 중복되기 때문에 충돌 가능성이 있다.

Signature 영역

정의를 설명하며 JWT 는 교환 대상 정보인 클레임이 무결하다는 보장을 한다고 했다. JWT 는 서명을 통해서 페이로드의 변조를 막는다!

HMAC 을 예시로들면, 원본 메세지에서 해쉬를 추출하고 비밀키를 통해 암호화해 서명을 생성하고, 생성한 서명을 JWT 문자열의 마지막에 붙인다. 원본 메세지의 정보가 만약 변조되었다면 변조된 메시지에서 생성한 해쉬값과 토큰 뒤에 붙어있는 HMAC 값이 다르기 때문에 메시지가 변조되었음을 알 수 있다.

앞서 Header 에 어떤 알고리즘을 통해 서명을 만들었는지를 저장한다고 했는데, 서명 생성 알고리즘은 HMAC, SHA256 등등 많기 때문에 구분을 하기 위해 저장하는 것이다.

JWT 의 장단점

단점도 당연히 있지

  1. 정보를 저장하는 페이로드가 커질 수록 인코딩되는 문자열의 길이도 길어진다.

    일반적인 인가에서 요청 헤더에 토큰을 포함하거나, URL 에 붙이는 방식을 사용하는데 길어진 토큰 문자열로 인해 네트워크 대역폭이 낭비된다.

  2. 토큰의 정보를 수정 할 수 없음

    변조 방지를 위해 서명을 생성하지만, 동일한 이유로 토큰내의 정보를 수정 할 수 없다. 따라서 토큰의 정보 수정 == 토큰의 재발급이다.

  3. 토큰은 취약하다.

    토큰을 변조 할 수 없는 것과는 별개로, 토큰이 활용되는 프로세스들을 보면 URL 에 노출되거나 웹 브라우저의 저장소에 저장하고 있는 경우들이 있다.

    이 말은 쉽게 탈취가 된다는 뜻이다. 탈취된 토큰에 포함된 정보를 알아내는 것은 Base64 인코딩된 문자열을 다시 풀기만 하면 되므로 매우 쉽다. 위에 용어정리에서 말한 JWE 를 통해 페이로드 자체를 암호화해 서버측에서 풀 수 있도록 하는 것도 하나의 해결방법이 될 수 있겠다.

장점도 있지

별도의 인증 저장소가 불필요

앞서 두 프로세스를 비교하며 별도의 인증 저장소 없이 엑세스 권한을 확인 할 수 있다는 것을 알아보았다. 이것은 단순히 프로세스가 간소화 되었다는 것에서 이점이 있는 것이 아니라 수평 확장에도 매우 용이하다.

JWE 등을 통해 페이로드를 암호화하거나, 기타 복호화 로직들은 CPU 사이클을 소모한다. JWT 는 자신에 대한 정보, 클레임에 대한 정보를 스스로 가지고 있으므로, 서명을 확인하는 과정과 페이로드를 복호화 하는 과정을 수행하는 하드웨어적 자원을 수평 확장할 수있다.

마치며

JWT 가 무엇인지, 어떻게 만들어 지는지, 어떻게 쓰는지에 대해 간단하게 알아보았다.

다음 포스팅에서는 진짜 진짜로 인증 코드, 엑세스 토큰, 리프레시 토큰을 처리하는 Authorization 서버와 해당 인증/인가 요청을 하는 Client 서버를 띄워봐야겠다.

참고 자료

https://doqtqu.tistory.com/275

http://www.opennaru.com/opennaru-blog/jwt-json-web-token/

https://12bme.tistory.com/130

profile
개발을 사랑하는 개발자. 끝없이 꼬리를 물며 답하고 찾는 과정에서 공부하는 개발자 입니다. 잘못된 내용 혹은 더해주시고 싶은 이야기가 있다면 부디 가르침을 주세요!

0개의 댓글