NodeJs. JWT(JSON Web Token) 만들기

2023. 1. 22. 01:55NodeJs

 

목차
1. JWT(JSON Web Token)
2. header(헤더)와 payload(페이로드)의 인코딩 : encode
3. signature(시그니처) 생성하기 : createSignature
4. JWT 만들기 : sign
5. 토큰 비교하기 : verify

1. JWT(JSON Web Token)

 

 

JWT는 기존의 로그인 방식을 보완한 것으로, 쿠키를 암호화, 규격화 해준다.

 

지난번 포스팅 마지막에서 JWT의 원리를 이용해서 직접 JWT를 만들어 보았다.

 

https://char1ey.tistory.com/88

 

JWT(JSON Web Token)

목차 1. 쿠키(Cookie)와 세션(Session) 2. 암호화 3. JWT(JSON Web Token) HTTP 프로토콜은 기본 스펙은 비연결성이다. 3 way hand shake 연결 후에 한 번의 요청과 응답 메시지를 주고받은 후에 4 way hand shake로 연결

char1ey.tistory.com

 

메서드를 만들 때 crypto를 활용하므로, crypto에 대해 모른다면 다음 포스팅도 참고하도록 하자.

 

https://char1ey.tistory.com/90

 

NodeJs.crypto와 암호화

목차 1. crypto 2. 단방향 암호화 3. 단방향 암호화 하기(crypto 메서드) 4. 양방향 암호화 1. crypto crypto란 암호화하다 라는 의미를 가지고 있다. crypto는 노드가 제공하는 내장 모듈중 하나이며, 다양한

char1ey.tistory.com

 

 

이번에는 서버에서 JWT를 사용이 가능하도록, Class와 메서드를 구현해 보도록하자.

 

 

Class로 구현되는 JWT

 

Class가 아니더라도 구현이 가능하지만 이번에는 Class로 기본 틀을 잡고, 나머지 기능들은 메서드로 구현해보도록 하자.

 

JWT 기본구조

class JWT {
    constructor({ crypto }) {
        this.crypto = crypto
    }
    
    method(){...}
    method(){...}
    method(){...}
    ...
}

2. header(헤더)와 payload(페이로드)의 인코딩 : encode

 

 

 

위에서 만들어 놓은 기본구조를 통해서 기능을 하나씩 구현해 보도록하자.

 

 

jWT는 기본 형식이 있다.

 

< JWT 기본구조 >
header.payload.signature

 

위의 구조중에 header와 payload는 인코딩되고, signature는 둘을 이용한 암호화이다.

 

인코딩되는 헤더와 페이로드

 

우선 인코딩을 진행해보도록하자.

 

class JWT {
    constructor({ crypto }){
        this.crypto = crypto
    }
    
    encode(obj){
    return Buffer.from(JSON.stringify(obj)).toString('base64').replace(/[=]/g, "")
    // return Buffer.from(JSON.stringify(obj)).toString('base64url')
    }   
}

 

과정은 이렇다.

 

 

  1. 객체로 된 데이터를 넣어 JSON 문자로 만든다.
  2. JSON문자를 64진수로 인코딩한다.

 

(여기서 base64url과 base64의 차이는 나머지 bit를 나타내냐 마냐의 차이이다.)

 

완성된 메서드는 헤더와 페이로드를 64진수로 인코딩 시켜주는 것이다.

이제 인코딩된 헤더와 페이로드를 이용하여 암호화를 진행시켜 signature를 만들어보자.

 

 

 


3. signature(시그니처) 생성하기 : createSignature

 

 

 

signature는 header와 payload에 키(salt)를 붙여 암호화를 진행한 문자열이다.

 

 

class JWT {
    constructor({ crypto }){
        this.crypto = crypto
    }
    
    // encode(obj){
    //     return Buffer.from(JSON.stringify(obj)).toString('base64url')
    // }

    createSignature(base64urls, salt = "char1ey"){
        const data = base64urls.join(".")
        return this.crypto.createHmac("sha256", salt).update(data).digest("base64url")
    }
}

 

간단한 코드이니 과정을 한번 살펴보자.

 

  1. 앞서 만들어졌을 base64로 인코딩된 문자들을 받아 합친다.
  2. 키(salt)를 받아 암호화 시킨것을 반환해준다. 이는 signature가 된다.

 

지금 까지의 과정은 인코딩과 signature의 생성이다.

 

 

 


4. JWT 만들기 : sign

 

 

 

기존에 만들어놓은 인코딩된 header와 payload를 signature와 합쳐놓으면 JWT이 완성된다.

 

회원가입을 하는 등 데이터를 입력받으면 데이터베이스에 JWT형태로 저장해놓는다.

(※ 데이터를 모두 암호화 하는것은 아니고, 비밀번호와 같은 알려져서는 안되는 것들을 암호화한다.)

 

 

class JWT {
    constructor({ crypto }){
        this.crypto = crypto
    }

    // encode(obj){
    // return Buffer.from(JSON.stringify(obj)).toString('base64url')
    // }
    
    // createSignature(base64urls, salt = "web7722"){
    //     const data = base64urls.join(".")
    //     return this.crypto.createHmac("sha256", salt).update(data).digest("base64").replace(/[=]/g, "")
    // }
    
    sign(data, options = {}){
        const header = this.encode({ tpy: "JWT", alg: "HS256" })
        const payload = this.encode({ ...data, ...options })
        const signature = this.createSignature([ header, payload ])
        return [header, payload, signature].join(".")
        // return `${header}.${payload}.${signature}`
    }
}

 

앞에서 인코딩, signature를 만드는 메서드를 만들었으니,

입력받은 데이터를 메서드를 사용해 그대로 만들어 주기만 하면된다.

 

과정은 다음과 같다.

 

  1. headerpayload를 만든다(base64url).
  2. signature를 만든다.
  3. " . "으로 구분하여 합친다.

 

이렇게 만들어진 JWT는 데이터베이스에 저장한다.

 

JWT의 저장

 

 


5. 토큰 비교하기 : verify, decode

 

 

 

이제 데이터베이스에는 암호화된 데이터가 있다.

사용자는 자신이 누구인지 값을 정확히 입력해주면, 이전에 JWT와 signature가 같을 것이다.

 

만약, 데이터를 잘못 입력했다면 전혀다른 signature가 나와 처음 저장된 signature와 비교해서 로그인을 막아줄 수 있다.

 

데이터를 올바르게 입력한 경우에는 증명이 되었으니 사용자의 중요하지 않은 정보들은 다시 건네주도록 한다.

 

class JWT {
    constructor({ crypto }){
        this.crypto = crypto
    }
    
    verify(token, salt){
        const [ header, payload, signature ] = token.split('.')
        const newSignature = this.createSignature([ header, payload ], salt)
        if( newSignature !== signature){
            throw new Error("토큰이 일치하지 않습니다.")
        }

        return this.decode(payload)
    }
    
    decode(base64){
    return JSON.parse(Buffer.from(base64, 'base64').toString('utf-8'))
    } 
    
    // createSignature(base64urls, salt = "char1ey"){
    //     const data = base64urls.join(".")
    //     return this.crypto.createHmac("sha256", salt).update(data).digest("base64").replace(/[=]/g, "")
    // }
}

 

우선 decode를 살펴보면, encode와 정확히 반대되는 역할이니 어렵지 않다. 한 번 훑어보도록 하자.

 

직접 토큰을 비교하는 verify의 과정을 살펴보도록 하자.

 

  1.  데이터베이스의 토큰을 가져와 header, payload, signature로 나눠준다.
  2.  입력받은 데이터를 이용해서 signature를 만든다.(newSignature)
  3.  데이터베이스에서 가져온 signaturenewSignature를 비교한다.
  4.  일치하지 않는다면 에러를 발생시키고, 일치한다면 payload부분을 디코딩해서 반환한다.

 

JWT를 이용한 로그인

'NodeJs' 카테고리의 다른 글

NodeJs.dotenv 사용하기  (0) 2023.01.28
NodeJs.multer(npm 패키지, 파일 업로드하기)  (0) 2023.01.25
NodeJs. JWT(JSON Web Token)  (0) 2023.01.20
NodeJs.ORM - Sequelize(2)  (0) 2023.01.13
NodeJs.ORM - Sequelize(1)  (1) 2023.01.11