2020. 05. 23 수정
이전 글에서 회원 가입을 할 때 단방향 암호화 방식을 이용하여 회원의 비밀번호를 암호화 하는 방법에 대해 말씀드렸습니다.
로그인을 할 때는 회원가입 할 때 적용한 같은 방식의 암호화 과정을 거치고, DB에 저장된 암호화된 비밀번호와 비교하여 사용자 인증을 해야 합니다.
이번 글에서는 암호화된 비밀번호로 로그인을 처리하는 방법과 상태를 유지하는 쿠키와 세션에 대해 알아보겠습니다.
전체 소스는 깃헙을 참고해주시길 바랍니다.
1. 로그인 form 및 라우터 함수 등록
먼저 로그인 뷰 페이지를 작성합니다.
/views/user/login.ejs
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Document</title>
</head>
<body>
<h3>로그인</h3>
<form action="/user/login" method="post">
<table>
<tr>
<td>이메일 : </td>
<td><input type="text" name="userEmail"></td>
</tr>
<tr>
<td>비번 : </td>
<td><input type="password" name="password"></td>
</tr>
<tr>
<td><input type="submit" value="로그인"></td>
</tr>
</table>
</form>
</body>
</html>
다음으로 로그인을 처리하는 라우터 미들웨어를 작성합니다.
/routes/users.js
const express = require('express');
const crypto = require('crypto');
const router = express.Router();
const models = require("../models");
router.get('/sign_up', function(req, res, next) {
res.render("user/signup");
});
router.post("/sign_up", async function(req,res,next){
let body = req.body;
let inputPassword = body.password;
let salt = Math.round((new Date().valueOf() * Math.random())) + "";
let hashPassword = crypto.createHash("sha512").update(inputPassword + salt).digest("hex");
let result = models.user.create({
name: body.userName,
email: body.userEmail,
password: hashPassword,
salt: salt
})
res.redirect("/user/sign_up");
})
// 메인 페이지
router.get('/', function(req, res, next) {
res.send('환영합니다~');
});
// 로그인 GET
router.get('/login', function(req, res, next) {
res.render("user/login");
});
// 로그인 POST
router.post("/login", async function(req,res,next){
let body = req.body;
let result = await models.user.findOne({
where: {
email : body.userEmail
}
});
let dbPassword = result.dataValues.password;
let inputPassword = body.password;
let salt = result.dataValues.salt;
let hashPassword = crypto.createHash("sha512").update(inputPassword + salt).digest("hex");
if(dbPassword === hashPassword){
console.log("비밀번호 일치");
res.redirect("/user");
}
else{
console.log("비밀번호 불일치");
res.redirect("/user/login");
}
});
module.exports = router;
- 로그인에 성공하면 "환영합니다 ~"메시지를 보여줍니다.
- 로그인에 실패하면 다시 로그인을 하라는 의미로 redirect 하여 login 페이지를 보여줍니다.
login을 처리하는 라우터 함수를 자세히 보도록 하겠습니다.
- find() 메서드를 통해 사용자가 입력한 아이디에 해당하는 데이터를 조회합니다.
- 비밀번호는 result.dataValues.password를 통해 얻을 수 있습니다.
- 사용자가 입력한 비밀번호를 암호화 하는 방식이 회원가입 할 때랑 다릅니다.
- salt를 무작위로 생성하는 것이 아니라 DB에 저장된 값을 가져온다는 것입니다.
- 그 이유는 암호화를 할 때 사용한 salt 값을 사용자 인증할 때도 똑같이 사용해야 하기 때문입니다.
- 즉, 사용자가 입력한 평문을 같은 방식으로 암호화 한 값( hashPassword )과 DB에 저장된 값( dbPassword )을 비교합니다.
이제 http://localhost:3000/users/login에 접근한 뒤, 로그인이 잘 되는지 테스트 해보시기 바랍니다.
2. 쿠키를 이용한 로그인 상태 유지
지금 상태는 로그인을 통해 사용자 인증만 했을 뿐, 로그인 상태를 유지하고 있지는 않습니다.
이제 로그인 상태를 유지하는 쿠키와 세션에 대해 알아보도록 하겠습니다.
( 쿠키와 세션에 대한 내용은 여기를 참고해주세요 ! )
쿠키를 사용하기 위해서는 cookie-parser 모듈을 사용합니다.
그런데 이 모듈은 express-generator로 프로젝트를 만들 때 이미 설치 및 미들웨어로 등록했기 때문에, 요청 객체 req에 cookies 속성이 추가되고, 응답 객체 res에서 cookie() 메서드를 호출할 수 있습니다.
아래 사진은 cookie() 메서드에서 사용할 수 있는 옵션들입니다.
우선 login을 처리하는 기존 라우터 함수에 쿠키 설정 부분을 추가적으로 작성하도록 하겠습니다.
/routes/users.js
...
// 메인 페이지
router.get('/', function(req, res, next) {
if(req.cookies){
console.log(req.cookies);
}
res.send("환영합니다 ~");
});
...
// 로그인 POST
router.post("/login", async function(req,res,next){
let body = req.body;
let result = await models.user.findOne({
where: {
email : body.userEmail
}
});
let dbPassword = result.dataValues.password;
let inputPassword = body.password;
let salt = result.dataValues.salt;
let hashPassword = crypto.createHash("sha512").update(inputPassword + salt).digest("hex");
if(dbPassword === hashPassword){
console.log("비밀번호 일치");
// 쿠키 설정
res.cookie("user", body.userEmail , {
expires: new Date(Date.now() + 900000),
httpOnly: true
});
res.redirect("/user");
}
else{
console.log("비밀번호 불일치");
res.redirect("/user/login");
}
});
비밀번호가 일치했을 때, 쿠키를 설정할 수 있도록 했습니다.
쿠키를 설정하는 cookie() 메서드를 호출할 때,
- 첫 번째 인자는 쿠키의 이름,
- 예제에서는 user라는 이름을 작성했습니다.
- 두 번째 인자는 쿠키의 값,
- 예제에서는 사용자의 이메일을 작성했습니다.
- 세 번째 인자는 옵션
- 단위는 ms입니다. ( 1일 == 60 * 60 * 24 초 )
- 예제에서는 시간을 기준으로 24시간으로 설정했습니다.
- 웹 서버에서만 접근할 수 있도록 쿠키에 플래그를 지정하는 httpOnly 옵션을 추가했습니다.
이제 서버를 실행하여, 로그인을 해보시기 바랍니다.
로그인에 성공하면 콘솔창에서 쿠키를 확인할 수 있습니다.
브라우저에서도 쿠키를 확인할 수도 있습니다.
크롬 브라우저를 기준으로 "개발자도구 -> 상단의 Application -> 좌측의 Storage -> Cookies"에서 확인이 가능합니다.
3. 세션을 이용한 로그인 상태 유지
이번에는 쿠키 말고, 세션을 사용하여 로그인 상태를 유지해보도록 하겠습니다.
Node.js에서 세션을 사용하려면 외부 모듈인 express-session 모듈을 설치해야 합니다.
( express-session 깃헙은 여기를 참고해주세요. )
# npm install --save express-session
그리고 세션을 사용하려면 cookie-parser 모듈도 같이 사용해야 합니다.
세션이 원래 쿠키를 이용하기 때문이죠.
세션에 대한 미들웨어를 설정하기 위해, app.js 파일을 다음과 같이 수정하겠습니다.
...
const session = require('express-session');
...
app.use(session({
key: 'sid',
secret: 'secret',
resave: false,
saveUninitialized: true,
cookie: {
maxAge: 24000 * 60 * 60 // 쿠키 유효기간 24시간
}
}));
...
해당 session 코드는 app.js 파일의 적당한 상단에 위치해야 에러없이 동작됩니다.
- key
- 세션의 키 값
- secret
- 세션의 비밀 키, 쿠키값의 변조를 막기 위해서 이 값을 통해 세션을 암호화 하여 저장
- resave
- 세션을 항상 저장할 지 여부 (false를 권장)
- saveUninitialized
- 세션이 저장되기전에 uninitialize 상태로 만들어 저장
- cookie
- 쿠키 설정
이어서 로그인 할 때, 처리하는 라우터 함수에 세션을 사용해보기로 하겠습니다.
기존에 작성되어 있던 쿠키 설정 부분을 수정합니다.
/routes/users.js
// 로그인 GET
router.get('/login', function(req, res, next) {
let session = req.session;
res.render("user/login", {
session : session
});
});
// 로그인 POST
router.post("/login", async function(req,res,next){
let body = req.body;
let result = await models.user.findOne({
where: {
email : body.userEmail
}
});
let dbPassword = result.dataValues.password;
let inputPassword = body.password;
let salt = result.dataValues.salt;
let hashPassword = crypto.createHash("sha512").update(inputPassword + salt).digest("hex");
if(dbPassword === hashPassword){
console.log("비밀번호 일치");
// 세션 설정
req.session.email = body.userEmail;
}
else{
console.log("비밀번호 불일치");
}
res.redirect("/user/login");
});
세션을 설정하는 방법은 쿠키보다 더 간단합니다.
로그인에 성공하면 req.session의 email 속성에 사용자 이메일 값으로 추가하면 됩니다.
그러면 사용자 인증이 필요한 뷰 페이지를 렌더링할 때 클라이언트로 session 객체를 전달하기만 하면 됩니다.
따라서 아래와 같이 뷰 페이지를 수정하여,
- 로그인이 된 상태이면 "로그인에 성공하셨습니다~" 메시지를 출력하고,
- 비로그인 상태면 로그인 입력 화면을 출력하도록 하겠습니다.
/views/user/login.ejs
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Document</title>
</head>
<body>
<% if(session.email){ %>
<h3>로그인에 성공하셨습니다 ~</h3>
<form action="/user/logout" method="get">
<input type="submit" value="로그아웃">
</form>
<% } else{ %>
<h3>로그인</h3>
<form action="/user/login" method="post">
<table>
<tr>
<td>이메일 : </td>
<td><input type="text" name="userEmail"></td>
</tr>
<tr>
<td>비번 : </td>
<td><input type="password" name="password"></td>
</tr>
<tr>
<td><input type="submit" value="로그인"></td>
</tr>
</table>
</form>
<% } %>
</body>
</html>
로그인 상태와 비로그인 상태를 구분할 수 있는 것은 session객체의 email 속성을 확인함으로써 가능합니다.
( 로그아웃은 뒤에서 살펴 볼 세션 삭제와 관련되므로 미리 작성했습니다. )
이제 http://localhost:3000/users/login에서 테스트를 해보세요.
4. 세션 삭제
세션을 삭제하는 방법도 간단합니다.
사용자가 로그아웃 했을 때는 사용자 인증된 상태가 유지되면 안되므로, 세션을 삭제 하는 것이 좋습니다.
/routes/users.js
// 로그아웃
router.get("/logout", function(req,res,next){
req.session.destroy();
res.clearCookie('sid');
res.redirect("/user/login")
})
- 세션을 삭제하는 메서드는 destroy() 입니다.
- 또한 세션을 설정한 미들웨어에서 쿠키에 대한 정보도 추가했으므로 쿠키도 삭제해줘야 합니다.
- clearCookie() 메서드를 통해 쿠키를 삭제할 수 있으며, 이 때 인자로 넘겨주는 값은 미들웨어에서 등록한 세션 키입니다.
이상으로 암호화된 비밀번호에 대해 로그인을 하는 방법과 쿠키와 세션을 이용하여 로그인을 유지하는 방법에 대해 알아보았습니다.
[ 참고 자료 ]