JWT(JSON Web Token) - 김종근

TEAM.새싹·2020년 5월 1일
4
post-thumbnail

으쌰으쌰 프로젝트에서 로그인 기능 구현 과정에서 적용한 JWT에 대해 학습한 내용을 정리해봤습니다.

세션 기반 인증 vs 토큰 기반 인증

JWT를 알아보기 전에 HTTP 프로토콜에서 사용자를 인증하는 방법에 대해서 이야기해봅시다.

HTTP 프로토콜의 주요한 두 가지 특징은 다음과 같습니다.

  • Statusless(무상태) : 통신이 종료되면 어떠한 상태의 정보도 남지 않는다.
  • Connectless(비연결성) : 요청에 따른 응답을 받으면 연결이 끊어짐

HTTP 프로토콜의 특징으로 인해 HTTP 프로토콜을 따르는 통신 방식에서는 로그인 후 다시 웹페이지에 접근하면 로그인 상태가 유지되지 않는다는 문제점이 있습니다. 이러한 문제점을 해결하기 위해 등장한 것이 쿠키(Cookie)세션(Session)을 이용한 세션 기반 인증입니다.

세션(Session) 기반 인증

앞서 말했다시피 세션 기반 인증은 쿠키와 세션이라는 기술을 이용합니다. 사용자가 웹사이트에 로그인하는 과정을 통해 쿠키와 세션의 역할을 알아보겠습니다.

  1. 사용자가 로그인을 시도합니다.

    • 이 때, id와 password 등 로그인에 필요한 정보를 가지고 서버에 요청을 보냅니다.
  2. 서버는 요청이 들어오면 로그인 정보에 대한 유효성을 검증합니다.

    • 검증이 완료되면 사용자에 대한 정보를 고유한 id을 기준으로 서버 내 특정 공간에 저장합니다.
    • 이 특정 공간이 바로 세션 저장소이고, 고유 id값으로 저장되는 정보를 세션이라고 합니다.
  3. 서버는 응답 헤더에 세션 id를 포함시켜 요청에 대한 응답을 반환합니다.

  4. 응답을 받은 클라이언트는 해당 세션 id를 클라이언트 내의 공간에 저장합니다.

    • 클라이언트 내의 이 공간이 쿠키입니다.
  5. 사용자는 인증이 필요한 요청에 접근할 때마다 세션 id가 담긴 쿠키를 요청 헤더에 포함시켜 요청을 보내고, 서버는 세션 저장소에 일치하는 세션 id가 있는지 검증한 후 요청에 해당하는 데이터를 반환합니다.

요약하자면 세션 기반 인증은 사용자 인증 정보를 세션 형태로 서버 내 메모리에 저장해두고 인증이 필요할 때마다 세션 id로 인증을 검증한다는 것입니다.

꽤나 괜찮은 아이디어임은 분명하지만 세션 기반 인증에는 몇 가지 단점이 있습니다. 아이러니하게도 가장 분명한 단점은 세션 기반의 특징인 서버에 메모리를 저장한다는 점입니다. 서버는 클라이언트가 요청하는 데이터를 응답함과 동시에 사용자의 인증 정보까지 관리를 하게 되었습니다. 그리고 접속한 사용자가 증가할수록 서버 역시 그만큼의 부담을 지게 됩니다. (심지어 브라우저를 껐다켜도 로그인이 유지되는 서비스를 제공하고자 한다면 관리해야 하는 세션의 수는 현재 사용자+a일 것입니다.) 이러한 세션 기반의 문제를 개선하기 위해서 등장한 것이 토큰 기반 인증입니다.

토큰(Token) 기반 인증

토큰 기반 인증은 세션 기반 인증과 다르게 토큰을 이용하여 인증을 하는 방식입니다. 마찬가지로 로그인 flow를 통해 토큰 기반 인증에 대해 알아보겠습니다.

  1. 사용자가 로그인을 시도합니다. (로그인에 필요한 정보를 가지고 서버에 요청)
  2. 서버는 요청이 들어오면 로그인 정보에 대한 유효성을 검증합니다.
    • 검증이 완료되면 정상적으로 발급된 토큰임을 증명하는 signature를 갖는 토큰을 생성합니다.
  3. 서버는 응답 시 생성된 토큰을 클라이언트에 반환합니다.
  4. 응답 받은 클라이언트는 해당 토큰을 쿠키 등에 저장하고, 요청 시에 요청 헤더에 토큰을 담아 서버로 요청합니다.
  5. 서버는 토큰의 유효성을 검증하여 데이터를 전달합니다.

서버에서 상태 정보를 저장하는 세션 기반 인증과 다르게 토큰 기반 인증은 서버에서 상태 정보를 저장하고 있지 않습니다. 토큰 자체가 유효한 토큰인지 아닌지만 검증하면 되기 때문에 서버에 부하를 주지 않는다는 특징이 있습니다. 또한 서버가 여러 대인 경우, 세션 기반 인증은 여러 대 중 한대의 서버만이 세션을 가지고 있기 때문에 다른 서버에서 사용자의 요청을 받게 되면 세션이 적용되지 않는 문제가 생깁니다. 세션 기반 인증은 이를 해결하기 위해서 sitcky 세션(같은 서버에 세션을 계속 연결시키는 방식)을 사용해야하겠지만, 토큰 기반 인증은 이러한 구축없이 자연스럽게 문제가 해결된다는 장점이 있어 서버 확장성 측면에서도 장점을 갖습니다.

하지만 토큰을 기반으로 하는 인증 역시 만능은 아닙니다. stateless한 토큰의 특성 때문에 토큰은 강제로 만료시킬 수 없다는 문제가 있습니다. 만약 토큰이 공격자에게 탈취되었다면, 공격자는 토큰이 만료될 때까지 서버에 요청을 할 수 있습니다. 때문에 토큰 기반 인증을 구현하려 한다면 동시에 보안에 주의해야합니다.

JWT(JSON Web Token)란?

다시 본론으로 돌아와 JWT에 대해 이야기해보겠습니다. JWT는 JSON Web Token의 약자로 이름 그대로 JSON 형식을 이용하여 웹에서 사용할 수 있는 엑세스 토큰을 다루는 표준입니다. JWT는 토큰 기반 인증 방식으로 토큰 자체에 유저 정보를 담아서 HTTP 헤더로 전달하기 때문에 유저 세션을 유지할 필요가 없고 가볍게 데이터를 주고받을 수 있다는 장점이 있습니다.

JWT의 인증 과정

JWT의 인증 과정은 다음과 같습니다.

  1. client가 서버에게 인증 정보를 보냅니다.

  2. server는 인증 정보를 토대로 token을 생성해서 client에게 전송합니다.

  3. client는 받은 token을 매 request때마다 같이 보냅니다.

  4. server는 token으로 client를 인증합니다.

JWT 기본 구조

모든 JWT는 header, payload 그리고 signature로 총 3가지로 구성되어 있습니다.

.을 구분자로 3가지 문자열로 되어있습니다.

PartDescription
Header- 데이터의 타입
- 알고리즘 종류
Payload(Claims)- 실제 어떤 데이터가 담겨 있는지(암호화가 안되어있어 웹으로 노출하면 안되는 데이터를 담아서는 안된다)
- 가능한 많은 데이터를 담지 않는 것이 좋다.
- Production으로 사용 할 시에는 토큰이 언제까지 유효한지 담는 것이 좋음
Signature- 데이터와 토큰이 위변조 되지 않았음을 증명
- 데이터를 일정하게 해싱하고 해싱한 데이터를 암호화 해서 데이터가 위변조 되지 않음을 증명
- HMAC-SHA256 사용 (해당 알고리즘을 사용하기 위해 비밀키를 생성하는데 비밀키는 반드시 노출이 되지 않도록 조심해야 한다)

헤더(header)

웹 토큰의 헤더 정보

typ : 토큰의 타입을 명시합니다.

alg : 해싱 알고리즘. 해싱 알고리즘으로는 보통 HMAC SHA256 혹은 RSA 가 사용되며, 이 알고리즘은, 토큰을 검증 할 때 사용되는 signature 부분에서 사용됩니다.

{
  "typ": "JWT",
  "alg": "HS256"
}

JSON은 "\n" 등 개행문자를 포함하고 있기 때문에 HTTP Header 등에 넣기가 불편합니다. 그래서 JWT는 Base64 기반으로 인코딩해 하나의 문자열로 변환해서 사용합니다. (단, Base64는 암호화된 문자열이 아닙니다. 같은 문자열에 대해서는 항상 같은 인코딩 문자열을 반환합니다.)

내용(payload)

실제 토큰으로 사용하려는 데이터가 담기는 부분. 각 데이터를 Claim이라고 하며 다음과 같이 3가지 종류가 있습니다.

Reserved claims(등록된 클레임) : 이미 예약된 Claim으로 필수는 아니지만 사용하길 권장합니다. key 는 모두 3자리 String입니다.

  • 모든 이름이 짧은데, JWT를 최대한 작게 만드려는 디자인 원칙에 맞춘 것입니다.
  • iss: 토큰 발급자 (issuer)
  • sub: 토큰 제목 (subject)
  • aud: 토큰 대상자 (audience)
  • exp: 토큰의 만료시간 (expiraton), 시간은 NumericDate 형식으로 되어있어야 하며 (예: 1480849147370) 언제나 현재 시간보다 이후로 설정되어있어야 합니다.
  • nbf: Not Before 를 의미하며, 토큰의 활성 날짜와 비슷한 개념입니다. 여기에도 NumericDate 형식으로 날짜를 지정하며, 이 날짜가 지나기 전까지는 토큰이 처리되지 않습니다.
  • iat: 토큰이 발급된 시간 (issued at), 이 값을 사용하여 토큰의 age 가 얼마나 되었는지 판단 할 수 있습니다.
  • jti: JWT의 고유 식별자로서, 주로 중복적인 처리를 방지하기 위하여 사용됩니다. 일회용 토큰에 사용하면 유용합니다.

Public claims(공개 클레임) : 사용자 정의 Claim.

  • Public 이라는 이름처럼 공개용 정보입니다.
  • 충돌 방지를 위해 URI 포맷을 이용해 저장합니다.

Private claims(비공개 클레임) : 사용자 정의 Claim

  • Public claims 과 다르게 사용자가 임의로 정한 정보입니다.
  • 일반 정보를 저장합니다.
{
"name" : "bell",
"age" : 26
}

서명(signature)

Header와 Payload의 데이터 무결성과 변조 방지를 위한 서명

  • 헤더의 인코딩값과, 정보의 인코딩값을 합친후 주어진 비밀키로 해쉬를 하여 생성합니다.

서명 부분을 만드는 슈도코드(pseudocode)의 구조는 다음과 같습니다.

HMACSHA256(
  base64UrlEncode(header) + "." +
  base64UrlEncode(payload),
  secret)

이렇게 만든 해쉬를 Base64 형태로 인코딩하면 됩니다.

JWT 생성 과정

전체 과정을 간단하게 다시 확인해보겠습니다.

  1. JSON 형식으로 claim 정의(payload)합니다.
{
  "name": "hellozin",
  "job": "developer"
}
  1. JSON으로 정의된 claim을 base64 방식으로 인코딩합니다.
ewogICJpZCI6ICJoZWxsb3ppbiIsCiAgImpvYiI6ICJkZXZlbG9wZXIiCn0=
  1. JSON으로 정의된 claim으로 HMAC값을 생성합니다.
// 알고리즘="SHA256", 비밀키="hello"
4C136E93C7720EE7C20F8375A487101D1142112DAF7A7A48960A5991220D8B11
  1. 사용된 알고리즘 정보를 JSON으로 정의한 뒤 base64 방식으로 인코딩합니다.
// 알고리즘 정보
{
  "alg": "SHA256",
  "typ": "JWT"
}
// base64 방식으로 인코딩
ewogICJhbGciOiAiU0hBMjU2IiwKICAidHlwIjogIkpXVCIKfQ==
  1. 생성된 정보들로 토큰을 생성합니다.
{알고리즘정보}.{메시지}.{HMAC}

ewogICJhbGciOiAiU0hBMjU2IiwKICAidHlwIjogIkpXVCIKfQ==.ewogICJpZCI6ICJoZWxsb3ppbiIsCiAgImpvYiI6ICJkZXZlbG9wZXIiCn0=.4C136E93C7720EE7C20F8375A487101D1142112DAF7A7A48960A5991220D8B11

마치며

지금까지 토큰 기반 인증 방식인 JWT에 대해 알아봤습니다. 프로젝트에 JWT를 적용하기 위해 학습을 하게 된 것이지만, 개인적으로 토큰 기반의 인증 방식이 등장하게 된 배경이 더 흥미로웠던 것 같습니다. JWT 자체는 언어별로 라이브러리가 있어 구현에는 크게 어려움이 없었습니다. jwt.io 에 방문하시면 설명과 함께 직접 실습까지 해볼 수 있도록 제공이 되어 있으니 참고하시면 좋을 것 같습니다.

혹시! 부족한 내용이나 잘못된 부분이 있다면 댓글로 공유 부탁드립니다!🙏🏻

참고

profile
전남대 출신 개발 스터디 그룹 새싹팀 입니다. @ECONOVATION

0개의 댓글