Jwt

»

JWT

Node.js JWT API 서버 만드는 방법

모듈 설치

npm install save body-parser express jsonwebtoken mysql2 sequelize

모듈들의 사용 목적

  • body-parser : HTTP Body에서 값들을 빼올때 사용
  • jsonwebtoken : JWT 토큰을 위해 사용
  • sequelize : MySQL과 통신할 때 사용
  • mysql2 : sequelize에서 MySQL과 통신할 때 사용

JWT 토큰 구성

header.payload.signature
  • header: 토큰의 타입과 해시 암호화 알고리즘으로 구성되어 있다.
  • payload: 토큰에 담을 클레임(claim) 정보를 포함하고 있다. name-value 한 쌍으로 이루어져있고, 여러개의 클레임 들을 넣을 수 있다.
  • signature: secret key를 포함하여 암호화되어 있다.

JWT를 통한 Client-Server간 인증 과정

  1. Client에서 로그인을 하면
  2. Server에서 로그인 확인 후 secret kety를 통해 Access token을 발급하여 클라이언트에 JWT를 반환해준다.
  3. 그 후 JWT를 클라이언트에서 보관하다 서버에 요청을 할 때 마다 JWT를 같이 Request Header에 전달해주면
  4. 서버에서 클라이언트가 준 JWT의 서명을 체크하고 JWT에서 사용자 정보를 확인한 뒤
  5. 올바를 경우 클라이언트의 Request에 맞는 Response를 반환하고, 맞지 않을 경우 그에 따른 처리를 한다.

JWT 장점

  • 사용자 인증에 필요한 모든 정보는 토큰 자체에 포함하기 때문에 별도의 인증 저장소가 필요없다.

token 생성법

const jwt = require('jsonwebtoken');

var userInfo = {id: 1, username: 'inyong'};
var secretKey = 'SeCrEtKeYfOrHaShInG';
var options = {expiresIn: '7d', issuer: 'inyongTest', subject: 'userInfo'};

jwt.sign(userInfo, secretKey, options, 
    function(err,token){
      if(err) console.log(err);
      else console.log(token); // eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6MSwidXNlcm5hbWUiOiJpbnlvbmciLCJpYXQiOjE1NTc0ODc2MjIsImV4cCI6MTU1ODA5MjQyMiwiaXNzIjoiaW55b25nVGVzdCIsInN1YiI6InVzZXJJbmZvIn0.idVKe2FVsmvwYBPFJc9vMXi4eZRfFJ6rwhiHIY4gZeo
    }
)

jwt.sign() 함수에 들어가는 4가지 인자

  • userInfo : 아이디, 비밀번호 등 사용자 정보가 들어간 object이다. 형식은 상관없음.
  • secretKey : 여러가지 복잡한 문자열로 되어있는 키.
  • options: 토큰에 대한 여러가지 정보를 설정한다. expiresIn은 토큰 만료일, issuer, subject는 토큰에 대한 정보이다. 외에도 options가 더 있다.
  • 4번째 인자로 들어가는 익명함수 : token 생성결과를 4번째 인자의 콜백함수로 받을 수 있으므로 넣어준 함수.

jwt.sign은 비동기 함수이므로 Promise 처리를 해줘야한다

즉 res.send()로 사용자에게 토큰을 보내준다

token 전달

const express = require("express");
const jwt = require('jsonwebtoken');
const app = express();

app.get('/',(req, res)=>{
  const getToken = () => {
    return new Promise((resolve, reject) => {
      jwt.sign(
        {
          id: 1,
          username: 'inyong'     // 유저 정보
        },
        
        'SeCrEtKeYfOrHaShInG',   // secrec Key
        
        {
          expiresIn: '7d',
          issuer: 'inyongTest',   // options
          subject: 'userInfo'
        }, 
        
        function(err,token){
          if(err) reject(err)      // callback
          else resolve(token)
        }
      )
    });
  } 
  
  getToken().then(token =>{
    res.send(token); // eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6MSwidXNlcm5hbWUiOiJpbnlvbmciLCJpYXQiOjE1NTc0ODc2MjIsImV4cCI6MTU1ODA5MjQyMiwiaXNzIjoiaW55b25nVGVzdCIsInN1YiI6InVzZXJJbmZvIn0.idVKe2FVsmvwYBPFJc9vMXi4eZRfFJ6rwhiHIY4gZeo
  })
});

var server = app.listen(3000, function(){
  console.log("Express server has started on port 3000");
});

jwt.verify 매개변수 3개

  • token: client에게서 받은 token
  • secretkey : token 생성 시 사용했던 secretKey
  • 3번째 인자로 들어간 익명함수 : 유효성 검사 결과를 처리할 callback 함수

예시)

client

var token = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6MSwidXNlcm5hbWUiOiJpbnlvbmciLCJpYXQiOjE1NTc0OTE3NTYsImV4cCI6MTU1ODA5NjU1NiwiaXNzIjoiaW55b25nVGVzdCIsInN1YiI6InVzZXJJbmZvIn0.fpLkn6R6zjJXFq0i9xWpSmLrYmo-afyBjbPg2fJM25s';

fetch('http://localhost:3000', {
      method: 'POST',
      body: JSON.stringify({name:'inyong'}),
      headers: {
        'content-type': 'text/json',
        'x-access-token': token
      }
})
.then(res => {return res.json()})
.then(res => {
  this.render(res);
});

// client 단 서버 요청

server

//생략
...

const router = (req, res, next) => {
  const token = req.headers['x-access-token'] || req.query.token;// client에게서 받은 토큰

/* 토큰이 없으면 403 에러 응답 처리 */if(!token){
    return res.status(403).json({
      success: false,
      message: 'not logged in'
    });
  }
/* 토큰 유효성 검사 */const p = new Promise((resolve, reject) => {
    jwt.verify(token, req.app.get('jwt-secret'), (err,decoded) => {
      if(err) reject(err);
      else resolve(decoded);
    })
  });
/* 유효하지 않은 토큰으로 403 에러 처리 */const onError = (error) => {
    res.status(403).json({
      success: false,
      message: error.message
    })
  };

  p.then((decoded)=>{
    res.json(decoded);
  }).catch(onError);
}

app.post('/decoded', router);

참고로  secret 키와 토큰 검증 부분은 요청시 계속 사용되는 부분이다.

token 검증

사용자는 서버에게서 토큰을 받은 후, 서버에게 요청을 보낼 때, request.Header에 토큰을 포함하여 요청을 보낸다.

그러면 서버는 사용자에게서 받은 토큰이 유효한 것인지 확인한다.

const secretKey = ''; // 아까 token 만들때 썼던 secretkey
const router = (req, res) => {
  const token = req.headers['x-access-token'] || req.query.token;
  jwt.verify(token, secretKey, 
    function(err, decoded){
      console.log(err) // 유효하지 않은 토큰
      console.log(decoded) // 유효한 토큰, 유저 정보 Object 반환
    }
}

참조

Node.js JWT API서버 만드는 방법

JWT 정리 - 개인정리

[2019.05.10] JWT-Token 방식의 정보 인증 [사용법]