저번 포스팅에서는 Crypto 모듈을 활용하여 로그인까지 구현을 해보았는데 오늘은 이어서 로그인 이후에 인증하는 방식으로 JWT 토큰 방식을 구현해보도록 하겠습니다
JWT란?
Json Web Token의 약자이며, 선택적 서명 및 선택적 암호화를 사용하여 데이터를 만들기 위한 인터넷 표준입니다
토큰은 비공개 시크릿키 또는 공개/비공개 키를 사용하여 서명이 됩니다
JWT 토큰 구조
아래와 같은 구조를 가지고 있으며 각각 부분을 점(.)으로 구분하고 있습니다
aaaaaa.bbbbb.cccccc
header(aaaaaa) | { "alg": "HS256", "typ": "JWT" } |
서명 생성을 위해 어느 알고리즘을 사용할지를 식별합니다 |
payload(bbbbb) | { "sub": "1234567890", "name": "John Doe", "iat": 1516239022 } |
토큰에 담을 실제 정보가 들어있는 부분입니다 |
VERIFY SIGNATURE(cccccc) | HMACSHA256( base64UrlEncode(header) + "." + base64UrlEncode(payload), your-256-bit-secret ) secret base64 encoded |
서명 부분은 토큰을 인증하거나 검증할때 사용하는 부분입니다 |
Node JS JWT 구현
Node JS JWT을 구현하려면 우선 관련된 모듈부터 설치를 진행해야 됩니다
npm i jsonwebtoken
JWT 토큰 생성
const jwt = require('jsonwebtoken');
const option = require("../config/jwt_key").option
async tokenSign(idx,id,nickname){
const data = {
idx:idx,
id : id,
nickname : nickname
}
const token = jwt.sign(data,process.env.SECRETKEY,option) ;
return token;
}
실제 파라미터를 입력받아서 토큰을 생성하는 부분입니다, 저 같은 경우는 idx, id, nickname값을 받아서 토큰을 생성하였으며
jwt모듈에 sign 함수를 사용하여 토큰을 생성합니다, 이때 secretkey, option부분을 설정하였는데, secretkey는 nodejs 환경변수, option는 config폴더에 설정하였습니다
.env
SECRETKEY=chatnode
시크릿키 설정할 때 주의할 점은 실제 실무에서는 위와 같이 단순 문자열로 지정하면 보안성이 매우 낮아지는 주의 하시길 바랍니다
config/jwt_key.js
module.exports = {
option : {
algorithm : "HS256", // 해싱 알고리즘
expiresIn : "1h", // 토큰 유효 기간
issuer : "jungHyeon" // 발행자
}
}
config에는 jwt알고리즘, 유효시간, 발행자를 설정합니다
이렇게 만든 토큰 발급 함수를 로그인 로직에 추가해주고, response에 토큰을 추가해줍니다
return new Promise((resolve,reject)=>{
try{
const sql = "select idx,id,password,salt,nickname from user where id = ?";
mysql.query(sql,[param.id],async (err,rows,fields)=>{
if(err){
reject(err);
}else{
// console.log(rows.length);
if(rows.length){
let hasspass = await encry.checkPassword(param.pass,rows[0].salt);
if(rows[0].password === hasspass){
result.err = 0;
result.accessToken = await jwtToken.tokenSign(rows[0].idx,rows[0].id,rows[0].nickname);
}else{
result.err = 101;
result.errMsg = "비밀번호를 확인해주세요";
}
}else{
result.err = 102;
result.errMsg = "아이디를 확인해주세요.";
}
resolve(result)
}
});
}catch(err){
console.log(err);
}
})
response에 엑세스 토큰을 추가하게 되면 로그인 시 다음과 같이 결과가 리턴이 됩니다
이렇게 발급받은 토큰을 프론트엔드에서는 로그인 후 호출하는 API들에게는 호출 시 토큰을 같이 보내줘야 합니다
그렇기 위에서는 로그인 api호출 후 발급받은 토큰을 로컬 스토리지 혹은 vuex에 저장을 하여 사용하게 됩니다
저는 vuex를 사용하여 구현하였습니다
loginView.vue
/**
* @description 로그인
*/
login(){
if(this.input.valid){
let params = {
id : this.input.id,
pass : this.input.password
}
this.axios.post("/login",params).then((res)=>{
// console.log(res);
if(res.data.err == 0){
alert("로그인 성공");
this.$store.commit("SET_TOKEN",res.data.accessToken)
this.$router.replace('/roomList')
}else{
alert(res.data.errMsg);
}
}).catch((err)=>{
console.log(err);
});
}else{
alert("필수 값을 입력해주세요.");
}
},
vuex
import Vue from 'vue'
import createPersistedState from 'vuex-persistedstate';
import Vuex from 'vuex'
Vue.use(Vuex)
export default new Vuex.Store({
state: {
token : ""
},
getters: {
},
mutations: {
SET_TOKEN(state,value){
state.token = value;
}
},
actions: {
},
modules: {
},
plugins: [createPersistedState()]
})
로그인 성공 이후에 발급받은 토큰을 vuex에 저장을 하게 됩니다, 이때 vuex특징상 새로고침을 하게 되면 vuex에 저장되어있는 데이터들이 모두 초기화되기 때문에 vuex-persistedstate 모듈을 추가하여 새로고침 이후에는 데이터가 유지되도록 하였습니다
토큰 데이터가 저장이 되었다면 axios호출 시 헤더 값에 추가하여 전송할 수 있도록 axios 플러그인을 수정합니다
axios.js
_axios.interceptors.request.use(
function(config) {
// Do something before request is sent
if(location.href.includes("local")){
//로컬 서버
//api 호스트 주소
config.baseURL = "http://localhost:3000";
}
// Do something before request is sent
//content type : json
config.headers.put['Content-Type'] = 'application/json';
//axios 타임아웃 1초
config.timeout = 2000;
//서버에서 받은 토큰값을 쿠기에 저장을 할시 true, 저장을 안할시에는 false
// api 호스트 주소와 같은 오리진을 사용할경우 request header 에 쿠기가 자동적으로 생성이 되는데
// 다른 오리진일경우에는 생성이되지 않습니다, 이떄 이를 해결하기위해 프론트단에서는 withCredentials 을 true로 설정해줍니다
config.withCredentials = false;
//토큰 등록
if(store.state.token != ""){
config.headers.common["Authorization"] = "Bearer " + store.state.token;
}
return config;
},
function(error) {
// Do something with request error
return Promise.reject(error);
}
);
axios request interceptors 부분에 axios headers Authorization 값을 추가해줍니다
이렇게 되면 API 호출 시 header부분에 Auhtorizaiton값이 추가가 되어 API가 호출이 됩니다
JWT 검증
이제 토큰은 발급받았으니 발급받은 토큰이 유효한지 검증하는 로직이 추가가 되어야 합니다
토큰 검증 같은 경우는 API들에 공통적으로 사용되는 부분이면서, 실제 로직이 구현되기 전에 검증부터 처리되어야 되기 때문에 미들웨어로 추가하여 구현하였습니다
우선 토큰을 검증하는 로직입니다
async verify(token){
let verifyToken
try{
verifyToken = jwt.verify(token,process.env.SECRETKEY)
}catch(err){
if (err.message === 'jwt expired') {
//토큰 만료
return 1;
} else if (err.message === 'invalid token') {
//유효하지 않은 토큰
return 2;
} else {
//유효하지 않은 토큰
return 2;
}
}
return verifyToken;
}
파라미터로 넘겨받은 토큰 값과 환경변수에 있는 시크릿 값을 통하여 검증을 진행하며 만약 토큰이 유효하지 않을 경우 각 오류마다 리턴이 되도록 하였습니다
이후 해당 로직을 사용하여 미들웨어를 구현해보도록 하겠습니다
./middleware/verify.js
const Jwt = require("../models/jwt")
const verify = {
tokenVerify:async(req,res,next)=>{
const jwtToken = new Jwt();
const headerToken = req.headers.authorization;
if(headerToken == "" || headerToken === undefined){
res.status(400).json({status: 400, message: "invalid token"})
}else{
const bearer = headerToken.split(" ");
const token = bearer[1];
let verify = await jwtToken.verify(token);
if(verify == 1){
return res.status(400).json({status: 400, message: "token expired"})
}else if(verify == 2){
return res.status(400).json({status: 400, message: "invalid token"})
}else if(verify.idx === undefined){
return res.status(400).json({status: 400, message: "invalid token"})
}
next();
}
}
}
module.exports = verify;
미들웨어로 구성하였기 때문에 request, response를 모두 받을 수 있으며 미들웨어 호출 이후에는 무조건 실제 컨트롤러가 구현되어야 되기 때문에 next()는 필수입니다
우선 토큰 값 같은 경우는 request header에 authorization값으로 전달을 받았으며 bearer토큰이기 때문에 전달받은 토큰 값을 split 하여 실제 토큰 값만 빼내었습니다
이후 위에서 만들었던 verify 모듈을 사용하여 토큰을 검증하고 검증 결과에 따라 400 에러와 메시지를 출력하게 됩니다
토큰이 검증이 정상적이라면 이후 컨트롤러 로직이 next()를 통하여 구현이 됩니다
이제 이렇게 만든 미들웨어를 각 컨트롤러에 적용하게 되면 아래와 같은 구성이 됩니다
router/index.js
const router = require("express").Router();
const controller = require("./controller");
const tokenVerify = require("../middleware/verify").tokenVerify
router.post("/join", controller.join);
router.post("/login",controller.login)
router.get("/roomList",tokenVerify,controller.roomList)
router.get('/', function(req, res){
console.log("root");
res.json({
success: "root",
});
});
module.exports = router;
login, join 컨트롤러 같은 경우는 아직 토큰 발급 전이기 때문에 미들웨어를 추가를 안 했으며, 이후 채팅방 리스트를 보여주는 API 같은 경우는 로그인 이후에 호출하는 API이기에 미들웨어를 추가하였습니다
마치며
기존에는 백엔드에서 발급해준 토큰 값을 받아서 axios에 전달하는 것만 구현을 해보았으나, 실제 백엔드에서 토큰을 발급받고 검증을 해보니 토큰의 구조에 대해서 더 정확하게 알 수 있었던 거 같습니다 또한 node js 미들웨어 부분에 더욱더 학습되었습니다(미들웨어 생성 후 next()를 넣지를 않아서 계속 timeout이 나와 삽질을 조금 했습니다..) 여기까지 하면 기본적인 회원가입, 로그인, 인증 처리는 완료된 거 같으며 이후에는 socket io를 사용하여 실제 방생 성부터 채팅까지 구현해보도록하겠습니다
'javascript' 카테고리의 다른 글
Node Js,Vue - SocketIo 세팅 (0) | 2022.05.22 |
---|---|
Node js Crypto 모듈 로그인 (0) | 2022.05.11 |
단방향 암호화 와 Nodejs Crypto 모듈 (0) | 2022.05.08 |
eval 함수 없이 계산기 만들기-2일차 (0) | 2022.04.10 |
eval 함수 없이 계산기 만들기-1일차 (0) | 2022.04.09 |