본문 바로가기

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

[53일차 복습] JWT 예제 토큰 sha256 암호화, 복호화, 검증하기 with JavaScript, Node.js

반응형

처음부터 다시 

 

1. 기본 세팅 

$npm init 

$npm i express nunjucks body-parser cookie cookie-parser (crypto 추후 추가) 

 

server.js 와 파일 구조

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 rel ="stylesheet" href="./css/main.css">
    <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="패스워드를 입력해주세요">
                <buton id = "localLogin" class="loginSubmit">로그인</buton>
                <button id = "kakaoLogin" class="loginSubmit kakao">카카오로그인</button>
                <p class="joinWord">
                    <span class="joinBtb">회원가입</span>
                </p>
            </div>
        </div>
    </div>
</body>
</html>

 css, js 잘 연결되었는지 확인 

-> layerPopup 안보이게 하고 또 다른 class 명 추가해서 open 

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

 

 

2. 처음 load 되었을 때 함수 init 실행되게 하기 -> 로그인 버튼 누르면 layerPopup 뜨게 하기 

document.addEventListener('DOMContentLoaded', init);
function init(){
    const loginBtn=document.querySelector('#loginBtn');
    loginBtn.addEventListener('click',loginBtnFn);
}
function loginBtnFn(){
    const layerPopup=document.querySelector('.layerPopup');
    layerPopup.classList.add('open');    
}

 

 

3. layerPopup 클릭하면 닫히게 만들기 (로그인 페이지를 눌렀을 때는 안닫히게) 

 

방법 1 (방법2 추천) 

function layerPopupClose(event){
    const layerPopup = document.querySelector('.layerPopup')
    const loginPopup=document.querySelector('.loginPopup')
    console.log(event)
    console.log(this)
    if(event.target!=loginPopup){
        layerPopup.classList.remove('open');
    }
}

방법 2

function layerPopupClose(event){
    console.log(event)
    console.log(this)
    if(event.target==this){
        this.classList.remove('open');
    }
}

 

4. 로그인 버튼 눌렀을 떄 서버에 fetch로 요청하기 BODY 방법 2가지 

(현재 하고 있는 코드와는 별개) 질문 : form action body 보내는 것과 지금 버튼click event 로 보내는 방법 어떻게 다른지

이전까지 Req post로 보낼 때 form action='/login' 으로 하는 코드를 많이 썼는데 이번에 처음으로 click 해서 body를 보내보는 걸 함 . 그 차이점은 ↓↓↓

1. click event => 우리가 직접 http header body 작성 

2, form tag 사용 => form이 자동으로 ( application:x-www-form-~ 요거 사용해서) body 내용 보내줌 

 

BODY POST로 보내는 방법 2가지 중 첫번째 (직접 click event로 header,body 작성하는 방법 중 첫번째 ) 

main.js

document.addEventListener('DOMContentLoaded', init);
function init(){
    const loginBtn=document.querySelector('#loginBtn');
    loginBtn.addEventListener('click',loginBtnFn);
    const layerPopup = document.querySelector('.layerPopup')
    layerPopup.addEventListener('click',layerPopupClose);
    const localLogin=document.querySelector('#localLogin');
    localLogin.addEventListener('click',localLoginFn);
}
function loginBtnFn(){
    const layerPopup=document.querySelector('.layerPopup');
    layerPopup.classList.add('open');    
}
function layerPopupClose(event){
    if(event.target==this){
        this.classList.remove('open');
    }
}
async function localLoginFn(){
    let userid = document.querySelector('#userid');
    let userpw = document.querySelector('#userpw');

    if(userid.value==''){
        alert('아이디를 입력해주세요');
        userid.focus();
        return 0;
    }
    if(userpw.value==''){
        alert('패스워드를 입력해주세요');
        userpw.focus();
        return 0;
    }

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

/auth/local/login 부분 server.js에 추가 Post 니까 bodyPaser 다운, 코드 추가

$body-parser

server.js

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

nunjucks.configure('views',{
    express:app,
})
app.set('view engine', 'html');
app.use(bodyParser.urlencoded({extended:false}));
app.use(express.static('public'));
//app.use(cookieParser());

app.get('/', (req,res)=>{
    res.render('index.html');
})

app.post('/auth/local/login',(req,res)=>{
    let {userid,userpw} = req.body;
    res.json({
        result:true,
        msg:'로그인 성공했습니다.'
    })

})

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

응답 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);

content-type: applicaition/json이면 -> body: JSON.stringfy({}) 로 ! 

 

 

터미널 console.log = undefined -> 미들웨어 app.user(Parser.json()); 을 놔서 json 을 받을 수 있게 만들기 

app.use(bodyParser.json());

요코드 하나만 넣으면 server on -> console.log잘 찍힘 

 

 

첫번째 방법 : QueryString 으로 보냄 'content-type' : 'application/x-www-form-urlencoded'

두번째 방법:  Json 으로 보냄 'content-type': 'application/json' 

- bodyParsor.json() 필요

 

 

 

EXTRA ) JSON 형태로 보낼 때 body말고 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,
            })
        },
    }
    let response = await fetch (url,options);
    console.log(response);

server.js

app.post('/auth/local/login',(req,res)=>{
    let {userid,userpw} = JSON.parse(req.get('data'));
    console.log(userid,userpw)
    res.json({
        result:true,
        msg:'로그인 성공했습니다.'
    })
})

 

body로 보낼 때는 app.post받을 때 JSON을 안써줬어도 되었다 (server.js 에 app.use(bodyParser()) 가 있어서 그런 것 같다. 여기서는 body로 보낸게 아니고 headers에 넣은거라 JSON.stringify 를 써서 json으로 보낸걸 받을 때 JSON.parse로 파싱함  req.query or req.body 가 아니고 --> req.get('data') 'data'라는 key값에 담긴걸 get 하겠다는 의미같음 

 

 

-> 다시 post로 돌려놓기 

지금까지 로그인 누르면 아이디,비번이 vs터미널에 나오게 만듬 

DB는 아직 연결 x 먼저 

 

5. 로그인 성공여부 만들기 

server.js

app.post('/auth/local/login',(req,res)=>{
    let {userid,userpw} = req.body;
    console.log('body req : ' ,userid, userpw)
    let result = {
        result:false,
        msg:'아이디와 패스워드를 확인해주세요'
    }
    if(userid =='root'&&userpw=='root'){
        result = {
            result:true,
            msg:'로그인 성공 '
        }
    }
    res.json(result)
})

main.js

    let response = await fetch (url,options);
    let res_result = await response.json();
    console.log(res_result);

 

 

6. 로그인 성공 시 token 생성하기 

.$npm i crypto dotenv

.env 파일 생성, salt라는 변수에 아무string 적기 (암호) 

salt=asdfasdf

jwt.js 파일 생성, 코드 작성 -> console.log에 찍힌 token값 JWT 사이트가서 확인 if verified ->  module exports 

require('dotenv').config();
const crypto = require('crypto');
//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('asdf');
console.log(token)
//userid 값만 넣어줄 예정 
//module.exports=(userid)=>{ } 아래와 같은 뜻 
module.exports = createToken;

server.js에서 JWT 토큰 키 가져오기

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(bodyParser.json());
app.use(bodyParser.urlencoded({extended:false}));
app.use(express.static('public'));
//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접속 후 결과 리턴
    let result = {};
    if(userid =='asdf'&&userpw=='asdf'){
        result = {
            result:true,
            msg:'로그인 성공 '
        }
        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('server start port: 3000');
})

 

 

 

 

 

7. TOKEN검증 (생성된 token과 내가 만든 token 이 일치하는지)

middleware 폴더 생성 -> auth.js 파일 생성, 코드 작성

require('dotenv').config();
const crypto = require('crypto');
const tokenKey=require('../jwt');

let accessToken = tokenKey('asdf');
console.log(accessToken)

console.log 찍기  ** 주의 폴더 안에 server이기 때문에 폴더 경로까지 쓰기

node .\middleware\auth.js

console창에 key 뜨는거 확인 

 

jwt.js 에서 만든 token 값이 잘 넘겨왔는지 확인했으면 받은 token값을 .을 기준으로 split, 배열에 담기 

let accessToken = tokenKey('asdf');
let accessToken_arr=accessToken.split('.');
let header = accessToken_arr[0];
let payload = accessToken_arr[1];

변수 여러개 한번에 담기↓↓

require('dotenv').config();
const crypto = require('crypto');
const tokenKey=require('../jwt');

let accessToken = tokenKey('asdf');
let [header, payload, signature]=accessToken.split('.');

검증 / 실패 만들기

require('dotenv').config();
const crypto = require('crypto');
const tokenKey=require('../jwt');

let accessToken = tokenKey('asdf');
let [header, payload, signature]=accessToken.split('.');
let signatureToCheck = signatureCheck(); 질문 1/ 인자값 꼭 있어야하는지-> ㄴㄴ! 2. fn 없애기가능?

if(signature==signatureToCheck){
    console.log('검증')
}else{
    console.log('실패')
}
function signatureCheck(){
    const signatureChecked = crypto.createHmac('sha256',Buffer.from(process.env.salt))
                        .update(header+'.'+payload)
                        .digest('base64')
                        .replace('==','=').replace('=','');
    return signatureChecked
}

 

 

8. TOKEN 원래의 형태로 다시 복원하기 

JS에 복원함수 있음 -> JSON.parse( Buffer.from(payload, 'base64') ~

payloade 안에 userid, exp 가 있어서 payload를 바꿔서 안에 있는 userid,exp 값 변수에 담기

require('dotenv').config();
const crypto = require('crypto');
const tokenKey=require('../jwt');

let accessToken = tokenKey('asdf');
let [header, payload, signature]=accessToken.split('.');
let signatureToCheck = signatureCheck(header,payload);

function signatureCheck(){
    const signatureChecked = crypto.createHmac('sha256',Buffer.from(process.env.salt))
                        .update(header+'.'+payload)
                        .digest('base64')
                        .replace('==','=').replace('=','');
    return signatureChecked
}

if(signature==signatureToCheck){
    console.log('검증')
    let {userid,exp}=JSON.parse(Buffer.from(payload,'base64').toString());
    console.log(userid, exp)
    req.userid = userid;
}else{
    console.log('실패')
}

 

 

9. token 생성 이후 2시간 지났는지 if 조건문 만들기 

    if (signature == signatureToCheck) {
        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('토큰 만료');
            redirect('/msg=토큰만료')
            return 0;  //안써주면 안되더라구..
        }
        req.userid=userid; // 질문
        next(); //질문ok next() ? 여기에 쓴 이유 아래 더이상 실행할게 없는데
        //module.exports 되고 그 뒤에 명령 계속 실행해라 ~ 같음 (in server.js-/auth/local/login) 
    } else {
        console.log('실패')
        res.redirect('/msg=해킹이다')
    }

 

 

 

10. exports  하기 ->userid 사용하려면 middleware 통해 사용

질문 : module.exports를 끝에 한 줄로 쓰기 ? 

auth.js

module.exports할 때 언제는 맨 밑에 쓰고 언제는 요렇게  =()=>{}ㄴ

하나만 쓸 때는 아래처럼 한번에 처리

두개 이상이면 아래다가 쓰는 편이 좋음 (아래 예시 有) 

 

 

 

require('dotenv').config();
const crypto = require('crypto');
const tokenKey = require('../jwt');

module.exports = (req, res, next) => { 질문 // 어쩔떄는 맨 아래 exports / 여기는 조금 다른 이유?
    let accessToken = tokenKey('asdf');
    let [header, payload, signature] = accessToken.split('.');
    let signatureToCheck = signatureCheck(header, payload);

    function signatureCheck() {
        const signatureChecked = crypto.createHmac('sha256', Buffer.from(process.env.salt))
            .update(header + '.' + payload)
            .digest('base64')
            .replace('==', '=').replace('=', '');
        return signatureChecked
    }

    if (signature == signatureToCheck) {
        console.log('검증')
        let { userid, exp } = JSON.parse(Buffer.from(payload, 'base64').toString());
        //질문 - 위의 코드  toString('base64')와 어떻게 다른지
        console.log(userid, exp)
        let newExp = new Date().getTime();
        if (newExp > exp) {
            console.log('토큰 만료');
            redirect('/msg=토큰만료')
            return 0;
        }
        req.userid=userid;
        next();
    } else {
        console.log('실패')
        res.redirect('/msg=해킹이다')
    }
}

위에 module.exports(req,res,next)=>{전체 내용} 요방법도 있고 아래처럼 aa 라는 이름의 function을 exports 해도됨

require('dotenv').config();
const crypto = require('crypto');
const tokenKey = require('../jwt');

function aa (req, res, next) { 질문 // 어쩔떄는 맨 아래 exports / 여기는 조금 다른 이유?
    let accessToken = tokenKey('asdf');
    let [header, payload, signature] = accessToken.split('.');
    let signatureToCheck = signatureCheck(header, payload);

    function signatureCheck() {
        const signatureChecked = crypto.createHmac('sha256', Buffer.from(process.env.salt))
            .update(header + '.' + payload)
            .digest('base64')
            .replace('==', '=').replace('=', '');
        return signatureChecked
    }

    if (signature == signatureToCheck) {
        console.log('검증')
        let { userid, exp } = JSON.parse(Buffer.from(payload, 'base64').toString());
        //질문 - 위의 코드  toString('base64')와 어떻게 다른지
        console.log(userid, exp)
        let newExp = new Date().getTime();
        if (newExp > exp) {
            console.log('토큰 만료');
            redirect('/msg=토큰만료')
            return 0;
        }
        req.userid=userid;
        next();
    } else {
        console.log('실패')
        res.redirect('/msg=해킹이다')
    }
}
module.exports=aa;

위 질문 답변 : let { userid, exp } = JSON.parse(Buffer.from(payload, 'base64').toString());
        //질문 - 위의 코드  toString('base64')와 어떻게 다른지
        // JWT 토큰 값 = accessToken (base64)로이미 변환된 글자

payload = base64 로 변환되어 있음 .toString() 원래 글자로 바꾼다. (디코딩) string을 객체로 바꿈 

그럼 저기 (payload,'base64').toString()  toSTring --> base64로 되어있는 문자열을--> b** console.log쳐보기 

 

2 -> 16진수 연동잘됨

16 -> 64진수 변환 쉽다 

64진수 좋은 점 

10진수 ex) 10 == 0010 4칸 사용 

16진수 ex) 10 == A 1칸 사용

64진수 -> 10진수의 글자들을 또 함축 가능 

=>변환쉽고, 글자수도 줄여져서 , 특히 대량일 때. 

 

JSON.parse -> json 객체로 바꿔주는거 

일반 stirng -> base64 - 인코딩

base63 -> stirng - 디코딩

 

암호화, 복호화 방법, 순서 ↓↓

https://blckchainetc.tistory.com/207

 

[JavaScript] .crypto.createHmac 'sha256' 해시 알고리즘 암호화, 복호화 / 자바스크립트, node.js, JWT 토큰

JavaScript Node.js | crypto.createHmac() crypto.createHmac() 매서드는 Hmac 객체를 생성하는데 사용된다. 기본 구조 crypto.createHmac(algorithm, key, options) 첫 번째 인자값 : 알고리즘 방식 ex) sha256,..

blckchainetc.tistory.com

 

 

 

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

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

index.html 회원정보 a 추가

    <button id ="loginBtn">로그인</button>
    <a href="/user/info">회원정보 보기</a>

server.js 에 /user/info 추가 

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

 

반응형