본문 바로가기

블록체인 기반 핀테크 및 응용 SW개발자 양성과정 일기

[53일차] 20210528 JWT token 암호화 , 검증 예제 Node.js

반응형

 

 

어제 만든 토큰을
인증키 검증을 통해 맞다면 json 64비트로 만든걸 글자형으로 다시 만들기


$npm i nunjucks
+ 코드 작성

const nunjucks=require('nunjucks'); nunjucks.configure('views',{ express:app, }) app.set('view engine', 'html'); app.get('/', (req,res)=>{ res.render('index.html') })

로그인 버튼 누르면 로그인 팝업 나오게 만들기 (window.open 말고 layer popup 으로!)
css,JS 를 사용해보기 static 정적파일 사용할 수 있게 세팅하기

app.use(express.static('public'));


파일 구조

views - index.html

<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Document</title> <link href="./css/main.css" rel = "stylesheet"> <script type="text/javascript" src="./js/main.js"></script> </head> <body> <button id = "loginBtn">로그인</button> <div class ="layerPopup"> <div class="loginPopup"> <div id = "loginForm"> //form -> div 변경 <h1>로그인 페이지</h1> <input type="text" name = "userid" id="userid" placeholder="아이디를 입력해주세요."> <input type="password" name="userpw" id="userpw" placeholder="패스워드를 입력해주세요."> <button id = "localLogin" class="loginSubmit" >로그인</button> <button id = "kakaoLogin" class = "loginSubmit kakao">카카오로그인</button> <p class="joinWord">아직도 회원이 아니세요? <span class="joinBtn">회원가입</span> </p> </div> </div> </div> </body> </html>

html에 css, js 잘 연결되었는지 확인해보기 ↓↓
public - css - main.css

*{ margin:0; padding:0; color:darkblue; }

publick - js - main.js

alert('dd');


main.css

*{ margin:0; padding:0; } .layerPopup{ position:fixed; top:0; width:100%; height:100%; background:#000000a6; } .loginPopup{ width:400px; height:300px; background:white; box-sizing:border-box; padding:0 50px 50px 50px; position:absolute; left:50%; top:50%; transform: translate(-50%,-50%); } #loginForm>h1{ margin-top:32px; } #loginForm>input{ width:100%; box-sizing:border-box; padding:7px 14px; margin-top:10px; border:1px solid #666; } #loginForm>.loginSubmit{ width:100%; height:30px; border:1px solid #666; background:#fff; margin-top:10px; cursor:pointer; border-radius:15px; } #loginForm>.loginSubmit.kakao{ background:yellow; } .joinWord{ margin-top:10px; text-align: left; font-size: 12px; } .joinWord>.joinBtn{ float:right; color:blue; cursor:pointer; }


.layerPopup display: none 주기 & open도

.layerPopup{ display:none; position:fixed; top:0; width:100%; height:100%; background:#000000a6; } .layerPopup.open{ display:block; }



main.js

document.addEventListener('DOMContentLoaded',init); // 딱 한번 최초 실행 function init(){ const loginBtn = document.querySelector('#loginBtn'); loginBtn.addEventListener('click',loginBtnFn) } function loginBtnFn(){ const layerPopup =document.querySelector('.layerPopup'); // layerPopup.setAttribute('class', 'layerPopup open'); layerPopup.classList.add('open'); //오우 }



여기까지 localhost:3000 -> 로그인 버튼 클릭 -> 로그인 페이지 나오게 만듬!



검정 배경 클릭했을 때 닫히게 만들기

js

document.addEventListener('DOMContentLoaded',init); // 딱 한번 최초 실행 function init(){ const loginBtn = document.querySelector('#loginBtn'); const layerPopup=document.querySelector('.layerPopup'); loginBtn.addEventListener('click',loginBtnFn) layerPopup.addEventListener('click', layerPopupClose); } function loginBtnFn(){ const layerPopup =document.querySelector('.layerPopup'); // layerPopup.setAttribute('class', 'layerPopup open'); layerPopup.classList.add('open'); //오우 } function layerPopupClose(event){ console.log(event) }

console.log(event, this) 찍어보기












검정배경을 클릭하면 사라지지만 하얀 배경 클릭하면 안사라지게 만들기 !
js

document.addEventListener('DOMContentLoaded',init); // 딱 한번 최초 실행 function init(){ const loginBtn = document.querySelector('#loginBtn'); const layerPopup=document.querySelector('.layerPopup'); loginBtn.addEventListener('click',loginBtnFn) layerPopup.addEventListener('click', layerPopupClose); } function loginBtnFn(){ const layerPopup =document.querySelector('.layerPopup'); // layerPopup.setAttribute('class', 'layerPopup open'); layerPopup.classList.add('open'); //오우 } function layerPopupClose(event){ console.log(event, this) if(event.target==this){ this.classList.remove('open'); } }



로그인버튼 눌렀을 때 서버에 요청하는 거 만들기
fetch로 내용 전달
main.js

document.addEventListener('DOMContentLoaded',init); // 딱 한번 최초 실행 function init(){ const loginBtn = document.querySelector('#loginBtn'); const layerPopup=document.querySelector('.layerPopup'); const localLogin=document.querySelector('#localLogin'); loginBtn.addEventListener('click',loginBtnFn) layerPopup.addEventListener('click', layerPopupClose); localLogin.addEventListener('click',localLoginFn); } function loginBtnFn(){ const layerPopup =document.querySelector('.layerPopup'); // layerPopup.setAttribute('class', 'layerPopup open'); layerPopup.classList.add('open'); //오우 } function layerPopupClose(event){ if(event.target==this){ this.classList.remove('open'); } } async function localLoginFn(){ const userid = document.querySelector('userid'); const userpw = document.querySelector('userpw'); if(userid.value==""){ alert('아이디를 입력해 주세요'); userid.focus(); return 0; // return을 끝낸다는 의미 //return 안쓰면 if 절 밖에 fetch가 실행될거야 } if(userpw.value==""){ alert('패스워드를 입력해주세요.'); userpw.focus(); return 0; } //POST auth/local/login let url = `http://localhost:3000/auth/local/login`; let options = { method:'POST', //userid,userpw값 두개를 보낼거야 server측에서 받을 수 있는 방법 2가지 //1. 일방적인 방법 key=value&key2=value2 headers:{ 'content-type':'application/x-www-form-encoded' }, //post로 보냈고 queryString으로 보냈구나 알도록 headers추가 body:`userid=${userid.value}&userpw=${userpw.value}`, } let response = await fetch(url,options); console.log(response) } 


$npm i body-parser
코드 추가

const bodyParser = require('body-parser'); app.user(bodyParser.urlencoded({extended:false,}));


여기까지 server.js

const express=require('express'); const cookieParser=require('cookie-parser'); const tokenKey=require('./key'); const nunjucks=require('nunjucks'); const bodyParser = require('body-parser'); const app = express(); nunjucks.configure('views',{ express:app, }) app.set('view engine', 'html'); app.use(express.static('public')); app.use(bodyParser.urlencoded({extended:false,})); app.use(cookieParser()); app.get('/', (req,res)=>{ res.render('index.html') }) app.post('/auth/local/login',(req,res)=>{ let {userid,userpw} = req.body; console.log(userid,userpw); res.json({ result:true, msg:'로그인에 성공했씁니다' }) }) app.get('/login',(req,res)=>{ let {id,pw} = req.query; if(id=='root' && pw=='root'){ res.cookie('token',tokenKey, {httpOnly:true,secure:true}); res.redirect('/?msg=로그인성공'); }else{ res.redirect('/?msg=id와 pw가 일치하지 않습니다.'); } }) app.get('/menu1',(req,res)=>{ console.log(req.cookies); res.send('menu1페이지입니다'); }) app.listen(3000,()=>{ console.log('start port : 3000'); })

public - main.js

document.addEventListener('DOMContentLoaded',init); // 딱 한번 최초 실행 function init(){ const loginBtn = document.querySelector('#loginBtn'); const layerPopup=document.querySelector('.layerPopup'); const localLogin=document.querySelector('#localLogin'); loginBtn.addEventListener('click',loginBtnFn) layerPopup.addEventListener('click', layerPopupClose); localLogin.addEventListener('click',localLoginFn); } function loginBtnFn(){ const layerPopup =document.querySelector('.layerPopup'); // layerPopup.setAttribute('class', 'layerPopup open'); layerPopup.classList.add('open'); //오우 } function layerPopupClose(event){ if(event.target==this){ this.classList.remove('open'); } } async function localLoginFn(){ const userid = document.querySelector('#userid'); const userpw = document.querySelector('#userpw'); if(userid.value==""){ alert('아이디를 입력해 주세요'); userid.focus(); return 0; // return을 끝낸다는 의미 //return 안쓰면 if 절 밖에 fetch가 실행될거야 } if(userpw.value==""){ alert('패스워드를 입력해주세요.'); userpw.focus(); return 0; } //POST auth/local/login let url = `http://localhost:3000/auth/local/login`; let options = { method:'POST', //userid,userpw값 두개를 보낼거야 server측에서 받을 수 있는 방법 2가지 //1. 일방적인 방법 key=value&key2=value2 headers:{ 'content-type':'application/x-www-form-urlencoded' }, //post로 보냈고 queryString으로 보냈구나 알도록 headers추가 body:`userid=${userid.value}&userpw=${userpw.value}`, } let response = await fetch(url,options); console.log(response) } 

public - index.html

<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Document</title> <link href="./css/main.css" rel = "stylesheet"> <script type="text/javascript" src="./js/main.js"></script> </head> <body> <button id = "loginBtn">로그인</button> <div class ="layerPopup"> <div class="loginPopup"> <div id = "loginForm"> <h1>로그인 페이지</h1> <input type="text" name = "userid" id="userid" placeholder="아이디를 입력해주세요."> <input type="password" name="userpw" id="userpw" placeholder="패스워드를 입력해주세요."> <button id = "localLogin" class="loginSubmit" >로그인</button> <button id = "kakaoLogin" class = "loginSubmit kakao">카카오로그인</button> <p class="joinwWord">아직도 회원이 아니세요? <span class="joinBtn">회원가입</span> </p> </div> </div> </div> </body> </html>

pupblic- main.css

*{ margin:0; padding:0; } .layerPopup{ display:none; position:fixed; top:0; width:100%; height:100%; background:#000000a6; } .layerPopup.open{ display:block; } .loginPopup{ width:400px; height:300px; background:white; box-sizing:border-box; padding:0 50px 50px 50px; position:absolute; left:50%; top:50%; transform: translate(-50%,-50%); } #loginForm>h1{ margin-top:32px; } #loginForm>input{ width:100%; box-sizing:border-box; padding:7px 14px; margin-top:10px; border:1px solid #666; } #loginForm>.loginSubmit{ width:100%; height:30px; border:1px solid #666; background:#fff; margin-top:10px; cursor:pointer; border-radius:15px; } #loginForm>.loginSubmit.kakao{ background:yellow; } .joinWord{ margin-top:10px; text-align: left; font-size: 12px; } .joinWord>.joinBtn{ float:right; color:blue; cursor:pointer; }

 

-------------------------------------body 보내는 방법 1 끗 (body - form type)--------------------------------------

 

-------------------------------이제 body 보내는 방법 2번째 시작 (body - json형식으로)--------------------------------


main.js

 let url = `http://localhost:3000/auth/local/login`; let options={ method:'POST', headers:{ 'content-type':`application/json` }, body:JSON.stringify({ userid:userid.value, userpw:userpw.value, }), } let response = await fetch(url,options); console.log(response)


요청은 잘 들어갔음 (요청헤더, body쪽 봐봐) 근데 console.log = undefined
-> middleware 하나 놔주기 in server.js

app.use(bodyParser.json());

body에 json 오면 받을 수 있게 해라 ~ server on -> console 잘 뜸

queryString으로 보내면 content Type - x-www 이거
json type 이면 content type - json + app.use(bodyParser.json()


API 요청할 때 웬만하면 JSON 형태로 보내자 header에다가 넣어서도 보낼 수 잇음.
main.js

let url = `http://localhost:3000/auth/local/login`;
let options = {
    method: 'POST',
    headers: {
        'content-type': `application/json`,
        'data': JSON.stringify({
            userid: userid.value, userpw: userpw.value
        })
    },
    body: JSON.stringify({
        userid: userid.value,
        userpw: userpw.value,
    }),
}



req.get() -> headers의 속성값에 접근할 수 있음
string으로 받은거 다시 json으로 바꿔서 사용해야함
server.js

app.post('/auth/local/login', (req, res) => {
    //let {userid,userpw} = req.body; 
    let { userid, userpw } = JSON.parse(req.get('data'));
    //String -> 객체 형태로 돌리기 
    console.log('data req: ', userid, userpw);
    //console.log('body req: ',userid,userpw); 
    res.json({ result: true, msg: '로그인에 성공했씁니다' })
})



요청자에 따라 header, body 에 넣어주세요 ~ 이렇게 요구가 달라서 다 알아야함

다시 돌려놓기
server.js

app.post('/auth/local/login',(req,res)=>{ let {userid,userpw} = req.body; //let {userid, userpw} = JSON.parse(req.get('data')); //String -> 객체 형태로 돌리기 //console.log('data req: ', userid, userpw); console.log('body req: ',userid,userpw); res.json({ result:true, msg:'로그인에 성공했씁니다' }) })

main.js

 let url = `http://localhost:3000/auth/local/login`; let options={ method:'POST', headers:{ 'content-type':`application/json`, }, body:JSON.stringify({ userid:userid.value, userpw:userpw.value, }), }





로그인 성공여부 / db는 ㄴㄴ
server.js

app.post('/auth/local/login',(req,res)=>{ let {userid,userpw} = req.body; console.log('body req: ',userid,userpw); //원래는 DB접속 후 결과 return let result={}; if(userid=="asdf"&&userpw=="asdf"){ result = { result:true, msg:'로그인에 성공햇습니다.' } }else{ result={ result:false, msg:'아이디와 패스워드를 확인해주세요' } } res.json(result); })


defualt로 실패를 깔고 가도 가능













main.js

async function localLoginFn() {
    const userid = document.querySelector('#userid');
    const userpw = document.querySelector('#userpw');
    if (userid.value == "") {
        alert('아이디를 입력해 주세요');
        userid.focus();
        return 0; // return을 끝낸다는 의미 //return 안쓰면 if 절 밖에 fetch가 실행될거야 } 
    }
    if (userpw.value == "") {
        alert('패스워드를 입력해주세요.');
        userpw.focus(); return 0;
    }
    //POST auth/local/login 
    let url = `http://localhost:3000/auth/local/login`;
    let options = {
        method: 'POST',
        headers: { 'content-type': `application/json`, },
        body: JSON.stringify({
            userid: userid.value,
            userpw: userpw.value,
        })
    }
    let response = await fetch(url, options);
    let res_result = await response.json(); //body내용 가져오기 promise객체 떨어지게 
    console.log(res_result)
}









로그인 성공했을 때 token 생성해주기
토큰 생성 코드 기니까 파일 따로 만들자 -jwt.js
$npm i crypto
$npm i dotenv


jwt.js (userid 를 담은 토큰 생성과정)

require('dotenv').config();
const crypto = require('crypto');
const { create } = require('domain');
//JWT 토큰 header.payload.signature 
function createToken(userid) {
    let header = { "tpy": "JWT", "alg": "HS256", }
    //현재시간으로부터 미래시간 : 2시간을 더한 수 
    //두시간 뒤 재로그인 - 길면 길수록 해킹 위험 up 
    let exp = new Date().getTime() + ((60 * 60 * 2) * 1000) //1970.1.1부터 0 // 1월1일 1초지났으면 1000 
    let payload = {
        //인자로 들어온 애 
        userid,
        //expiary date 
        exp,
    }
    ///Buffer.from - 바이너리 파일 만들기 
    const encodingHeader = Buffer.from(JSON.stringify(header))
                            .toString('base64')
                            .replace('=', '').replace('=', '');
    const encodingPayload = Buffer.from(JSON.stringify(payload))
                            .toString('base64')
                            .replace('=', '').replace('=', '');
    //console.log(encodingHeader, encodingPayload); 
    //암호화 시작 //첫번째 : 암호방식 /두번째인자 : key값 (이게 공개되면 해킹됨) - best-env 
    let signature = crypto.createHmac('sha256', Buffer.from(process.env.salt))
                            .update(encodingHeader + "." + encodingPayload)
                            .digest('base64')
                            .replace('=', '').replace('=', '');
    let jwt = `${encodingHeader}.${encodingPayload}.${signature}` return jwt;
}
let token = createToken('too');
console.log(token)
//userid 값만 넣어줄 예정 
//module.exports=(userid)=>{ } 아래와 같은 뜻 
module.exports = createToken;


console.log에 찍힌 값 -> JWT사이트가서 확인 - > secret base 62 endoce 체크 -> Signature Verified

token 으로 가져오기 in

const tokenKey=require('./jwt')


server.js

const express = require('express');
const cookieParser = require('cookie-parser');
const nunjucks = require('nunjucks');
const bodyParser = require('body-parser');
const tokenKey = require('./jwt')

const app = express();
nunjucks.configure('views', { express: app, })
app.set('view engine', 'html');
app.use(express.static('public'));
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: false, }));
app.use(cookieParser());
app.get('/', (req, res) => {
    res.render('index.html')
})
app.post('/auth/local/login', (req, res) => {
    let { userid, userpw } = req.body;
    console.log('body req: ', userid, userpw);
    //원래는 DB접속 후 결과 return 
    let result = {};
    if (userid == "asdf" && userpw == "asdf") {
        result = {
            result: true, msg: '로그인에 성공햇습니다.'
        } //여기에 token 을 생성해주면 됨 

        let token = tokenKey
        res.cookie('ccessToken', token, { httpOnly: true, secure: true, });
    } else {
        result = {
            result: false, msg: '아이디와 패스워드를 확인해주세요'
        }
    }
    res.json(result);
})
app.get('/login', (req, res) => {
    let { id, pw } = req.query;
    if (id == 'root' && pw == 'root') {
        res.cookie('token', tokenKey, { httpOnly: true, secure: true });
        res.redirect('/?msg=로그인성공');
    } else {
        res.redirect('/?msg=id와 pw가 일치하지 않습니다.');
    }
})
app.get('/menu1', (req, res) => {
    console.log(req.cookies);
    res.send('menu1페이지입니다');
})
app.listen(3000, () => { console.log('start port : 3000'); })


main.js

document.addEventListener('DOMContentLoaded', init); // 딱 한번 최초 실행 
function init() {
    const loginBtn = document.querySelector('#loginBtn');
    const layerPopup = document.querySelector('.layerPopup');
    const localLogin = document.querySelector('#localLogin');
    loginBtn.addEventListener('click', loginBtnFn)
    layerPopup.addEventListener('click', layerPopupClose);
    localLogin.addEventListener('click', localLoginFn);
}
function loginBtnFn() {
    const layerPopup = document.querySelector('.layerPopup');
    // layerPopup.setAttribute('class', 'layerPopup open'); 
    layerPopup.classList.add('open'); //오우 
}
function layerPopupClose(event) {
    if (event.target == this) {
        this.classList.remove('open');
    }
}
async function localLoginFn() {
    const userid = document.querySelector('#userid');
    const userpw = document.querySelector('#userpw');
    if (userid.value == "") {
        alert('아이디를 입력해 주세요');
        userid.focus();
        return 0; // return을 끝낸다는 의미 //return 안쓰면 if 절 밖에 fetch가 실행될거야 
    }
    if (userpw.value == "") {
        alert('패스워드를 입력해주세요.');
        userpw.focus();
        return 0;
    } //POST auth/local/login 
    let url = `http://localhost:3000/auth/local/login`;
    let options = {
        method: 'POST',
        headers: { 'content-type': `application/json`, },
        body: JSON.stringify({
            userid: userid.value, userpw: userpw.value,
        })
    }
    let response = await fetch(url, options);
    let res_result = await response.json(); //body내용 가져오기 promise객체 떨어지게 
    console.log(res_result) //res_result 안에 result, msg 라는 속성이 있음 
    let { result, msg } = res_result; alert(msg);
    if (result) {
        //로그인 성공 
        let layerPopup = document.querySelector('.layerPopup');
        layerPopup.classList.remove('open');
    } else {
        userid.value == '';
        userpw.value == '';
        userid.focus();
    }
}



'sha256' 암호 방식 -> salt = asdfasdf
저 값은 우리만의 열☆쇠



middleware 폴더 - auth.js 파일 만들기

require('dotenv').config(); 
const crypto = require('crypto'); 
const tokenKey = require('../jwt'); 
let token = tokenKey('root'); 
console.log(token);


console.log 찍을 떄 주의사항 폴더 안에 있는거라 폴더값까지 써야해



토큰에서 . 쩜을 구분으로 배열로 나눠서 담기

require('dotenv').config(); 
const crypto = require('crypto'); 
const tokenKey = require('../jwt'); 
let accessToken = tokenKey('root'); 
console.log(accessToken); 
let tokenarray = accessToken.split('.'); 
let header=tokenarray[0]; 
let payload=tokenarray[1]; 
console.log(header, payload) 


이렇게도 가능

require('dotenv').config(); 
const crypto = require('crypto'); 
const tokenKey = require('../jwt'); 
let accessToken = tokenKey('root'); 
console.log(accessToken); 
let [header,payload] = accessToken.split('.'); 
console.log(header) 
console.log(payload) 



두개의 값으로 암호화를 진행하고 기존의 signature 와 같은지 쳌


SHA256 블록체인이랑도 관련있는 암호화 방식 - 단방향 암호화
기존에 만들었던걸 검증할 때 다시 만들어서 체크를 할 수밖에 없음 - 다르면 문제잇음


auth.js

require('dotenv').config();
const crypto = require('crypto');
const tokenKey = require('../jwt');
let accessToken = tokenKey('root');
//client의 cookie.accesstoken값 
console.log(accessToken);
//let tokenarray = accessToken.split('.');
//let header=tokenarray[0]; 
//let payload=tokenarray[1]; 
let [header, payload, sign] = accessToken.split('.');
let signature = getSignature(header, payload);
console.log(signature)
if (sign == signature) {
    console.log('검증된 토큰입니다.');
} else {
    console.log('해킹이다.')
}
function getSignature(header, payload) {
    const signature = crypto.createHmac('sha256', Buffer.from(process.env.salt))
        .update(header + "." + payload)
        .digest('base64')
        .replace('=', '').replace('==', '');
    return signature;
}



console.log 해보면 '검증된 토큰입니다'


이제 다시 원래 형태로 복원하기
복원 함수가 js 는 있음
Buffer.from ()
첫번째 인자값 : payload
두번째 인자값 : 'base64'

base64인데 또 바이너리로 만든다고? -> 두번쨰 인자값 넣어 -> 바이너리 데이터로 표현됨 => String으로 바꿔주기

String -> Object로 JSON.parse 를 통해 만들기

얘를 각각 변수에 담기

if(sign==signature){ 
	console.log('검증된 토큰입니다.'); 
    let {userid,exp}=JSON.parse(Buffer.from(payload, 'base64')
    .toString()); 
    
    console.log(userid,exp) 
}else{ 
    console.log('해킹이다.') } 

 


exp : token 생성한 시간으로부터 2시간 뒤를 저장한 getTim()
요 2시간이 만료되었는지 if 조건문 넣기

require('dotenv').config();
const crypto = require('crypto');
const tokenKey = require('../jwt');
let accessToken = tokenKey('root');
//client의 cookie.accesstoken값 
console.log(accessToken);
let [header, payload, sign] = accessToken.split('.');
let signature = getSignature(header, payload);
console.log(signature)
if (sign == signature) {
    console.log('검증된 토큰입니다.');
    let { userid, exp } = JSON.parse(Buffer.from(payload, 'base64').toString());
    console.log(userid, exp)
    //시간 넘었는지 쳌 
    let newExp = new Date().getTime();
    if (newExp > exp) {
        console.log('토큰 만료되엇스빈다.');
        return 0;
    } //여기까지 다 검증되면 이제 userid를 사용해도 된다 . 
    console.log(userid);
} else {
    console.log('해킹이다.')
}
function getSignature(header, payload) {
    const signature = crypto.createHmac('sha256', Buffer.from(process.env.salt))
        .update(header + "." + payload).digest('base64')
        .replace('=', '').replace('==', ''); return signature;
}


이제 userid 사용하려면 middleware 통해서 사용
미들웨어 완료

require('dotenv').config();
const crypto = require('crypto');
const tokenKey = require('../jwt');
module.exports = (req, res, next) => {
    let { accessToken } = req.cookies; //client의 cookie.accesstoken값 
    if (accessToken == undefined) {
        res.json({ result: false, msg: '로그인을 진행해줍쇼' });
    }
    let [header, payload, sign] = accessToken.split('.');
    let signature = getSignature(header, payload);
    console.log(signature)
    if (sign == signature) {
        console.log('검증된 토큰입니다.');
        let { userid, exp } = JSON.parse(Buffer.from(payload, 'base64').toString());
        console.log(userid, exp) //시간 넘었는지 쳌 
        let newExp = new Date().getTime();
        if (newExp > exp) {
            res.redirect('/?msg=토큰만료요')
        } //모든 검증이 완료됨 - 이제 userid를 사용해도 된다 . 
        req.userid = userid;
        next();
    } else {
        res.redirect('/?msg=해킹이요');
    }
}
function getSignature(header, payload) {
    const signature = crypto.createHmac('sha256', Buffer.from(process.env.salt))
        .update(header + "." + payload)
        .digest('base64')
        .replace('=', '').replace('==', '');
    return signature;
}


server.js - 미들웨어 auth 가져오기

const auth = require('./middleware/auth')


server.js /user/info 생성 - 미들웨어 info에 넣기

app.get('/user/info',auth, (req,res)=>{ 
		res.send('user info입니다< br/> hello ${req.userid}') 
})



index.html

<body> <button id = "loginBtn">로그인</button> <a href="/user/info">회원정보 보기</a> //추가 <div class ="layerPopup"> <div class="loginPopup">


cookie값 지우기
auth.js

 if (newExp > exp) { res.clearCookie('accessToken'); res.redirect('/?msg=토큰만료요') }



res.json ({}) 부분 -> redirect로 다 바꿔줌 auth.js - 3부분




전체 코드
server.js

const express = require('express');
const cookieParser = require('cookie-parser');
const nunjucks = require('nunjucks');
const bodyParser = require('body-parser');
const tokenKey = require('./jwt')
const auth = require('./middleware/auth')
const app = express();
nunjucks.configure('views', { express: app, })
app.set('view engine', 'html');
app.use(express.static('public'));
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: false, }));
app.use(cookieParser());
app.get('/user/info', auth, (req, res) => {
    res.send(`user info입니다 hello ${req.userid}`)
})
app.get('/', (req, res) => {
    res.render('index.html')
})
app.post('/auth/local/login', (req, res) => {
    let { userid, userpw } = req.body;
    console.log('body req: ', userid, userpw); //원래는 DB접속 후 결과 return 
    let result = {};
    if (userid == "asdf" && userpw == "asdf") {
        result = { result: true, msg: '로그인에 성공햇습니다.' }
        //여기에 token 을 생성해주면 됨 
        let token = tokenKey(userid)
        res.cookie('accessToken', token, { httpOnly: true, secure: true, });
    } else {
        result = { result: false, msg: '아이디와 패스워드를 확인해주세요' }
    }
    res.json(result);
})

app.listen(3000, () => { console.log('start port : 3000'); })

jwt.js

require('dotenv').config(); const crypto = require('crypto'); const { create } = require('domain'); //JWT 토큰 header.payload.signature function createToken(userid){ let header = { "tpy":"JWT", "alg":"HS256", } //현재시간으로부터 미래시간 : 2시간을 더한 수 //두시간 뒤 재로그인 - 길면 길수록 해킹 위험 up let exp = new Date().getTime() + ((60*60*2) *1000) //1970.1.1부터 0 // 1월1일 1초지났으면 1000 let payload = { //인자로 들어온 애 userid, //expiary date exp, } ///Buffer.from - 바이너리 파일 만들기 const encodingHeader = Buffer.from(JSON.stringify(header)).toString('base64').replace('=','').replace('==',''); const encodingPayload = Buffer.from(JSON.stringify(payload)).toString('base64').replace('==','').replace('=',''); //console.log(encodingHeader, encodingPayload); //암호화 시작 //첫번째 : 암호방식 /두번째인자 : key값 (이게 공개되면 해킹됨) - best-env let signature = crypto.createHmac('sha256',Buffer.from(process.env.salt)).update(encodingHeader+"."+encodingPayload).digest('base64').replace('=','').replace('==',''); let jwt = `${encodingHeader}.${encodingPayload}.${signature}` return jwt; } let token = createToken('root'); console.log(token) //userid 값만 넣어줄 예정 //module.exports=(userid)=>{ } 아래와 같은 뜻 module.exports = createToken; 

views- index.html

<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Document</title> <link href="./css/main.css" rel = "stylesheet"> <script type="text/javascript" src="./js/main.js"></script> </head> <body> <button id = "loginBtn">로그인</button> <a href="/user/info">회원정보 보기</a> <div class ="layerPopup"> <div class="loginPopup"> <div id = "loginForm"> <h1>로그인 페이지</h1> <input type="text" name = "userid" id="userid" placeholder="아이디를 입력해주세요."> <input type="password" name="userpw" id="userpw" placeholder="패스워드를 입력해주세요."> <button id = "localLogin" class="loginSubmit" >로그인</button> <button id = "kakaoLogin" class = "loginSubmit kakao">카카오로그인</button> <p class="joinwWord">아직도 회원이 아니세요? <span class="joinBtn">회원가입</span> </p> </div> </div> </div> </body> </html>

.env

salt=vneineign

public-js - main.js

document.addEventListener('DOMContentLoaded',init); // 딱 한번 최초 실행 function init(){ const loginBtn = document.querySelector('#loginBtn'); const layerPopup=document.querySelector('.layerPopup'); const localLogin=document.querySelector('#localLogin'); loginBtn.addEventListener('click',loginBtnFn) layerPopup.addEventListener('click', layerPopupClose); localLogin.addEventListener('click',localLoginFn); } function loginBtnFn(){ const layerPopup =document.querySelector('.layerPopup'); // layerPopup.setAttribute('class', 'layerPopup open'); layerPopup.classList.add('open'); //오우 } function layerPopupClose(event){ if(event.target==this){ this.classList.remove('open'); } } async function localLoginFn(){ const userid = document.querySelector('#userid'); const userpw = document.querySelector('#userpw'); if(userid.value==""){ alert('아이디를 입력해 주세요'); userid.focus(); return 0; // return을 끝낸다는 의미 //return 안쓰면 if 절 밖에 fetch가 실행될거야 } if(userpw.value==""){ alert('패스워드를 입력해주세요.'); userpw.focus(); return 0; } //POST auth/local/login let url = `http://localhost:3000/auth/local/login`; let options={ method:'POST', headers:{ 'content-type':`application/json`, }, body:JSON.stringify({ userid:userid.value, userpw:userpw.value, }) } let response = await fetch(url,options); let res_result = await response.json(); //body내용 가져오기 promise객체 떨어지게 console.log(res_result) //res_result 안에 result, msg 라는 속성이 있음 let {result,msg} = res_result; alert(msg); if(result){ //로그인 성공 let layerPopup = document.querySelector('.layerPopup'); layerPopup.classList.remove('open'); }else{ userid.value==''; userpw.value==''; userid.focus(); } } 

public - main.css

*{ margin:0; padding:0; } .layerPopup{ display:none; position:fixed; top:0; width:100%; height:100%; background:#000000a6; } .layerPopup.open{ display:block; } .loginPopup{ width:400px; height:300px; background:white; box-sizing:border-box; padding:0 50px 50px 50px; position:absolute; left:50%; top:50%; transform: translate(-50%,-50%); } #loginForm>h1{ margin-top:32px; } #loginForm>input{ width:100%; box-sizing:border-box; padding:7px 14px; margin-top:10px; border:1px solid #666; } #loginForm>.loginSubmit{ width:100%; height:30px; border:1px solid #666; background:#fff; margin-top:10px; cursor:pointer; border-radius:15px; } #loginForm>.loginSubmit.kakao{ background:yellow; } .joinWord{ margin-top:10px; text-align: left; font-size: 12px; } .joinWord>.joinBtn{ float:right; color:blue; cursor:pointer; }

middleware-auth.js

require('dotenv').config(); const crypto = require('crypto'); const tokenKey = require('../jwt'); module.exports = (req, res, next) => { let {accessToken} = req.cookies; //client의 cookie.accesstoken값 if(accessToken==undefined){ res.redirect('/?msg=로그인을 해줍쇼'); return 0; } let [header, payload, sign] = accessToken.split('.'); let signature = getSignature(header, payload); console.log(signature) if (sign == signature) { console.log('검증된 토큰입니다.'); let { userid, exp } = JSON.parse(Buffer.from(payload, 'base64').toString()); console.log(userid, exp) //시간 넘었는지 쳌 let newExp = new Date().getTime(); if (newExp > exp) { res.clearCookie('accessToken'); res.redirect('/?msg=토큰만료요') } //모든 검증이 완료됨 - 이제 userid를 사용해도 된다 . // 이쪽에서 db 접속해서 처리 req.userid=userid; next(); } else { res.redirect('/?msg=해킹이요'); } } function getSignature(header, payload) { const signature = crypto.createHmac('sha256', Buffer.from(process.env.salt)).update(header + "." + payload).digest('base64').replace('=', '').replace('==', ''); return signature; }

 

라이브리 사용 참고&nbsp;



반응형