SHA256
- 안전한 해시 알고리즘 by NSA
- Secure Hash Algorithm 중 하나
- 32 바이트 또는 64 바이트 워드 사용
- 내부 상태 크기 : 256, 블록 크기 : 512, 길이 한계 64, 워드 크기 : 32, 과정수 64, 사용연산 +, and, or, xor, shr, rotr
https://ko.wikipedia.org/wiki/SHA
JavaScript, node.js 로 sha256 해시 알고리즘 사용하여 token생성 - 암호화, 검증, 복호화 하는 절차 정리
-> https://blckchainetc.tistory.com/207
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;