본문 바로가기

웹 인프라

JWT란 뭘까?

안녕하세요 :)

 

오늘은 JWT에 대해 알아보겠습니다. 현실 세계에서 사람이 각자 자신의 신분을 보증하는 것처럼, 웹 환경에서도 사용자 개개인을 인증하는 어떤 방법이 필요합니다. 웹 환경에서는 JWT가 그 역할을 해주는데요.

출처: https://goteleport.com/blog/what-are-jwts/

이 글에서는 JWT가 무엇인지, 어떻게 구성되고 작동하는지, 실제 인증 과정에서 어떤 역할을 하는지 살펴보도록 하겠습니다. 

 

JWT란?

Json Web Token의 약자이며, 웹 애플리케이션에서 사용자 인증과 데이터 교환을 위해 사용되는 토큰 기반 Stateless(무상태)인증 방식입니다.

 

여기서 무상태 인증 방식이란 무엇일까요?

서버가 사용자 인증 정보를 별도로 저장하지 않고, 클라이언트가 매 요청마다 토큰을 서버로 보내어 인증을 유지하는 방식을 말합니다. 이 방식을 사용하게 되면, 서버는 상태를 관리할 필요가 없어져 다른 환경으로 쉽게 확장할 수 있고, 리소스 부담 또한 적어집니다. 그리고 클라이언트가 인증 상태를 로컬에서 유지하기 때문에 다양한 환경에서 일관된 인증이 가능하며, 서버 장애 시에도 인증 상태에는 영향을 받지 않을 수 있습니다.

 

JWT는 Json 포맷으로 구성되어 있는데, 이 Json 포맷 덕분에 사람과 컴퓨터 모두 쉽게 정보를 이해할 수 있는 구조로 정보를 담을 수 있습니다.

JWT를 사용자에 대한 정보를 담고 있는 디지털 신분증이라고 생각하면 편합니다. 예를 들어, 사용자가 로그인하면 서버는 이 사람의 ID나 권한과 같은 정보를 담은 JWT를 만들어 사용자에게 전달합니다. JWT는 Json 형식으로 정보를 담고 있기 때문에 사람이나 컴퓨터가 내용을 쉽게 확인할 수 있습니다.

 

Json 형식으로 정보를 담고 있다는 게 무슨 말일까요?

아래 예시처럼 Json 포맷 덕분에 사용자 정보는 키와 값의 형태로 표현됩니다.

{
    "user_id" : "jinuk123",
    "role" : "admin"
}

이와 같은 방식으로 사용자 정보를 표현할 수 있다는 뜻입니다. 

 

JWT는 사용자 정보를 Json 형태로 담아 클라이언트에 전달하고, 클라이언트는 이후 서버와의 여러 요청에서 자신이 인증된 사용자임을 JWT를 통해 증명하게 됩니다.

 

여기서 중요한 점은, JWT에 담긴 정보가 안전하게 보호되어야 한다는 것입니다. JWT의 Signature(서명)가 바로 이 역할을 하는데요, Signature 덕분에 JWT는 중간에 변조되지 않고 서버에서 인증된 사용자임을 보장할 수 있게 됩니다.

 

Signature(서명)이란 무엇일까요?

여기서 '서명'이란, JWT가 변조되지 않았는지 확인하기 위한 보안 장치입니다.

 

조금 더 자세히 이해하기 위해 JWT의 구조를 보도록 하겠습니다.

 

JWT 구조

JWT는 기본적으로 Header, Payload, Signature 세 부분으로 구성됩니다. 각 요소가 서로 다른 역할을 수행하면서 하나의 온전한 토큰을 형성하게 됩니다. 

 

1. Header(헤더)

  • Header는 JWT의 메타데이터를 담고 있는 부분입니다. 
  • 메타데이터는 JWT가 어떻게 암호화되었는지를 설명하는 정보를 담고 있습니다. 주로 두 가지 정보가 들어가게 됩니다.
    • typ(타입): 토큰이 JWT라는 걸 나타냅니다.
    • alg(알고리즘): 토큰이 변조되지 않았는지 검증하기 위해 사용된 암호화 알고리즘을 지정합니다. 보통 HMAC SHA256(HS256) 또는 RSA(RS256)가 많이 사용됩니다.

 

2. Payload(페이로드)

  • Payload는 JWT의 본문에 해당하는 부분이며, Claims(클레임)이 담겨 있는 부분입니다.
  • Clamis이란 인증된 사용자에 대한 정보를 담고 있는 데이터로, 클레임들은 JWT를 통해 전달되고 클라이언트와 서버 간의 정보 교환에서 사용됩니다.
  • Payload에는 아래와 같은 종류의 클레임이 담길 수 있습니다.
    • Registered Clamis(등록된 클레임): JWT에서 권장하는 클레임입니다.
    • Public Clamis(공개된 클레임): 애플리케이션이 필요로 하는 추가적인 정보를 JWT에 원하는 이름으로 담는 클레임입니다. 
    • Private Clamis(비공개 클레임): 서버와 클라이언트가 서로만 알아야 하는 어떠한 정보를 담는 클레임입니다. 다른 시스템과는 공유하지 않는, 오직 서로만 알 수 있는 약속된 정보들입니다. 
  • Payload는 Base64Url로 인코딩되므로, 누구나 디코딩하여 내용을 확인할 수 있지만, Payload는 서명되지 않으면 변조가 불가능합니다. 
  •  Payload에는 주로 아래와 같은 클레임이 담기게 됩니다. 아래는 JWT에서 자주 사용되는 기본적인 정보들입니다.
    • sub(subject): 사용자 고유 ID를 의미하고, 이 토큰이 누구에 대한 것인지를 나타냅니다.
    • exp(expiration time): 토큰의 만료 시간을 의미합니다. 시간이 지나면 더 이상 해당 토큰은 유효하지 않습니다.
    • iat(issued at): 토큰이 언제 발급되었는지를 나타냅니다.
    • iss(isser): 토큰을 발급한 주체를 의미합니다. 일반적으로 발급자의 도메인이나 식별자를 나타냅니다.
    • aud(audience): 이 토큰의 대상을 나타냅니다. 주로 토큰을 사용할 서비스나 애플리케이션의 이름을 담아, 어떤 시스템이 이 토큰을 사용해야 하는지 나타내는 역할을 합니다.
    • nbf(not before): 토큰이 유효해지기 시작하는 시간을 나타냅니다. 시간이 지나기 전까지는 토큰을 사용할 수 없습니다.

 

3. Signature(서명)

  • Signature는 JWT의 무결성을 보장합니다.
  • 이 '서명'을 통해 토큰이 변조되지 않았음을 검증할 수 있습니다.
  • Header와 Payload를 조합하여, 서버가 가진 Secret Key로 암호화된 것입니다.
  • Signature 생성되는 과정과 역할은 아래와 같습니다.
    • 서버는 Header와 Payload를 Base64Url로 인코딩하고, 이를 비밀 키와 지정된 암호화 알고리즘(Header의 alg)을 사용해 서명합니다.
    • 생성된 서명은 JWT 맨 마지막에 포함되며, 서버는 클라이언트가 보낸 JWT를 검증할 때 이 서명을 사용하여 토큰이 무결하다는 것을 확인하게 됩니다.
    • 클라이언트가 서버로부터 JWT를 발급받은 후, 서버는 클라이언트가 요청할 때마다 이 서명을 검증하여 토큰이 변조되지 않았음을 확인합니다.
  • 서명이 유효하다면, 서버는 해당 사용자를 신뢰할 수 있는 대상으로 간주하고 요청을 처리하게 됩니다.

 

JWT를 이용한 인증 방식

사용자가 로그인한 후 JWT를 사용하여 서버에 인증 요청을 보낼 수 있게 되며, 이 JWT는 인증이 필요한 모든 요청에서 사용됩니다.

이제 JWT 발급 과정과 인증 요청의 처리 방식의 흐름을 단계별로 살펴보겠습니다. 아래 예시로 든 JWT를 참고하면 도움이 될 거 같습니다.

{
  "header": {
    "alg": "HS256",
    "typ": "JWT"
  },
  "payload": {
    "sub": "user123",
    "role": "admin",
    "exp": 1718248548
  },
  "signature": "abc123signatureXYZ"
}

 

1. 로그인 및 JWT 발급

  • 사용자가 어떤 애플리케이션에서 아이디와 비밀번호를 입력하여 로그인합니다.
  • 서버는 사용자 정보를 확인한 후에 JWT를 발급합니다.
  • JWT에는 사용자 ID, 사용자 권한, 만료 시간 등의 정보가 담기고, 클라이언트는 이 JWT를 받아 안전하게 저장합니다.
  • 발급받은 JWT는 LocalStorage, SessionStorage, Cookies에 저장할 수 있는데, 저장 위치에 따라 보안 및 사용성이 달라지기 때문에 보안 공격에 대한 추가 조치를 생각해야 합니다.

2. 서버로 인증 요청 보내기

  • 클라이언트는 발급받은 JWT를 Authorization 헤더에 포함하여 서버로 인증 요청을 보냅니다.
  • 예를 들어, 사용자가 자신의 프로필 정보를 조회할 때, Bearer 방식으로 JWT를 포함합니다.
    • 예시: Authorization: Bearer abc123signatureXYZ
    • (Bearer방식: 인증 토큰을 Authorization 헤더에 담아 '이 토큰을 소지한 사람을 인증된 사용자'로 간주하여 처리하는 인증방식)

3. 서버에서 JWT 검증

  • 서버는 클라이언트로부터 받은 JWT를 Header, Payload, Signature로 나눈 후, 서명을 검증하여 JWT가 변조되지 않았음을 확인합니다.
  • 검증할 내용은 아래와 같습니다.
    • Signature가 서버의 비밀 키로 생성된 것이 맞는지 확인하여 변조 여부를 확인
    • exp(만료 시간)이 아직 지나지 않았는지 확인
  • 검증된 JWT가 유효하다면 서버는 인증된 사용자임을 확인하고 요청을 처리합니다.

4. 권한에 따른 요청 처리

  • 서버는 JWT의 Payload에 담긴 role이 "admin"인 경우에만 관리자 페이지에 접근을 허용하게 됩니다.
  • 만약 role이 "admin"이 아닌 다른 값인 경우라면, 접근이 제한됩니다.

5. JWT 만료 갱신 (리프레시 토큰 사용)

  • JWT에는 exp가 설정되어 있기 때문에, 토큰이 만료되면 더 이상 사용할 수 없습니다.
  • 이럴 경우, 리프레시 토큰을 통해 새로운 JWT를 발급받을 수 있습니다. 리프레시 토큰은 주로 더 긴 만료 시간을 가지며, 새로운 JWT를 발급하는 역할을 합니다.
  • 클라이언트가 만료된 JWT와 함께 리프레시 토큰을 전송하여, 서버에 새로운 JWT 발급을 요청합니다.
  • 서버가 리프레시 토큰이 유효하면 새로운 JWT를 발급해주고, 클라이언트는 다시 로그인하지 않아도 인증 상태를 유지할 수 있게 됩니다.

 

JWT를 사용할 시 주의할 점

위처럼 JWT를 잘 사용하게 되면 효율적인 인증 시스템을 구축할 수 있습니다. 하지만 JWT는 특성상 안전하게 사용하기 위해 주의해야 할 점이 몇 가지 있습니다. 특히, 토큰 탈취나 변조 방지, 만료 관리 등의 요소를 신중히 생각해야 합니다.

왼: CSRF, 오: XSS / 출처: https://www.wallarm.com/what/what-is-xss-cross-site-scripting, https://www.writesoftwarewell.com/how-csrf-attack-works-cross-site-request-forgery/

  1. JWT 저장 위치에 따른 보안문제
    • XSS(Cross Site Scripting) 공격에 대비해, LocalStorage에 JWT를 저장하는 것은 피하는 것이 좋습니다. 대신 HttpOnly와 Secure 옵션이 설정된 쿠키에 저장하는 것이 안전할 수 있습니다.
    • 하지만 쿠키를 사용해도, CSRF 공격에 대비해야 하므로 보안 대비책을 생각해야 합니다.
  2. 민감한 정보 포함 금지
    • JWT는 Base64Url로 인코딩되서 쉽게 디코딩될 수 있습니다. 따라서 토큰 내부인 Payload에 민감한 정보는 절대 포함하지 않는 것이 원칙입니다. 사용자 비밀번호나 금융 정보 등의 민감한 정보는 절대 주의해야 합니다.
  3. 적당한 만료 시간(exp) 설정
    • JWT가 탈취될 경우를 대비해, 너무 길지 않은 적당한 만료 시간을 설정하는 것이 좋습니다. 일반적으로 몇 분에서 몇 시간 정도로 설정합니다.

 

끝내는 말

이제 웹 환경에서 JWT가 어떻게 동작하는지 이해하셨을 거 같습니다. JWT는 서버에서 상태를 저장하지 않아도 클라이언트가 인증 상태를 로컬에서 유지할 수 있어서 매우 유용하게 사용할 수 있습니다. 다만, 보안 설정과 사용 위치에 따라서 정보를 보호하는 지침도 생각해봐야 한다는 점 잊지 않으셨음 좋겠습니다.

 

읽어주셔서 감사합니다 :)