▼로그인을 위한
server.js
전체 코드▼코드복사는 아래 코드로 해주세요! 나중에 설명할때 나오는 코드는 모듈이 포함되어있지 않습니다!
만약 에러가 난다면 해당 모듈을npm
명령어를 통해 설치해주시고import
해주세요
const express = require('express');
const app = express();
app.use(express.static(__dirname + ''));
// allows you to ejs view engine.
app.set('view engine', 'ejs');
// importing .env file
require('dotenv').config();
// importing jsonwebtoken module.
const jwt = require("jsonwebtoken");
// importing body-parser to create bodyParser object
const bodyParser = require('body-parser');
// allows you to use req.body var when you use http post method.
app.use(bodyParser.urlencoded({ extended: true }));
const cookieParser = require('cookie-parser');
app.use(cookieParser());
// importing db function that connects with MongoDB.
const { db } = require('./module/db');
// importing auth function
const { auth } = require('./module/authMiddleware');
// importing user schema.
const User = require('./module/user');
// importing bcrypt moudle to encrypt user password.
const bcrypt = require('bcrypt');
// declaring saltRounds to decide cost factor of salt function.
const saltRounds = 10;
// To use python script
var PythonShell = require('python-shell');
db();
app.get('/login', auth, function(req, res) {
const user = req.decoded;
if(user) {
return res.render('loggedin', {user:user.docs});
} else {
return res.sendFile(__dirname + '/login.html');
}
});
app.post('/login/:signInid/:signInpw', function(req, res, next) {
console.log('req.body: ', req.body);
let user = new User(req.body);
User.findOne({id:(user.id)}, function(err, docs) {
if(err) throw err;
else if(docs == null) { // Entered ID does not exist.
return console.log('Entered ID does not exist.');
} else { // when entered ID matches.
bcrypt.compare(user.pw, docs.pw, function (err, answer) {
if (err) throw err;
if(answer) {
req.user = docs;
return next();
} else {
return res.send('Your password does not match with your ID.');
}
})
}
});
});
app.post('/login/:signInid/:signInpw', function(req, res) {
const docs = req.user;
const payload = { // putting data into a payload
docs,
};
// generating json web token and sending it
jwt.sign(
payload, // payload into jwt.sign method
process.env.SECRET_KEY, // secret key value
{ expiresIn: "30m" }, // token expiration time
(err, token) => {
if (err) throw err;
else {
return res
.cookie('user', token,{maxAge:30*60 * 1000}) // 1000 is a sec
.end();
}
});
});
처음 유저가
/login
경로에 들어왔을때, 유저는 아래와 같은 화면을 마주쳐야한다.
그리고 유저가 로그인에 성공했다면 아래와 같은 화면을 마주쳐야한다.
유저 로그인이 유지가 되도록
JWT
토큰을 사용하면/
경로에 가더라도 아래처럼 로그인 유지가 가능하다.
아래는
/
경로에서 로그인 되어있지 않는 경우이다.
아래처럼 서버에서
/login
경로로 응답을 보낼때 로그인된 경우와 되지 않은 경우를 나눠서 보내야 하므로user
값이 있는지와 없는지를 확인후 없다면 평소처럼login.html
파일을, 있다면 매번 달라지는 유저 정보와 함께 응답해야한다. 이때ejs
가 필요하다.
user
값이 있는지 확인하기 위해req.decoded
를 불러오는 과정은 좀 더 아래에서 나온다.
// allows you to ejs view engine.
app.set('view engine', 'ejs');
app.get('/login', auth, function(req, res) {
const user = req.decoded;
if(user) {
// 왼쪽 user는 ejs템플릿 파일에서 사용되어질 변수 이름
// 오른쪽 user.docs는 템플릿에 실제 넣을 데이터값.(이 값에 유저정보 있음).
return res.render('loggedin', {user:user.docs});
} else {
return res.sendFile(__dirname + '/login.html');
}
});
ejs
는 아래 명령어를 통해 설치할 수 있다.
npm install ejs
ejs
를 사용할땐html
파일이 아닌ejs
확장자를 가진 파일을 따로 사용해야한다.
그래서login.html
파일을 복사후views
폴더에loggedin.ejs
라는 이름으로 복사해줬다.
views
폴더를 만들고 따로 관리하는 이유는 파일이 모일수록 번거롭기 때문이다. (별거없음)
ejs
파일은 이런식으로 사용할 수 있다.
<!-- loggedin.ejs -->
<!-- 이런식으로 서버에서 보낸 매번 달라지는 데이터가 자동으로 파일에 들어간다. -->
<h1><%= user.id %>! Welcome<br>
Your address: <%= user.address %>. <br>
You are: logged in. <br>
This is your private page! <br>
</h1>
로그인을 구현할 때 json web token 이라는 것을 발급해준다.
이유는http
요청을 할때마다 매번 유저정보를 서버와 클라이언트 사이에서 교환하면 보안적으로 좋지 않고user DB
에서 매번 검색하면 생각보다 이 작업도 무겁기 때문이다.
그래서 토큰이라는것을 발급해주는데 처음 로그인에 성공했을 경우 토큰을 발급해준다.
▼JWT토큰을 사용한 로그인 절차▼
1. 클라이언트에서 처음에 로그인 정보를 입력해 로그인시도
2. 서버에서bcrypt
모듈을 이용해 로그인정보와user DB
에 있는 암호화된 비밀번호 비교
3. 로그인 성공후 서버에서토큰
발급
4. 발급한토큰
을 쿠키에 저장해서 클라이언트에 응답.
5. 유저가 로그인이 계속 필요한 부분에서http
요청
6. 서버에서쿠키
에 저장되있는토큰
을 확인해user ID
사용가능,user DB
에 있는 정보와 일치하는지 권한 확인
7. 권한이 충분하다면, 5번의 요청에 대한 응답
아래는 클라이언트에서 로그인정보를 입력하고 서버에 데이터값을 보냈을때의
onclick
함수이다.
</script>
function signInAjax() {
const signinID = document.getElementById("signinID").value;
const signinPW = document.getElementById("signinPW").value;
document.getElementById("signinID").value = "";
document.getElementById("signinPW").value = "";
$.ajax({
type: "post",
url: 'http://localhost:8080/login/:signInid/:signInpw',
data: {id:signinID,pw:signinPW},
dataType:'text',
success: function(res) {
location.reload();
}
});
}
</script>
아래는 요청받은 정보를 이용해 유저를 검색하고 있다면
user DB
에 암호화로 저장되어진 비밀번호와 입력받은 비밀번호를bcrypt
모듈을 이용해 비교하는 미들웨어다.
app.post('/login/:signInid/:signInpw', function(req, res, next) {
// initializing user variable using schema
let user = new User(req.body);
User.findOne({id:(user.id)}, function(err, docs) {
if(err) throw err;
else if(docs == null) { // Entered ID does not exist.
return console.log('Entered ID does not exist.');
} else { // when entered ID matches.
// user.pw -> user entered password and docs.pw -> encrypted user DB password
bcrypt.compare(user.pw, docs.pw, function (err, answer) {
if (err) throw err;
if(answer) {
// req.user variable can be used in next middleware
req.user = docs;
return next();
} else {
return res.send('Your password does not match with your ID.');
}
})
}
});
});
비밀번호가 일치해서 유저 확인이 되었다면 next()메소드를 통해 다음 미들웨어로 보내준다.
아래 미들웨어에서는JWT
를 발급하고 쿠키로 저장한다.
process.env.SECRET_KEY
는
.env
파일에SECRET_KEY = 'jwtSecret';
를 추가해줬다. 보안에 중요한 파일이므로 따로 보관해준다.
.env
파일이 있는 경로에서 아래 터미널 명령어 사용 또는SECRET_KEY = 'jwtSecret';
복붙.
echo >> .env "SECRET_KEY = 'jwtSecret';"
토큰 발행후
user
라는 이름으로 쿠키에 저장해 클라이언트에 응답해준다.
토큰이 유효기한과 쿠키의 유효기한은 30분으로 같게 설정해줬다. 추가로 쿠키에서의 숫자단위는 millisecond이므로 1/1000초다. 따라서 1000 = 1초.
나중에 쿠키를 없애는 방법으로 로그아웃도 구현할 수 있다.
// importing .env file
require('dotenv').config();
app.post('/login/:signInid/:signInpw', function(req, res) {
const docs = req.user;
const payload = { // putting data into a payload
docs,
};
// generating json web token and sending it
jwt.sign(
payload, // payload into jwt.sign method
process.env.SECRET_KEY, // secret key value
{ expiresIn: "30m" }, // token expiration time
(err, token) => {
if (err) throw err;
else {
return res
.cookie('user', token,{maxAge:30*60 * 1000}) // 1000 is a sec
.end();
}
});
});
아래에서 쿠키가 브라우저에 저장됨을 확인할 수 있다.
토큰을 발급받았으면
jwt
모듈의auth
메소드를 이용해 토큰을 확인할 수 있다.
authMiddleware.js
파일을 새로 만들어주고 아래와 같이 만들었다.
생각보다 별거 없이jwt.verify()
함수에 쿠키값과 비밀키만 넣으면 된다.
나머지는 에러처리.
▼authMiddleware.js
파일 전체코드▼
const jwt = require('jsonwebtoken');
const path = require('path');
// importing .env file
require('dotenv').config({ path: path.resolve(__dirname, '../.env') });
exports.auth = (req, res, next) => {
// 인증 완료
try {
// 요청 헤더에 저장된 토큰(req.headers.authorization)과 비밀키를 사용하여 토큰을 req.decoded에 반환
req.decoded = jwt.verify(req.cookies.user, process.env.SECRET_KEY);
return next();
}
// 인증 실패
catch (error) {
// Token has been expired
if (error.name === 'TokenExpiredError') {
console.log('auth TokenExpiredError');
next();
// return res.status(419).json({
// code: 419,
// message: 'Token has been expired.'
// });
}
// JsonWebTokenError
if (error.name === 'JsonWebTokenError') {
console.log('JsonWebTokenError');
next();
// return res.status(401).json({
// code: 401,
// message: 'Invalid token.'
// });
}
}
}
auth
함수로 가져와 준 다음 필요한 미들웨어에 그냥 아래처럼 넣으면 사용되어진다.
auth
함수가 브라우저의 쿠키를 확인해 유저를 확인한 다음user
변수에다가 결과를 리턴한다.
user
가 있다면 그정보를 바탕으로ejs
파일과 함께 클라이언트에 응답할것이고
user
가 없다면 비어있는 값이므로html
파일만 클라이언트에 응답한다.
// importing auth file
const { auth } = require('./module/authMiddleware');
app.get('/login', auth, function(req, res) {
const user = req.decoded;
if(user) {
return res.render('loggedin', {user:user.docs});
} else {
return res.sendFile(__dirname + '/login.html');
}
});
위 코드처럼 처음 로그인에 성공해서 토큰만 가지고있다면 원하는 경로에
auth
함수를 집어넣어 토큰 확인을 통해 유저 권한이 필요한 부분에 적용할수 있다. 아이디에 맞춰서 주소값이 변하는걸 확인할 수 있다.
▼최종 결과물▼