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로 보내서 인증!
- 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)