NodeJS - JWT Token 사용하기

김정욱·2020년 12월 10일
28

NodeJS

목록 보기
10/22
post-thumbnail

JWT ?

JSON Web Token의 약자로, 각 객체 사이에서 속성 정보 (Claim)
JSON 데이터 구조로 표현하고 암호화를 통해 정보를 전달하는 Token의 대표!


[ 특징 ]

  • 가볍고 자가 수용적 (self-contained)
  • HTTP header / URI 파라미터를 이용해 전달 가능
  • 다양한 프로그래밍 언어에서 지원!

[ 구성 ]

  • HEADER(헤더)
    • typ : 토큰의 타입을 지정(JWT)
    • alg : 해싱 알고리즘을 지정(보통 HMAC SHA256 / RSA 사용)
{
  "typ": "JWT",
  "alg": "HS256"
}

  • PAYLOAD(내용) - 사용되는 정보의 한 조각을 클레임(claim)이라고 함
                                    (name-value 쌍)
    1) 등록된(registerd) 클레임 : 토큰에 대한 정보를 담기 위해 정해진 클레임
/* 모든 등록된 클레임은 선택적으로 사용! 필수가 아님! */
iss : 토큰 발급자 (issuer)
sub : 토큰 제목 (subject)
aud : 토큰 대상자 (audience)
exp : 토큰의 만료시간 (expiration) / 형식은 NumericDate
nbf : Not Before 을 의미 / 토큰의 활성 날짜

    2) 공개(public) 클레임 : 충돌이 방지된 collision-resistant 이름을 가져야함
                                    (URI를 많이 사용함)

{
	"https://neity16.com/auth/is_admin": true
}

    3) 비공개(private) 클레임 : 클라이언트와 서버 간 합의하에 사용되는 이름들
          (데이터)

{
"idx": 3,
"id": "neity16",
"age": 25,
}

  • VERIFY SIGNATURE(서명)
    : header + payload 정보를 비밀키로 해쉬를 하여 생성!
      (즉, payload가 바뀌어도 이 값에 영향을 주기 때문에 보안성이 높아짐!)
HMACSHA256(
  base64UrlEncode(header) 
  + "." 
  + base64UrlEncode(payload),
  secretkey)

[ 작동 원리 ]

(출처 : 경선 블로그 https://gngsn.tistory.com/)

1 ) 클라이언트에서 HTTP Request 요청을 보냄
2 ) 서버는 유효성을 확인하고 Token을 발급하여 Response로 보냄

{
    “jwt” : “eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZHgiOj
    IsImlkIjoiZ25nc24iLCJhZ2UiOjIzLCJpc3MiOiJvdXItc
    29wdCIsImV4cCI6IjE1OTA5MzcxOTkifQ.P4WFfJKZ6XZVU
    Q1qlLqKaGBilx4AS-q0DNnbAXt8Jbs”
}

3 ) 클라이언트는 해당 Token 값을 HTTP Request header / URI로 보내서 인증!

NodeJS에서 사용하기(Access Token)

  • jwt에서 사용 할 함수jwt.sign --> 토큰 발급
                                         jwt.verify --> 토큰 인증(확인)

[ 사용 ]

1) /config/secretkey.js 생성

(/config/secretkey.js)
: 해당 파일은 유출되면 안되므로 /config에 위치하여 .gitignore로 처리!


module.exports = {
    secretKey : 'YoUrSeCrEtKeY', // 원하는 시크릿 ㅍ키
    option : {
        algorithm : "HS256", // 해싱 알고리즘
        expiresIn : "30m",  // 토큰 유효 기간
        issuer : "issuer" // 발행자
    }
}

2) /modules/jwt.js 생성하여 sign / verify 를 생성

(/modules/jwt.js)

const randToken = require('rand-token');
const jwt = require('jsonwebtoken');
const secretKey = require('../config/secretKey').secretKey;
const options = require('../config/secretKey').options;
const TOKEN_EXPIRED = -3;
const TOKEN_INVALID = -2;

module.exports = {
    sign: async (user) => {
        /* 현재는 idx와 email을 payload로 넣었지만 필요한 값을 넣으면 됨! */
        const payload = {
            idx: user.userIdx,
            email: user.email,
        };
        const result = {
            //sign메소드를 통해 access token 발급!
            token: jwt.sign(payload, secretKey, options),
            refreshToken: randToken.uid(256)
        };
        return result;
    },
    verify: async (token) => {
        let decoded;
        try {
            // verify를 통해 값 decode!
            decoded = jwt.verify(token, secretKey);
        } catch (err) {
            if (err.message === 'jwt expired') {
                console.log('expired token');
                return TOKEN_EXPIRED;
            } else if (err.message === 'invalid token') {
                console.log('invalid token');
                console.log(TOKEN_INVALID);
                return TOKEN_INVALID;
            } else {
                console.log("invalid token");
                return TOKEN_INVALID;
            }
        }
        return decoded;
    }
}

3) signin시 토큰 생성에 사용

(/controllers/user.js)

const jwt = require('../modules/jwt');

signin : async ( req, res ) => {
...
   /* user정보를 DB에서 조회 */
  const user = await User.getUserByEmail(email);
  /* user의 idx, email을 통해 토큰을 생성! */
  const jwtToken = await jwt.sign(user);
  return res.status(statusCode.OK).send(util.success(statusCode.OK, responseMsg.LOGIN_SUCCESS, {
       /* 생성된 Token을 클라이언트에게 Response */
        token: jwtToken.token
    }))
}

4) /middlewares/auth.js 작성 (인증이 필요한 곳에 미들웨어로 적용하기 위함)

(/middlewares/auth.js)

const jwt = require('../modules/jwt');
const MSG = require('../modules/responseMessage');
const CODE = require('../modules/statusCode');
const util = require('../modules/util');
const TOKEN_EXPIRED = -3;
const TOKEN_INVALID = -2;

const authUtil = {
    checkToken: async (req, res, next) => {
        var token = req.headers.token;
        // 토큰 없음
        if (!token)
            return res.json(util.fail(CODE.BAD_REQUEST, MSG.EMPTY_TOKEN));
        // decode
        const user = await jwt.verify(token);
        // 유효기간 만료
        if (user === TOKEN_EXPIRED)
            return res.json(util.fail(CODE.UNAUTHORIZED, MSG.EXPIRED_TOKEN));
        // 유효하지 않는 토큰
        if (user === TOKEN_INVALID)
            return res.json(util.fail(CODE.UNAUTHORIZED, MSG.INVALID_TOKEN));
        if (user.idx === undefined)
            return res.json(util.fail(CODE.UNAUTHORIZED, MSG.INVALID_TOKEN));
        req.idx = user.idx;
        next();
    }
}

module.exports = authUtil;

5) 인증이 필요한 곳에서 route에 middleware로 설정!

(/routes/record.js)

profile
Developer & PhotoGrapher

1개의 댓글

comment-user-thumbnail
2024년 11월 8일

좋은 글 감사드립니다!

답글 달기