
▼로그인을 위한
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함수를 집어넣어 토큰 확인을 통해 유저 권한이 필요한 부분에 적용할수 있다. 아이디에 맞춰서 주소값이 변하는걸 확인할 수 있다.
▼최종 결과물▼
