pw + salt를 붙여 암호화, salt는 물리적으로 다른 db에 저장해야 함
예를 들어 사용자 정보를 mongoDB에 저장한다면 salt는 mySQL에 저장
salt는 사용자마다 다르게 랜덤하게 발행해야 함(정보 탈취 방지)
const sha = require('sha256');
app.post('/signup', (req, res) => {
console.log(req.body);
console.log(sha(req.body.userpw));
const crypto = require('crypto');
const generateSalt = (length = 16) => { //salt 16자로 랜덤 발행
return crypto.randomBytes(length).toString("hex");
};
const salt = generateSalt();
console.log(`Generated salt: ${salt}`);
console.log('salt 없는 pw hash: ', sha(req.body.userpw));
req.body.userpw = sha(req.body.userpw + salt);
console.log('salt 있는 pw hash: ', req.body.userpw);
mydb.collection('account')
.insertOne(req.body)
.then(result => {
console.log('회원가입 성공');
//MySQL에 salt를 저장
const sql = `INSERT INTO UserSalt (userid, salt)
VALUES (?,?)`; //?에 들어가는 data는 배열 인자에 들어감
mysqlconn.query(sql, [req.body.userid, salt], (err, result2) => { // 인자 3개(sql문, 삽입할 데이터(여러개가 들어갈 수 있으므로 배열로 선언), callback 함수)
if (err) {
console.log(err);
} else {
console.log('salt 저장 성공');
}
});
})
.catch(err => {
console.log(err);
});
res.redirect('/');
});

mysql에 salt 값 저장

이후 로그인 페이지에서 로그인 요청 시
req.body.userid의 값이 db에 있는지 확인(mongoDB)
있다면 salt 값이 저장된 다른 db(MySQL)에서 salt 값을 불러옴
db 안의 userid와 매치되는 userpw값과 sha(req.body.userpw + salt) 값이 일치하면 user 정보를 session에 저장 후 index.ejs로 렌더링, 아니면 login.ejs로 렌더링
app.post("/login", (req, res) => {
mydb
.collection("account")
.findOne({ userid: req.body.userid })
.then((result) => {
let salt;
const sql = `SELECT salt FROM UserSalt
WHERE userid=?`;
mysqlconn.query(sql, [req.body.userid], (err, rows, fields) => { // 콜백 함수 인자 : err, 결과 데이터들
console.log(rows);
salt = rows[0].salt;
const hashPw = sha(req.body.userpw + salt);
console.log(salt);
console.log('result.userpw: ', result.userpw);
console.log('hashPw: ', hashPw);
if (result != null && result.userpw == hashPw) {
req.body.userpw = hashPw;
req.session.user = req.body;
console.log("새로운 로그인");
// res.send(`${req.session.user.userid}님 환영합니다`);
res.render("index.ejs", { user: req.session.user });
} else {
//res.send("login fail");
res.render("login.ejs");
}
});
})
.catch((err) => {
console.log(err);
res.status(500).send();
});
});
passport는 이름 그대로 서비스를 사용할 수 있게끔 해주는 여권 같은 역할을 하는 Node.js용 인증 미들웨어
클라이언트가 서버에 요청할 자격이 있는지 인증할 때에 passport 미들웨어 사용
npm install passport
passport는 Strategy를 사용하여 사용자 요청에 대한 인증을 처리
대표적인 전략
LocalStrategy : 사용자의 아이디와 패스워드를 직접 받아서 인증하는 방식
OAuth : 외부 서비스를 통해 엑세스 토큰을 발급받아 인증하는 방식, 예를 들어 카카오톡, 페이스북, 구글 등을 통한 로그인
OpenID Connect : OAuth 2.0을 기반으로 하여 ID 토큰을 발급받아 인증하는 방식, 주로 구글 로그인에서 사용됨
사용자가 로그인한 상태를 유지하고, 각 요청에서 사용자를 식별할 수 있도록 도와줌
serializeUser
사용자가 로그인할 때 호출되며, 사용자 정보를 세션에 저장하는 역할
여기서 저장되는 정보는 보통 사용자 ID와 같은 간단한 정보
deserializeUser
이후 요청이 있을 때 호출되며, 세션에 저장된 정보를 이용해 사용자를 인증
이 과정에서 데이터베이스 조회 등을 통해 사용자 정보를 완전히 복원할 수 있음
기본 local은 session 방법을 사용하고 있기 때문에 express-session도 설치해야 함
npm install passport-local express-session
const passport = require('passport');
const LocalStrategy = require('passport-local').Strategy;
app.use(passport.initialize()); //passport를 사용한다고 express에 알림
app.use(passport.session()); //session을 이용하여 passport를 동작

app.post('/login', //'/login'으로 요청이 들어왔을 때
passport.authenticate('local', { //local 전략을 사용해 passport 실행
//successRedirect: '/', //인증 성공 시 '/'로 이동
//failureRedirect: '/fail' //인증 실패 시 '/fail'로 이동
}),
(req, res) => { //밑의 자격 검증 성공 시(done(null, result)) 실행되는 콜백함수
console.log(req.session);
console.log(req.session.passport);
res.render('index.ejs', { data: req.session.passport });
});

passport.use(new LocalStrategy( //요청에 대한 자격 검증을 use()를 통해 제공
{
usernameField: 'userid', //form에서 전달해줄 id와
passwordField: 'userpw', //password의 이름을 입력
session: true,
passReqToCallback: false //콜백 함수에서 request를 pass할거냐의 여부
},
function (inputid, inputpw, done) { //inputid, inputpw를 인자로 받아 검증을 하는 콜백함수 실행
mydb.collection('account')
.findOne({ userid: inputid })
.then(result => {
if (result.userpw == inputpw) { //result.userpw: DB, inputpw: input값
console.log('새로운 로그인');
done(null, result); //검증이 유효하면 done(null, result)로 result에 대한 정보 리턴
} else {
done(null, false, { message: '비밀번호 틀렸어요' }); //검증에 실패하면 done(null, false)를 리턴하고
} //실패한 이유를 메세지로 전달
})
.catch();
}
));
local strategy 내부의 로직을 실행하고 나면 done이 마지막으로 실행
done의 두 번째 인자가 false가 아니라면 passport.serializeUser가 실행됨

passport.serializeUser(function (user, done) {
console.log('serializeUser');
console.log(user);
done(null, user.userid); //세션에 사용자 ID를 저장
});
passport.deserializeUser(function (puserid, done) {
console.log('deserializeUser');
console.log(puserid);
mydb.collection('account')
.findOne({ userid: puserid })
.then(result => {
console.log(result);
done(null, result); //데이터베이스에서 사용자 정보를 조회하여 세션에 저장
})
.catch();
});
serializeUser : 로그인에 성공했을 때 유저 정보를 session에 저장하는 기능
로그인 성공 시
strategy에서 리턴한 result가 serializeUser에 user로 전달
session passport에 user값으로 user.userid(사용자의 식별자)가 저장
검증에 필요한 페이지에 방문할 때마다 deserializeUser가 실행
deserializeUser에서는 session에 있는 사용자의 식별자를 받아서 데이터베이스에서 조회, 해당 사용자를 찾아 done(null, result) result를 done 함수에 2번째 인자로 전달하면 request.user 객체에 전달됨
deserializeUser를 통해 request.user의 정보를 받을 수 있음
로그인이 되었다면 request.user가 있을 것이고 로그인 되지 않았다면 request.user가 없을 것








cmd> openssl req -nodes -new -x509 -keyout server.key -out server.cert
(server.key와 server.cert 파일이 생성됨. 이것을 server.js와 같은 디렉토리에 저장)


facebook 개발자 센터에서 MyBoard라는 앱 생성

const session = require("express-session");
app.use(
session({
secret: "암호화키",
resave: false, //매번 세션을 새로 발급받을지의 여부
saveUninitialized: false, //세션에 아무것도 들어있지 않을때에도 세션을 발급받을지의 여부
})
);
const passport = require('passport');
app.use(passport.initialize());
app.use(passport.session()); //내 세션과 연동
const FacebookStrategy = require('passport-facebook');
app.get('/facebook', passport.authenticate('facebook')); //'/facebook' url로 get 요청할때 passport 인증받음
app.get('/facebook/callback', passport.authenticate('facebook', {
successRedirect: '/', //성공했을 때 '/'(index)페이지로
failureRedirect: '/fail' //실패했을 때 '/fail' 페이지로, 필수 요소는 아님
}),
(req, res) => { } //화살표 함수로 들어가지 않기 때문에 비워둠
);
passport.use(new FacebookStrategy({ //여권 사용, 모듈을 생성자로 호출
clientID: "앱 ID",
clientSecret: "앱 시크릿 코드",
callbackURL: "/facebook/callback", //임의로 설정, 단 맨 앞에 '/'가 있어야 함
}, function (accessToken, refreshToken, profile, done) { //function 안의 작업이 성공하면 위의 succesRedirect로 이동/done은 콜백을 호출하는 인자/accessToken, refreshToken은 JWT
console.log('2', profile);
const authkey = 'facebook' + profile.id; //profile 안의 id, displayName은 facebook에서 profile을 통해 준 정보
const authName = profile.displayName;
mydb.collection('account')
.findOne({ userkey: authkey }) //사용자 정보를 못찾으면(없으면) 저장(중복 저장 피하기 위해)
.then(result => {
console.log('3', result);
if (result != null) { //연결하여 인증을 받았던 경험이 있으면
console.log('3-1 페이스북 사용자를 우리 DB에서 찾았음');
done(null, result); //err일 시 null, 아니면 result를 반환해 다음 흐름으로 넘어가겠다
} else {
console.log('3-1 페이스북 사용자를 우리 DB에서 못찾았음');
mydb.collection('account')
.insertOne({
userkey: authkey,
userid: authName
})
.then(insertResult => {
if (insertResult != null) {
console.log("3-2 페이스북 사용자를 우리 DB에 저장 완료");
mydb.collection('account')
.findOne({ userkey: authkey })
.then(result2 => {
if (result2 != null) {
console.log("3-3 페이스북 사용자를 우리 DB에 저장 후 다시 찾았음");
done(null, result2);
}
})
.catch(err => {
console.log(err);
})
}
})
.catch(err => {
console.log(err);
});
}
})
.catch(err => {
console.log(err);
});
}));
passport.serializeUser((user, done) => {
try{ //함수 안에 들어온 것을 확인
console.log('4 serializeUser', user);
done(null, user);
} catch(err) {
console.log(err);
}
});
app.get('/', (req, res) => { //첫 페이지 요청 시
console.log("'/'요청");
try { //serialize 된 상태로 왔을수도 있기 때문에 session에 passport가 있는지 확인
console.log("1", req.session.passport);
if(typeof req.session.passport != undefined //passport도 undefined가 아니고
&& req.session.passport.user) { //user정보도 passport에 있을 때
res.render("index.ejs", {data: req.session.passport}); //passport를 가지고 index.ejs로 렌더링
} else{ //passport가 없는 경우(브라우저를 막 띄운 상태)
res.render('index.ejs', {data:null}); //data가 null인 상태로 index.ejs 렌더링
}
} catch (err) {
console.log('1-1 '. err);
res.render('index.ejs', {data:null}); //에러가 나도 data가 null인 상태로 index.ejs 렌더링
}
});
passport.deserializeUser((user, done) => {
console.log('5, deserializedUser');
mydb
.collection("account")
.findOne({ userkey: user.userkey })
.then((result) => {
console.log(result);
done(null, result);
}); //user는 이미 passport에 있는 객체라서 이렇게 매번 DB에 가서 확인할 필요가 전혀없다!
});
signup.ejs
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Home</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.2.3/dist/css/bootstrap.min.css" rel="stylesheet"
integrity="sha384-rbsA2VBKQhggwzxH7pPCaAqO46MgnOM80zW1RWuH61DGLwZJEdK2Kadq2F9CUG65" crossorigin="anonymous">
</head>
<body>
<%- include('menu.html') %>
<div class="container mt-4">
<form action="/signup" method="post">
<div class="form-group">
<label>아이디</label>
<input type="text" name="userid" class="form-control userid">
</div>
<p></p>
<div><a href="#" class="btn btn-primary idCheck">중복확인</a></button>
<p></p>
</div>
<div class="form-group">
<label>비밀번호</label>
<input type="password" class="form-control" name="userpw">
</div>
<p></p>
<div class="form-group">
<label>소속</label>
<input type="text" class="form-control" name="usergroup">
</div>
<p></p>
<div class="form-group">
<label>이메일</label>
<input type="text" class="form-control" name="useremail">
</div>
<p></p>
<button type="submit" class="btn btn-primary" style="float:right" disabled>가입</button>
</form>
</div>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.2.3/dist/js/bootstrap.bundle.min.js"
integrity="sha384-kenU1KFdBIe4zVF0s0G1M5b4hcpxyD9F7jL+jjXkk+Q2h455rYXK/7HAuoJl+0I4"
crossorigin="anonymous"></script>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.7.1/jquery.min.js"></script>
<script>
$('.idCheck').click(function (event) {
event.preventDefault();
let userid = $('.userid').val();
console.log(userid);
$.ajax({
async: true,
type: 'post',
url: '/idcheck',
data: JSON.stringify({ userid: userid }),
contentType: 'application/json',
dataType: 'json',
success: function (data) {
if (data.idcheck == 0) {
alert('사용할 수 있는 아이디입니다');
$('button[type="submit"]').prop('disabled', false);
} else {
alert('중복된 아이디입니다');
$('button[type="submit"]').prop('disabled', true);
}
},
error: function (err) {
alert('error: ', err);
}
});
});
</script>
</body>
</html>
server.js
app.use(express.json());
app.post('/idcheck', async (req, res) => {
const exist = await mydb.collection('account')
.findOne({ userid: req.body.userid });
console.log('req에서 받은 userid', req.body.userid);
console.log('db에서 찾은 userid', exist);
if (exist) {
res.json({ idcheck: 1 });
} else {
res.json({ idcheck: 0 });
}
});
db에 존재하는 id를 중복 확인 시



db에 존재하지 않는 id를 중복 확인 시


오류 해결
클라이언트와 서버 사이를 오가는 data를 JSON 형식으로 보냈지만 express.json() 미들웨어를 등록하는 것을 잊어 서버에서 JSON 데이터가 파싱되지 않아 req.body.userid 값은 undefined, db에 있는 account.userid 값은 null이 되는 문제가 있었다

멋져요!