ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • JWT(1) 알아가기
    Etc 2018. 10. 17. 18:13

    JSON Web Token 이 뭘까?

    기본 정보

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

    수많은 프로그래밍 언어에서 지원됩니다

    JWT 는 C, Java, Python, C++, R, C#, PHP, JavaScript, Ruby, Go, Swift 등 대부분의 주류 프로그래밍 언어에서 지원됩니다.

    자가 수용적 (self-contained) 입니다

    JWT 는 필요한 모든 정보를 자체적으로 지니고 있습니다. JWT 시스템에서 발급된 토큰은, 토큰에 대한 기본정보, 전달 할 정보 (로그인시스템에서는 유저 정보를 나타내겠죠?) 그리고 토큰이 검증됐다는것을 증명해주는 signature 를 포함하고있습니다.

    쉽게 전달 될 수 있습니다

    JWT 는 자가수용적이므로, 두 개체 사이에서 손쉽게 전달 될 수 있습니다. 웹서버의 경우 HTTP의 헤더에 넣어서 전달 할 수도 있고, URL 의 파라미터로 전달 할 수도 있습니다.

     

    JWT 는 어떤 상황에서 사용될까?

    다음과 같은 상황에서 JWT 가 유용하게 사용 될 수 있습니다:

    • 회원 인증: JWT 를 사용하는 가장 흔한 시나리오 입니다. 유저가 로그인을 하면, 서버는 유저의 정보에 기반한 토큰을 발급하여 유저에게 전달해줍니다. 그 후, 유저가 서버에 요청을 할 때 마다 JWT를 포함하여 전달합니다. 서버가 클라이언트에게서 요청을 받을때 마다, 해당 토큰이 유효하고 인증됐는지 검증을 하고, 유저가 요청한 작업에 권한이 있는지 확인하여 작업을 처리합니다.
      서버측에서는 유저의 세션을 유지 할 필요가 없습니다. 즉 유저가 로그인되어있는지 안되어있는지 신경 쓸 필요가 없고, 유저가 요청을 했을때 토큰만 확인하면 되니, 세션 관리가 필요 없어서 서버 자원을 많이 아낄 수 있죠.
    • 정보 교류: JWT는 두 개체 사이에서 안정성있게 정보를 교환하기에 좋은 방법입니다. 그 이유는, 정보가 sign 이 되어있기 때문에 정보를 보낸이가 바뀌진 않았는지, 또 정보가 도중에 조작되지는 않았는지 검증할 수 있습니다.

     

    JWT 의 생김새

    JWT 는 . 을 구분자로 3가지의 문자열로 되어있습니다. 구조는 다음과 같이 이루어져있습니다:

    jwt자, 그럼 이렇게 3가지 부분으로 나뉘어져 있는 토큰을 하나하나 파헤쳐봅시다.

    JWT 토큰을 만들때는 JWT 를 담당하는 라이브러리가 자동으로 인코딩 및 해싱 작업을 해줍니다. 하지만 이 포스트에서는 JWT 토큰이 만들어지는 과정을 더 잘 파악하기 위해 하나하나 Node.js 환경에서 인코딩 및 해싱을 하도록 하겠습니다.

    헤더 (Header)

    Header 는 두가지의 정보를 지니고 있습니다.

    typ: 토큰의 타입을 지정합니다. 바로 JWT 이죠.

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

    한번, 이 예제를 살펴보세요 :

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

    (위 예제에서는 HMAC SHA256 이 해싱 알고리즘으로 사용됩니다)

    이 정보를 base64 로 인코딩을 하면 다음과 같습니다.

    Node.js 환경에서 인코딩하기

    const header = {
      "typ": "JWT",
      "alg": "HS256"
    };
    
    // encode to base64
    const encodedPayload = new Buffer(JSON.stringify(payload))
                                .toString('base64')
                                .replace('=', '');
    console.log('payload: ',encodedPayload);
    
    /* Result:
    header: eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9
    */
    eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9

    자, 이제 JWT 의 첫번째 파트가 완성되었습니다!

    참고: JSON 형태의 객체가 base64 로 인코딩 되는 과정에서 공백 / 엔터들이 사라집니다. 따라서, 다음과 같은 문자열을 인코딩을 하게 되죠:
    {"alg":"HS256","typ":"JWT"}

     

    정보 (payload)

    Payload 부분에는 토큰에 담을 정보가 들어있습니다. 여기에 담는 정보의 한 ‘조각’ 을 클레임(claim) 이라고 부르고, 이는 name / value 의 한 쌍으로 이뤄져있습니다. 토큰에는 여러개의 클레임 들을 넣을 수 있습니다.

    클레임 의 종류는 다음과 같이 크게 세 분류로 나뉘어져있습니다:

    등록된 (registered) 클레임,
    공개 (public) 클레임,
    비공개 (private) 클레임

    그럼, 하나 하나 알아볼까요?

    #1 등록된 (registered) 클레임

    등록된 클레임들은 서비스에서 필요한 정보들이 아닌, 토큰에 대한 정보들을 담기위하여 이름이 이미 정해진 클레임들입니다. 등록된 클레임의 사용은 모두 선택적 (optional)이며, 이에 포함된 클레임 이름들은 다음과 같습니다:

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

    #2 공개 (public) 클레임

    공개 클레임들은 충돌이 방지된 (collision-resistant) 이름을 가지고 있어야 합니다. 충돌을 방지하기 위해서는, 클레임 이름을 URI 형식으로 짓습니다.

    {
        "https://velopert.com/jwt_claims/is_admin": true
    }

    #3 비공개 (private) 클레임

    등록된 클레임도아니고, 공개된 클레임들도 아닙니다. 양 측간에 (보통 클라이언트 <->서버) 협의하에 사용되는 클레임 이름들입니다. 공개 클레임과는 달리 이름이 중복되어 충돌이 될 수 있으니 사용할때에 유의해야합니다.

    {
        "username": "velopert"
    }

    예제 Payload

    {
        "iss": "velopert.com",
        "exp": "1485270000000",
        "https://velopert.com/jwt_claims/is_admin": true,
        "userId": "11028373727102",
        "username": "velopert"
    }

    위 예제 payload 는 2개의 등록된 클레임1개의 공개 클레임2개의 비공개 클레임으로 이뤄져있습니다.

    위 데이터를 base64 로 인코딩을 해볼까요?

    Node.js 환경에서 인코딩하기

    const payload = {
        "iss": "velopert.com",
        "exp": "1485270000000",
        "https://velopert.com/jwt_claims/is_admin": true,
        "userId": "11028373727102",
        "username": "velopert"
    };
    
    // encode to base64
    const encodedPayload = new Buffer(JSON.stringify(payload))
                                .toString('base64')
                                .replace('=', '');
    
    console.log('payload: ',encodedPayload);
    
    /* result
    payload:  eyJpc3MiOiJ2ZWxvcGVydC5jb20iLCJleHAiOiIxNDg1MjcwMDAwMDAwIiwiaHR0cHM6Ly92ZWxvcGVydC5jb20vand0X2NsYWltcy9pc19hZG1pbiI6dHJ1ZSwidXNlcklkIjoiMTEwMjgzNzM3MjcxMDIiLCJ1c2VybmFtZSI6InZlbG9wZXJ0In0
    */
    eyJpc3MiOiJ2ZWxvcGVydC5jb20iLCJleHAiOiIxNDg1MjcwMDAwMDAwIiwiaHR0cHM6Ly92ZWxvcGVydC5jb20vand0X2NsYWltcy9pc19hZG1pbiI6dHJ1ZSwidXNlcklkIjoiMTEwMjgzNzM3MjcxMDIiLCJ1c2VybmFtZSI6InZlbG9wZXJ0In0

    주의: base64로 인코딩을 할 때 dA== 처럼 뒤에 = 문자가 한두개 붙을 때가 있습니다. 이 문자는 base64 인코딩의 padding 문자라고 부릅니다.
    JWT 토큰은 가끔 URL 의 파라미터로 전달 될 때도 있는데요, 이 = 문자는, url-safe하지 않으므로, 제거되어야 합니다. 패딩이 한개 생길 때도 있고, 두개 생길 때도 있는데, 전부 지워(제거해줘도 디코딩 할 때 전혀 문제가 되지 않습니다)

    JWT 토큰의 두번째 파트가 완성되었습니다!

     

    서명 (signature)

    JSON Web Token 의 마지막 부분은 바로 서명(signature) 입니다. 이 서명은 헤더의 인코딩값과, 정보의 인코딩값을 합친후 주어진 비밀키로 해쉬를 하여 생성합니다.

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

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

    이렇게 만든 해쉬를, base64 형태로 나타내면 됩니다 (문자열을 인코딩 하는게 아닌 hex → base64 인코딩을 해야합니다)

     

    한번, 이 포스트에서 사용된 예제 헤더와 정보를 해싱 해볼까요?

    먼저, 헤더와 정보의 인코딩 값 사이에 . 을 넣어주고, 합칩니다

    eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJ2ZWxvcGVydC5jb20iLCJleHAiOiIxNDg1MjcwMDAwMDAwIiwiaHR0cHM6Ly92ZWxvcGVydC5jb20vand0X2NsYWltcy9pc19hZG1pbiI6dHJ1ZSwidXNlcklkIjoiMTEwMjgzNzM3MjcxMDIiLCJ1c2VybmFtZSI6InZlbG9wZXJ0In0

    이 값을 비밀키의 값을 secret 으로 해싱을 하고 base64로 인코딩하면 다음과 같은 값이 나옵니다.




    출처: https://velopert.com/2389




    슈도코드란 ?


    특정 프로그래밍 언어가 아닌 일반적인 언어로 코드를 작성한 것, 가짜로 적었다는 뜻 입니다.
    이것은 허위로 작성한 코드이기 때문에 컴퓨터에서 실행 할 수 없으며 대략적인 모델링 하는 것으로 사용됩니다.



Designed by Tistory.