DB 정의
아이디와 비밀번호를 저장하는 데이터베이스를 만들어보자.
const userSchema = new mongoose.Schema({
username: {
type: String,
required: [true, 'Username cannot be blank'],
unique: true
},
password: {
type: String,
required: [true, 'Password cannot be blank'],
unique: true
}
})
비밀번호 암호화
비밀번호를 있는 그대로 저장하면 안 된다. 그런 일이 벌어져서는 안 되지만 누군가가 데이터베이스에 몰래 접근했다고 하자. 그럼 모든 사용자의 아이디와 비밀번호를 알 수 있을 것이다. 이런 상황을 막기 위해 비밀번호는 암호화해서 저장해야 한다.
해시 함수
암호화에는 해시 함수가 사용된다. 동일한 입력에 대해 해시 함수를 적용하면 암호화한 결과가 동일하다는 뜻이 된다. 해커가 데이터베이스에 접근해서 사용자들의 암호화된 비밀번호를 봐도 그 비밀번호가 뭐였는지 알 수 있는 방법이 있다. 해시 함수의 종류는 한정적이고, 사용자들은 동일한 비밀번호를 사용할 수 있다. 해커가 사람들이 많이 사용할만한 비밀번호를 해시 함수를 적용하여 테이블로 미리 만들어둔다면 암호화하기 전 비밀번호가 무엇이었는지 알 수 있을 것이다.
패스워드 솔트 (Password Salts)
그래서 패스워드 솔트를 사용한다. 비밀번호를 해시 함수로 암호화하기 전에 비밀번호에 임의의 문자열을 덧붙인다. 임의의 문자열을 덧붙여 암호화하면 해커가 미리 만들어놓은 테이블이 무용지물이 된다. 또한, 사용자마다 다른 패스워드 솔트를 사용하면 동일한 비밀번호를 사용하고 있어도 암호화된 비밀번호는 서로 다르다.
bcryptjs
npm에서 검색해 보면 bcrypt도 있고 bcryptjs도 있다. bcrypt는 node js를 위해서 만들어졌는데 브라우저에서는 작동하지 않는다. bcryptjs는 c++로 만들어져서 비교적 속도가 빠르고, 브라우저에서도 작동한다. 무엇을 써도 상관없지만 여기서는 bcryptjs를 사용한다.
계정 생성
미들웨어 사용 X
bcrypt의 hash 메서드를 사용하여 비밀번호를 암호화한다. 이때 12는 솔트의 길이를 지정한다. 문자열로 적으면 사용할 솔트를 지정할 수 있다.
app.post('/register', async (req, res) => {
const { username, password } = req.body;
const hash = await bcrypt.hash(password, 12);
const user = new User({ username: username, password: password });
await user.save();
res.redirect('/');
})
미들웨어 사용 O
사용자를 생성할 때 입력받은 사용자 이름과 비밀번호를 일단 암호화하지 않고 save 한다. save에 pre 트리거를 부착하여 실제로 save 하기 전에 비밀번호를 암호화한다.
app.js
app.post('/register', async (req, res) => {
const { username, password } = req.body;
const user = new User({ username: username, password: password });
await user.save();
res.redirect('/');
})
user.js
userSchema.pre('save', async function (next) {
this.password = await bcrypt.hash(this.password, 12);
next();
})
로그인 & 세션 유지
미들웨어 사용 X
입력받은 username으로 얻은 비밀번호와 입력받은 비밀번호가 일치한 지 비교한다. 참고로 isValid는 true 혹은 false를 반환한다. 비밀번호가 일치할 경우 세션에 사용자 id를 추가하고 쿠키를 전송한다. 그리고 userinfo로 redirect 하는데, userinfo 페이지에서 사용자가 쿠키를 가지고 있는지 확인한다. 쿠키가 있어야만 userinfo 페이지에 접속할 수 있기 때문에 쿠키가 없으면 로그인 화면으로 돌려보낸다.
const requireLogin = (req, res, next) => {
if (!req.session.user_id) { return res.redirect('/login'); }
next();
}
app.post('/login', async (req, res) => {
const { username, password } = req.body;
const user = await User.findOne({ username: username });
const isValid = await bcrypt.compare(password, user.password)
if (isValid) {
req.session.user_id = user._id;
return res.redirect('/userinfo');
}
res.redirect('/login');
})
app.get('/userinfo', requireLogin, (req, res) => {
res.render('userinfo');
})
미들웨어 사용 O
User 스키마에 아이디와 비밀번호가 일치하는지 확인하여 일치하면 객체를 반환하고 불일치하면 false를 반환하는 findAndValid라는 메서드를 추가한다.
app.js
app.post('/login', async (req, res) => {
const { username, password } = req.body;
const user = await User.findAndValid(username, password);
if (user) {
req.session.user_id = user._id;
return res.redirect('/userinfo');
}
res.redirect('/login');
})
user.js
userSchema.statics.findAndValid = async function (username, password) {
const user = await this.findOne({ username: username });
const isValid = await bcrypt.compare(password, user.password);
return isValid ? user : false;
}
'웹 프로그래밍' 카테고리의 다른 글
[Yelpcamp 프로젝트] 이미지 파일 (1) | 2023.12.07 |
---|---|
[Yelpcamp 프로젝트] 로그인 구현 (0) | 2023.12.01 |
[Yelpcamp 프로젝트] flash (0) | 2023.11.29 |
[Express] 세션 (0) | 2023.11.17 |
[JS] 쿠키 (0) | 2023.11.17 |