[JWT] 2. JSON Web Token

Bomin·2022년 9월 21일
0

[Side Project]

목록 보기
2/4
post-thumbnail

이전에는 세션 방식(서버 인증 방식) 을 이용해서 로그인 기능을 구현했지만,

최근엔 JWT라는 토큰 기반의 인증 방식이 나오면서 웹과 모바일에서 대부분의 인증이 JWT로 구현되고 있다. JWT(Json Web Token)는 클레임 토큰 기반 인증 방식으로 인증에 필요한 정보를 암호화시켜 토큰 body에 저장함으로써 클라이언트가 증명서처럼 사용한다.

세션과 비교하여 JWT

JWT는 세션과 비교하였을 때 무상태(stateless)와 확장성(scalability)의 이점이 있다.

토큰은 클라이언트에서 저장하기 때문에 서버측에서는 stateless하며, 요청이 왔을 때 서명이 옳바른지만 확인하면 되므로 비저장의 이점을 지닌다.

또한 msa와 같은 서버가 여러대로 분리되어 사용되고 있다면 어떤 유저가 로그인 했을때 그 유저가 처음 로그인했던 서버에만 요청을 보내야 하기 때문에 확장성에 제약이 있지만 토큰방식을 사용한다면 어떤 서버로 요청이 오더라도 관계없이 인증할 수 있다.

또한 보안적으로도 기존의 세션 방식의 HTTP 요청을 해커가 가로챌 경우 그 안의 쿠키를 이용해 악용될 수 있는 보안적 약점을 보완했다.

JSON Web Token 이란?

JSON Web Token (JWT) 은 웹표준 (RFC 7519) 으로서 두 개체에서 JSON 객체를 사용하여 가볍고 자가수용적인 (self-contained) 방식으로 정보를 안전성 있게 전달해준다.

  • 수 많은 프로그래밍 언어에서 지원된다. C, Java, Python, C++, R, C#, PHP, JavaScript, Ruby, Go, Swift 등 대부분의 주류 프로그래밍 언어에서 지원
  • 자가수용적(self-contained)이다. JWT는 필요한 모든 정보를 자체적으로 지닌다. JWT 시스템에서 발급된 토큰은 토큰에 대한 기본정보, 전달 할 정보(로그인시스템의 경우 유저정보), 그리고 토큰이 검증되었다는 것을 증명해주는 signature를 포함한다.
  • 쉽게 전달 가능하다. JWT는 자가수용적이므로, 두 개체 사이에서 손쉽게 전달 될 수 있다. 웹서버의 경우 HTTP의 헤더에 넣어서 전달하거나 URL의 파라미터로 전달 할 수도 있다.

JWT가 유용하게 사용되는 경우

  • 회원 인증 JWT를 사용하는 가장 흔한 시나리오. 유저가 로그인 → 서버는 유저의 정보기반한 토큰을 발급, 유저에게 전달 → 유저가 서버에 요청할 때마다 JWT를 포함하여 전달 → 서버가 클라이언트에게서 요청을 받을 때마다 해당 토큰이 유효하고 인증 됐는지 검증, 유저가 요청한 작업에 권한이 있는지 확인하여 작업 처리
  • 정보 교류 JWT는 두 개체 사이에서 안정성있게 정보를 교환하기에 좋은 방법. 그 이유는 정보가 sign 이 되어있기 때문에 정보를 보낸이가 바뀌진 않았는지, 또 정보가 도중에 조작되지는 않았는지 검증할 수 있다.

JWT 구성요소

  • HEADER : JWT의 헤더는 타입과 알고리즘을 지정하고, BASE64 인코딩 되어 가장 맨 앞에 위치한다. 토큰 유형과 해시 알고리즘이 들어있다.
  • PAYLOAD : JWT의 페이로드는 공개 클레임(claim. 정보의 한 단위, key/value 한 쌍을 의미)과 비공개 클레임을 작성한 뒤 BASE64 인코딩하여 두 번째 요소에 위치시킨다. 지금 로그인한 사람이 누구인지 정보가 들어가는데, 쉽게 노출되지 않도록 pk키 등 쉽게 식별할 수 없는 정보가 들어가야 한다. (유저 아이디 권장하지 않음). 토큰 유효기간은 claim set이라고도 불리는 페이로드에 저장할 수 있다.
    • payload는 공개 데이터다. 아래와 같이 누구나 jwt.io에 접속해 JWT 정보 확인 가능하다. 따라서 비밀번호 등의 보안이 필요한 정보는 payload에 저장하면 안된다.
    • 공개된 클레임 이름은 토큰에서 사용하기 위해서 정의했지만, 충돌을 방지하기 위해서 공개한 이름이고 비밀 클레임이름은 서버와 클라이언트가 협의로 사용하는 이름을 의미다.
  • SIGNATURE : JWT의 시그니처는 인코딩된 header와 페이로드를 합쳐 별도 지정된 secret key를 이용해 header에 지정된 알고리즘으로 암호화하여 생성한다. 즉, 토큰의 변조 여부를 확인할 수 있는 일련의 문자열로 header에서 지정한 알고리즘과 Payload를 secret key로 담는다. 프론트엔드에서 JWT를 백엔드 API 서버로 전송하면 서버에서는 전송받은 JWT의 서명 부분을 복호화하여 서버에서 생성한 JWT가 맞는지 확인한다. 계약서 위변조를 막기 위해 서로 사인하는 것과 같다.**** JWT에서는 정보는 공개되어 있지만 해시 값을 통해서 정보가 유효한지 확인하게 된다. 따라서 secret key가 유출되면 보안상의 위협이 된다.

JWT.IO

JWT 인증 과정

토큰 발행(로그인)

  1. 클라이언트가 유저에 대한 정보(ID,Password)를 서버에 보낸다.
  2. 서버는 DB에서 유저 정보의 유효성을 확인한다.
  3. 유저 정보 중 일부를 JWT body에 넣고 토큰을 발행한다.
  4. 클라이언트에게 응답한다.

토큰 검증

  1. HTTP header에 토큰 값을 담아서 보낸다.
  2. 서버는 토큰 값을 받아서 JWT 정보와 서버가 가지고 있는 secret key를 이용해서 서명(signature)을 만든다. 이 때 JWT의 서명과 일치하다면 유효한 토큰으로, 일치하지 않다면 유효하지 않은 요청으로 판단한다.

JWT를 이용한 인가 방식

Refresh token과 Access token 두 가지를 사용한다는 전제

  1. 클라이언트에서 로그인 요청(Post)

  2. 서버에서 요청이 들아온 로그인 정보와 DB에 저장된 유저 정보와 비교

  3. Access Token을 발급하여 클라이언트에 JWT를 반환

  4. 클라이언트에서 Access Token을 가지고 있다가 인가가 필요한 요청(댓글 작성, 회원정보 수정 등)을 할 때 Header에 담아 전달

  5. Access Token이 만료됐다면 서버에 있는 Refresh Token이 유효한 지 확인 후 Access Token 재발급

  6. 재발급한 Accss Token을 헤더에 담아 다시 서버에 요청

  7. 서버에서 토큰과 맞는 유저 정보가 있는지 확인(디코딩 과정?)

  8. 찾은 유저 정보와 요청한 유저가 일치하는지 확인.

  9. 일치하면 Request에 맞는 Response를 반환하고, 만료된 토큰이거나 잘못된 토큰이면(일치하는 유저가 없으면) 401 Unauthorized 리턴

JWT 사용 시 주의사항

JWT 페이로드 부분에는 사용자를 특정할 수 있는 정보가 들어가면 안 된다. 페이로드는 사용자를 식별할 수 있는 정보를 담고 있는데, 암호화가 아닌 단순 BASE64 인코딩으로 만든다. 누구나 쉽게 디코딩할 수 있기 때문에 아이디나 이메일 등 사용자 개인정보를 페이로드에 담아 인코딩한다면 개인정보가 유출될 우려가 있다. 따라서 DB에 저장된 유저의 PK값 같이 디코딩된 정보만 봐서는 어떤 사용자인지 알 수 없는 정보를 담아야 한다.

참고) payload에 들어가는 클레임 설명 (필수 아님. 모두 선택사항)

  • iss : 토큰 발급자(issuer)
  • sub : 토큰 제목(subject). 토큰이 갖는 문맥
  • aud : 토큰 대상자(audience). 토큰을 사용할 수신자
  • exp : 토큰의 만료시간(expiraton), 시간은 NumericDate 형식으로 되어있어야 하며 (예: 1480849147370) 언제나 현재 시간보다 이후로 설정해야 함
  • nbf : Not Before를 의미하며, 토큰의 활성 날짜와 비슷한 개념. 여기에도 NumericDate 형식으로 날짜를 지정하며, 이 날짜 이전에는 토큰을 처리하지 않아야 함을 의미
  • iat : 토큰이 발급된 시간(issued at), 이 값으로 토큰의 age가 얼마나 되었는지 판단
  • jti : JWT의 고유 식별자로, 중복 처리를 방지하기 위하여 사용. 일회용 토큰에 사용하면 유용

JWT 변조 공격 대처

대표적 JWT 변조 공격은 Signature Stripping인데 헤더의 alg 클레임을 None으로 변조하는 공격으로, 일부 JWT 라이브러리들이 alg가 None인 토큰을 유효한 토큰으로 인식하는 문제가 있다. 또는 웹 게시판 등에 사용자가 입력한 값이 DB에 저장되고, 프론트엔드단에 출력하는 구조를 가진 페이지에서 공격자가 <script>태그를 입력하면 공격이 성립할 수 있다. 이때 일반 쿠키 또는 세션스토리지에 저장한 토큰이 탈취당할 수 있다.

이를 보완하는 방법 첫 번째는 Refresh Token입니다. 사용자가 로그인할 때 Access Token과 함께 Refresh Token을 발급하는 것입니다.

  • Access Token : 짧은 시간 내 만료되는 토큰. 사용자의 인증, 인가에 사용되는 토큰. 주로 세션에 저장
  • Refresh Token : Access Token에 비해 긴 만료시간(하루 ~ 일주일 등)을 갖는 토큰으로 Access Token 재발급용 토큰. 노출되면 안 되므로 데이터베이스에 저장

클라이언트는 Access Token이 만료되었다는 오류를 받으면 따로 저장해두었던 Refresh Token을 이용하여 Access Token의 재발급을 요청한다. 서버는 유효한 Refresh Token으로 요청이 들어오면 새로운 Access Token을 발급하고, 만료된 Refresh Token으로 요청이 들어오면 오류를 반환, 사용자에게 로그인을 요구한다.

Access Token은 서버에 따로 저장해 둘 필요가 없지만, Refresh Token은 서버에 저장해 Access Token 재발행 시 검증에 활용해야 한다. 그러므로 Refresh Token을 이용한다는 것은 추가적인 I/O(Input/Output) 작업이 필요하다는 의미이며, 이는 I/O 작업이 필요없는 빠른 인증 처리를 장점으로 내세우는 JWT의 스펙에 포함되지 않는 부가적인 기술이다.

그 외에도 토큰 자체를 암호화 하기, HTTPS 통신에서만 쿠키를 전송할 수 있게 하기, 기기 로그인 IP 등을 토큰에 넣는 것도 방법이 될 수 있다.

참고자료
https://velopert.com/2389
https://ooeunz.tistory.com/74
https://naon.me/posts/til63

profile
Frontend-developer

0개의 댓글