본문 바로가기

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

[JavaScript / Node.js] sha256 해시 알고리즘 암호화, 검증, 복호화 JWT 토큰 사용 정리

반응형

 SHA256 

- 안전한 해시 알고리즘 by NSA 

- Secure Hash Algorithm 중 하나 

- 32 바이트 또는 64 바이트 워드 사용 

- 내부 상태 크기 : 256, 블록 크기 : 512, 길이 한계 64, 워드 크기 : 32, 과정수 64, 사용연산 +, and, or, xor, shr, rotr 

 

https://ko.wikipedia.org/wiki/SHA

 

SHA - 위키백과, 우리 모두의 백과사전

위키백과, 우리 모두의 백과사전. SHA(Secure Hash Algorithm, 안전한 해시 알고리즘) 함수들은 서로 관련된 암호학적 해시 함수들의 모음이다. 이들 함수는 미국 국가안보국(NSA)이 1993년에 처음으로 설

ko.wikipedia.org

 


 

JavaScript, node.js 로 sha256 해시 알고리즘 사용하여 token생성 - 암호화, 검증, 복호화 하는 절차 정리 

-> 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

 

 

 

1. JS 기본 세팅 / 터미널 명령어 실행 / 코드 작성

$npm init

$npm i express nunjucks body-parser cookie dotenv crypto

아래와 같이 파일 생성 ↓↓

server.js 코드 작성

const express=require('express');
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.json());
app.use(bodyParser.urlencoded({extended:false,}))
app.use(express.static('public'));

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

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

views-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>Login page</title>
    <link rel="stylesheet" href="./css/main.css">
    <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="joinWord">
                    <span class="joinBtn">회원가입</span>
                </p>
            </div>
        </div>
    </div>
</body>
</html>

public - main.js (index.html 맨 처음 page loaded 되면 나오는 html과 연결된 js) 

document.addEventListener('DOMContentLoaded', init)
function init(){
    let loginBtn = document.querySelector('#loginBtn');
    loginBtn.addEventListener('click', loginFn);
}

function loginFn(){
    let layerPopup = document.querySelector('.layerPopup');
    layerPopup.classList.add('open');
    layerPopup.addEventListener('click',layerPopupFn);
}

function layerPopupFn(event){
    let layerPopup =document.querySelector('.layerPopup');
    if(event.target == this){
        layerPopup.classList.remove('open') 
    }
}

public - main.css 코드 작성

*{
    margin:0;
    padding:0;
}

.layerPopup{
    display:block;
    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;
}

 

 

 

 

 

 

2. 로그인 click -> POST로 userid, userpw BODY에 담아 보내기 (json)

server.js

const express=require('express');
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.json());
app.use(bodyParser.urlencoded({extended:false,}))
app.use(express.static('public'));

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

app.post('/auth/local/login',(req,res)=>{
    let {userid,userpw} = req.body;
    let result={
        result:false,
        msg:'아이디와 비밀번호를 확인해주세요.'
    };
    if(userid=='asdf'&&userpw=='asdf'){
        result={
            result:true,
            msg:'로그인 성공'
        }
    }
    res.json(result);    
})

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

main.js 전체 코드 

document.addEventListener('DOMContentLoaded', init)
function init(){
    const loginBtn = document.querySelector('#loginBtn');
    loginBtn.addEventListener('click', loginFn);
    const localLogin=document.querySelector('#localLogin');
    localLogin.addEventListener('click',localLoginFn);
}

function loginFn(){
    let layerPopup = document.querySelector('.layerPopup');
    layerPopup.classList.add('open');
    layerPopup.addEventListener('click',layerPopupFn);
}

function layerPopupFn(event){
    let layerPopup =document.querySelector('.layerPopup');
    if(event.target == this){
        layerPopup.classList.remove('open') 
    }
}

async function localLoginFn(){
    let userid = document.querySelector('#userid');
    let userpw = document.querySelector('#userpw');

    if(userid==''){ alert('아이디를 입력해주세요'); userid.focus(); return 0;};
    if(userpw==''){ alert('비밀번호를 입력해주세요'); userpw.focus(); return0;};

    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();
    let {result,msg} = res_result;
    alert(msg);
    console.log(response)

    if(result){
        let layerPopup=document.querySelector('.layerPopup');
        layerPopup.classList.remove('open');
    }else{
        userid.value='', 
        userpw.value='',
        userid.focus();
    }
}

 

 

 

 

 

 

3. 로그인 성공 시 sha256 암호화된 token 만들어 cookie에 암호화해서 넣기

JWT.js 파일 생성 / 코드 작성 -> id값을 받아 token 만들어 module.exports to Server.js

dotenv, crypto package 설치되어있어야함 

JWT.js 전체코드 

require('dotenv').config();
const crypto = require('crypto');

function createToken(userid) {
    let header = { "tpy": "JWT", "alg": "HS256" }
    let exp = new Date().getTime() + (1000 * 60 * 60 * 2) //지금으로부터 2시간 뒤
    let payload = { userid, exp, }

    const encodingHeader = Buffer.from(JSON.stringify(header)).toString('base64')
                            .replace('==', '').replace('=', '');

    const encodingPayload = Buffer.from(JSON.stringify(payload)).toString('base64')
                            .replace('==', '').replace('=', '');

    const 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');
module.exports=createToken;

.env 파일 생성 / random key 값 넣기

SALT=ANYTHINGYOUCANWRITEHERETOMAKEITWORKASAKEYFORHASHALGORITHM

server.js JWT.js 가져오기 / userid 넣은 token 값 res.cookie에 넣기 

const tokenKey = require('./JWT');       //추가1
const app = express();

app.post('/auth/local/login',(req,res)=>{
    let {userid,userpw} = req.body;
    let result={
        result:false,
        msg:'아이디와 비밀번호를 확인해주세요.'
    };
    if(userid=='asdf'&&userpw=='asdf'){
        let token = tokenKey(userid);            //추가 2
        res.cookie('accessToken',token,{httpOnly:true,secure:true,});   //추라3
        result={
            result:true,
            msg:'로그인 성공'
        }
    }
    res.json(result);    
})

 

 

 

 

4. TOKEN 검증 

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

auth.js 전체코드 

아래 질문에서 인자값 header,payLoad가 들어갔는데 저 function signatureCheck( 요기에는) 인자값이 없어서 

궁금합니다 아니요 저 

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

module.exports=(req,res,next)=>{
    let [header,payLoad, signature] = tokenKey('asdf').split('.');
    let signatureToCheck = signatureCheck(header,payLoad); //질문 1. 인자값 꼭 존재? 2. Fn없이는?

    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());
        let now = new Date().getTime();
        if(now>exp){
            console.log('토큰만료');
            redirect('/msg=토큰만료');
            return 0; 
        }
        req.userid=userid;
        next();
    }else{
        console.log('검증실패');
        res.redirect('/msg=해킹이요');
    }
}

server.js 코드 추가 

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

- indexhtml에서 회원정보 클릭시 토큰 검증, 토큰 만료 확인 후 req.userid=userid담아 next(); 로 계속 진행 

app.get('/user/info',auth,(req,res)=>{
    let userid = req.userid;
    res.send(`${userid}님의 회원정보는 아래와 같습니다.`)
})

server.js 전체코드 

const express=require('express');
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(bodyParser.json());
app.use(bodyParser.urlencoded({extended:false,}))
app.use(express.static('public'));

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

app.post('/auth/local/login', (req,res)=>{
    let {userid,userpw} = req.body;
    let result={
        result:false,
        msg:'아이디와 비밀번호를 확인해주세요.'
    };
    if(userid=='asdf'&&userpw=='asdf'){
        let token = tokenKey(userid);
        res.cookie('accessToken',token,{httpOnly:true,secure:true,});
        result={
            result:true,
            msg:'로그인 성공'
        }
    }
    res.json(result);    
})

app.get('/user/info',auth,(req,res)=>{
    let userid = req.userid;
    res.send(`${userid}님의 회원정보는 아래와 같습니다.`)
})

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

 

 

 

5. DB연결

회원가입 join.html, server render 코드 작성

join.html

    <form method="post" action="/user/join_success">
        ID : <input type="text" name="userid" id="userid">
        PW : <input type="passwwor" name="userpw" id="userpw">    
        NAME : <input type="text" name="username" id="username">
        <button type="submit" id="submit" >submit</button>
    </form>

server.js

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

sequelize 사용 x

$npm i mysql

server.js mysql 코드 추가 

const mysql=require('mysql')  

let connection=mysql.createConnection({
    host:'localhost',
    user:'root',
    password:'5353',
    database:'user0530'
})
connection.connect();

createTable.sql 파일 추가 / 코드 작성 

* 주의점 마지막 field 작성 후 ' , ' 콤마 금지 !! -> 에러남 

CREATE DATABASE user0530;
use user0530;

CREATE TABLE user(
    idx INT(11) NOT NULL AUTO_INCREMENT PRIMARY KEY,
    userid VARCHAR(100) NOT NULL,
    userpw VARCHAR(100) NOT NULL,
    username VARCHAR(100) NOT NULL,
    joindate DATETIME DEFAULT CURRENT_TIMESTAMP
) AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4;

파일 경로 복사 -> mysql prompt 창 열고 source [파일경로] - database, table 생성 

server.js db 추가 sql 문 작성

app.post('/user/join_success',(req,res)=>{
    let {userid,userpw,username} =req.body;
    //db에 넣기 
    let sql=`insert into user (userid,userpw,username) values ('${userid}', '${userpw}','${username}')`;
    connection.query(sql,(error,results)=>{
        if(error){
            console.log(error)
        }else{
            console.log(results);
        }
    })
    res.redirect('/');
})

console.log 확인 - > result 잘 뜨는지  / mysql prompt 에 select * from user; 확인 -> 잘 저장되었는지 

 

로그인 id,pw == DB id,pw 일치 여부 확인 + session에 userid 추가 

server.js

app.post('/auth/local/login', (req, res) => {
    let { userid, userpw } = req.body;
    let sql = `select * from user where userid='${userid}'`;
    connection.query(sql, (error, results) => {
        if (error) {
            console.log(error);
        } else {
            let DB_userid = results[0].userid;
            let DB_userpw = results[0].userpw;
            let result = {
                result: false,
                msg: '아이디와 비밀번호를 확인해주세요.'
            };
            if (userid == DB_userid && userpw == DB_userpw) {
                let token = tokenKey(userid);
                res.cookie('accessToken', token, { httpOnly: true, secure: true, });
                result = {
                    result: true,
                    msg: '로그인 성공'
                }
            }
            req.session.userid = userid;
            res.json(result)
        }
    })
})

질문 : sql 비동기로 가져온느 법? 위에 코드 let result 부터 밖으로 뺴고 싶을 때 ? 여기서 

mysql2 install & 가져오기 & connection mysql2로 변경, connect() 도 변경

 

 

$npm i express-session

server.js session관련 코드 추가

const session=require('express-session');

app.use(session({
    secret:'aa',
    resave:true,
    secure:false,
    saveUninitialized:false,
}))

'asdf'으로 임시설정된 인자값 -> login_userid 로 바꾸기  (session에 추가된 userid값) 

auth.js

module.exports=(req,res,next)=>{
    let login_userid = req.session.userid;
    let [header,payLoad, signature] = tokenKey(login_userid).split('.');
    //: 토큰을 굳이 또 만드는이유, login할 때 토큰 생성하는데 토큰을 ???

 

 

 

 

질문 : JWT.js에 exports 2개 함수도 가능한지? cryptoPW function 넣게

문제점 : 한국어로 비밀번호로 회원가입하면 -> 로그인 비밀번호 입력이 자동으로 영어로 바뀌어서 암호화 된 비번이 서로 달라 로그인이 안됨 

 

 

 

 

 

6. 비밀번호 sha256 으로 암호화해서 db저장 & 로그인 시 일치 판정

전체 server.js

const express = require('express');
const nunjucks = require('nunjucks');
const bodyParser = require('body-parser');
const tokenKey = require('./JWT');
const cryptoPW=require('./cryptoPW')
const auth = require('./middleware/auth');
const mysql = require('mysql') 
const session=require('express-session');
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(session({
    secret:'aa',
    resave:true,
    secure:false,
    saveUninitialized:false,
}))

let connection = mysql.createConnection({
    host: 'localhost',
    user: 'root',
    password: '5353',
    database: 'user0530'
})
connection.connect();

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

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

app.post('/user/join_success', (req, res) => {
    let { userid, userpw, username } = req.body;
    let cryptoUserpw = cryptoPW(userpw);
    //db에 넣기 
    let sql = `insert into user (userid,userpw,username) values ('${userid}', '${cryptoUserpw}','${username}')`;
    connection.query(sql, (error, results) => {
        if (error) {
            console.log(error)
        } else {
            console.log(results);
        }
    });
    res.redirect('/');
})

app.post('/auth/local/login', (req, res) => {
    let { userid, userpw } = req.body;
    let result = {
        result: false,
        msg: '아이디와 비밀번호를 확인해주세요.'
    };
    
    let sql = `select * from user where userid='${userid}'`;
    connection.query(sql, (error, results) => {
        if (error || results=='') {
            console.log(error);
        } else {
            let DB_userid = results[0].userid; // db
            let DB_userpw = results[0].userpw; // db
            let cryptoUserpw = cryptoPW(userpw); //고객이 입력한pw 암호화
            if (userid == DB_userid && cryptoUserpw == DB_userpw) {
                let token = tokenKey(userid);
                res.cookie('accessToken', token, { httpOnly: true, secure: true, });
                result = {
                    result: true,
                    msg: '로그인 성공'
                }
            }
        }
        req.session.userid = userid;
        res.json(result)
    })
})

app.get('/user/info', auth, (req, res) => {
    let userid = req.userid;
    res.send(`${userid}님의 회원정보는 아래와 같습니다.`)
})

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

전체 main.js

document.addEventListener('DOMContentLoaded', init)
function init(){
    const loginBtn = document.querySelector('#loginBtn');
    loginBtn.addEventListener('click', loginFn);
    const localLogin=document.querySelector('#localLogin');
    localLogin.addEventListener('click',localLoginFn);
}

function loginFn(){
    let layerPopup = document.querySelector('.layerPopup');
    layerPopup.classList.add('open');
    layerPopup.addEventListener('click',layerPopupFn);
}

function layerPopupFn(event){
    let layerPopup =document.querySelector('.layerPopup');
    if(event.target == this){
        layerPopup.classList.remove('open') 
    }
}

async function localLoginFn(){
    let userid = document.querySelector('#userid');
    let userpw = document.querySelector('#userpw');

    if(userid==''){ alert('아이디를 입력해주세요'); userid.focus(); return 0;};
    if(userpw==''){ alert('비밀번호를 입력해주세요'); userpw.focus(); return0;};

    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();
    let {result,msg} = res_result;
    alert(msg);

    if(result){
        let layerPopup=document.querySelector('.layerPopup');
        layerPopup.classList.remove('open');
        //req.session.userid=userid; 질문 왜 여기는 안되징(server.js가능)


    }else{
        userid.value='', 
        userpw.value='',
        userid.focus();
    }
}

        //-> 여기에도 session 추가 해봣는데 
        //req - express가 가지고 있는 객체 

session - server 관리 

session값을 client에게 추가하면 악용될수도 / 그렇게되면 안됨 

 

전체 cryptoPW.js

const crypto = require('crypto');

function createPW(userpw){
    const cryptoPassword = crypto.createHmac('sha256',Buffer.from(userpw).toString())
                            .digest('base64')
                            .replace('==','').replace('=','');                            

    return cryptoPassword;
}  

module.exports=createPW;

 

 

반응형