일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | 5 | ||
6 | 7 | 8 | 9 | 10 | 11 | 12 |
13 | 14 | 15 | 16 | 17 | 18 | 19 |
20 | 21 | 22 | 23 | 24 | 25 | 26 |
27 | 28 | 29 | 30 |
- 프론트개발
- 자바스크립트
- 코드스테이츠
- [파이썬 실습] 기초 문제
- 프로그래머스
- 자바스크립트 날씨
- 날씨 웹 만들기
- 코딩부트캠프
- 개발일기
- JavaScript
- leetcode
- 부트캠프
- [AI 5기] 연습 문제집
- 자바스크립트 sort()
- [파이썬 실습] 중급 문제
- 삼항연산자
- 간단한 날씨 웹 만들기
- 엘리스
- RN 프로젝트
- 리트코드
- 자바스크립트 split()
- reactnativecli
- 자바스크립트 reduce()
- 엘리스 ai 트랙
- 프론트개발공부
- HTML
- 개발공부
- [파이썬 실습] 심화 문제
- 엘리스 AI 트랙 5기
- 자바스크립트 날씨 웹 만들기
- Today
- Total
개발조각
[모두의 이력서_5일차] 로그인 기능 Bcrypt, jsonwebtoken 본문
처음으로 혼자서 프론트부터 백까지 하는 프로젝트이다 보니 정확하지 않을 수 있습니다!
로그인 기능 만들기
login route 기본틀
// server.ts
// 로그인 라우터
app.post('/login', (req, res) => {
// 1. 요청된 이메일을 DB에 있는지 찾기
// 2. 요청된 이메일이 DB에 있다면 비밀번호가 맞는 비밀번호 인지 확인
// 3. 비밀번호가 같다면 Token 생성
// 4. 생성된 토큰을 쿠키에 저장
})
1. 요청된 이메일을 DB에 있는지 찾기
findOne()을 사용하여 이메일이 있는지 찾아야 됩니다.
findOne()은 몽고디비 메소드 입니다!
// server.ts
// 로그인 라우터
app.post("/login", (req, res) => {
// 1. 요청된 이메일을 DB에 있는지 찾기
User.findOne({ email: req.body.email }, (err: "empty", user:any) => {
if (!user) { // 요청된 이메일이 DB에 없으면
return res.json({
loginSuccess: false,
message: "제공된 이메일에 해당하는 유저가 없습니다.",
});
}
});
});
User컬렉션 안에 req.body.email 이 이메일을 가진 유저가 하나도 없으면 user가 없을 것입니다.
2. 요청된 이메일이 DB에 있다면 비밀번호가 맞는 비밀번호 인지 확인
요청된 이메일이 DB에 있다면 비밀번호가 맞는지 확인해 줘야 됩니다.
비밀번호를 확인하기 위해 유저가 입력한 비밀번호(plainPassword)와 DB에 있는 비밀번호가 맞는지 확인해 줘야 됩니다.
- plainPassword (사용자 입력 비밀번호) : test4
- DB에 있는 암호화된 비밀번호 : $2b$10$uVxrqmG6XrA/ZQ2/0K1WMuTc6qjeeWYolKhw.raulpiQd8ZN1ewra
이 둘이 같은지 확인하기 위해서는
plainPassword를 암호화 한 뒤, DB에 있는 암호화된 비밀번호가 같은지 체크해야 됩니다.
이렇게 해주는 이유는 이미 암호화된 비밀번호는 복구화가 될 수 없기 때문입니다.
// User.ts
userSchema.methods.comparePassword = function (plainPassword: string, cb: any) { // cb:콜백
bcrypt.compare(plainPassword, this.password, function (err, isMatch) {
// 비번이 같지 않으면
if (err) return cb(err);
// 비번이 같으면
cb(null, isMatch); // isMatch = true
});
};
bcrypt.compare를 사용하면 plainPassword와 암호화된 비밀번호가 일치하는지 확인할 수 있습니다.
다시 로그인 라우터로 와서 comparePassword를 작업한 걸 적용해 줘야 됩니다.
// server.ts
// 로그인 라우터
app.post("/login", (req, res) => {
// DB에서 요청한 Email 찾기
User.findOne({ email: req.body.email }, (err, user) => {
if (!user) {
return res.json({
loginSuccess: false,
message: "email을 다시 확인하세요.",
});
}
// 2.DB에서 요청한 Email이 있다면 비밀번호가 같은지 확인
user.comparePassword(req.body.password, (err, isMatch) => {
// 비밀번호가 불일치 할 경우
if (!isMatch)
return res.json({
loginSuccess: false,
message: "비밀번호가 틀렸습니다",
});
// 비밀번호가 일치할 경우
});
});
});
3. 비밀번호가 같다면 Token 생성
// server.ts
// 로그인 라우터
app.post("/login", (req, res) => {
// 1. 요청된 이메일을 DB에 있는지 찾기
User.findOne({ email: req.body.email }, (err, user) => {
if (!user) {
return res.json({
loginSuccess: false,
message: "email을 다시 확인하세요.",
});
}
// 2. 요청된 이메일이 DB에 있다면 비밀번호가 맞는 비밀번호 인지 확인
user.comparePassword(req.body.password, (err, isMatch) => {
if (!isMatch)
return res.json({
loginSuccess: false,
message: "비밀번호가 틀렸습니다",
});
// 3. 비밀번호가 같다면 Token 생성
user.generateToken((err, user) => {
if (err) return res.status(400).send(err);
});
});
});
});
토큰을 생성하기 위해 jwt(jsonwebtoken) 라이브러리를 다운로드합니다.
npm install jsonwebtoken --save
https://www.npmjs.com/package/jsonwebtoken
jsonwebtoken
JSON Web Token implementation (symmetric and asymmetric). Latest version: 9.0.0, last published: 3 months ago. Start using jsonwebtoken in your project by running `npm i jsonwebtoken`. There are 23096 other projects in the npm registry using jsonwebtoken.
www.npmjs.com
var jwt = require('jsonwebtoken');
var token = jwt.sign({ foo: 'bar' }, 'shhhhh');
이런 식으로 넣으라고 되어 있습니다.
jwt를 이용해서 token을 생성할 때는 user.id + 'screatToken' = token 이렇게 생성해주어야 합니다.
이렇게 해주셔야 나중에 token을 해석할 때 'secretToken'을 넣으면 user._id가 나옵니다.
그러면 token을 가지고 user._id가 누구인지 알 수 있습니다.
// User.ts
userSchema.methods.generateToken = function (cb: any) {
const user = this;
// jsonwebtoken을 사용해서 토큰 생성
const token = jwt.sign(user._id.toHexString(), "createToken");
user.token = token;
user.save(function (err: any, user: any) {
if (err) return cb();
cb(null, user);
});
};
jwt.sign() : DB에 들어있는 _id와 "createToken"을 합쳐서 새로운 토큰을 생성
user._id.toHexString() : 인자로 넘겨주는 값은 plain object여야 하기 때문에 .toHexString() 사용
이걸 참고하시면 좋을 것 같습니다!
toString()대신 toHexString() 을 사용한 이유가 궁금합니다~!! - 인프런 | 질문 & 답변
안녕하세요 강의 잘보고 있습니다! 강의를 보다가 궁금한점이 생겨서요! toString()대신 toHexString() 을 사용한 이유가 궁금합니다~!! - 질문 & 답변 | 인프런
www.inflearn.com
제 생각에는, MongoDB에서 제공하고 있는 예제 코드에서도 toHexString으로 사용하고 있고,
toString에 포함되어서 더 좁은 의미의 함수이기 때문에 예외발생률이 적어서 사용되는 것이 아닐까 싶습니다.
4. 생성된 토큰을 쿠키에 저장
생성한 토큰은 저장을 해야 됩니다.
토큰 저장은 쿠키, 로컬스토리지, 세션스토리지 3가지 중 하나에 넣어주면 됩니다.
(어디에 저장해야 가장 안전한 지는 논란이 많습니다.)
이번에는 쿠키에다가 하겠습니다.
쿠키, 로컬스토리지, 세션스토리지는 브라우저 > 개발자도구 > Application에 있습니다.
쿠키에다가 토큰을 저장하기 위해 cookie-parser를 설치해야 됩니다.
express에서 제공되는 cookie-parser 설치하기
npm install cookie-parser --save
// server.ts
import jwt from "jsonwebtoken";
// 로그인 라우터
app.post("/api/users/login", (req, res) => {
// 1. 요청된 이메일을 DB에 있는지 찾기
User.findOne({ email: req.body.email }, (err, user) => {
if (!user) {
return res.json({
loginSuccess: false,
message: "email을 다시 확인하세요.",
});
}
// 2. 요청된 이메일이 DB에 있다면 비밀번호가 맞는 비밀번호 인지 확인
user.comparePassword(req.body.password, (err, isMatch) => {
if (!isMatch)
return res.json({
loginSuccess: false,
message: "비밀번호가 틀렸습니다",
});
// 3. 비밀번호가 같다면 Token 생성
user.generateToken((err, user) => {
if (err) return res.status(400).send(err);
// 4. 생성된 토큰을 쿠키에 저장
res
.cookie("hasVisited", user.token)
.status(200)
.json({ loginSuccess: true, userId: user._id });
});
});
});
});
이렇게 적용하면 쿠키에 아래와 같이 들어가게 됩니다.
- Name : x_auth
- Value : 생성된 토큰
최종
// server.ts
import jwt from "jsonwebtoken";
app.use(cookieParser()); // 쿠키파서 사용 가능
// 로그인 라우터
app.post("/api/users/login", (req, res) => {
// 1. 요청된 이메일을 DB에 있는지 찾기
User.findOne({ email: req.body.email }, (err, user) => {
if (!user) {
return res.json({
loginSuccess: false,
message: "email을 다시 확인하세요.",
});
}
// 2. 요청된 이메일이 DB에 있다면 비밀번호가 맞는 비밀번호 인지 확인
user.comparePassword(req.body.password, (err, isMatch) => {
if (!isMatch)
return res.json({
loginSuccess: false,
message: "비밀번호가 틀렸습니다",
});
// 3. 비밀번호가 같다면 Token 생성
user.generateToken((err, user) => {
if (err) return res.status(400).send(err);
// 4. 생성된 토큰을 쿠키에 저장
res
.cookie("hasVisited", user.token)
.status(200)
.json({ loginSuccess: true, userId: user._id });
});
});
});
});
// User.ts
import jwt from "jsonwebtoken";
userSchema.methods.comparePassword = function (plainPassword, cb) {
bcrypt.compare(plainPassword, this.password, function (err, isMatch) {
if (err) return cb(err);
cb(null, isMatch);
});
};
userSchema.methods.generateToken = function (cb) {
var user = this;
// jsonwebtoken을 사용해서 토큰 생성
var token = jwt.sign(user._id.toHexString(), "createToken");
user.token = token;
user.save(function (err, user) {
if (err) return cb();
cb(null, user);
});
};
callback문법 수정하기
이제 postman으로 테스트를 해야 되는데 테스트할 때 MongooseError가 납니다.
이유는 Mongoose5(5에서는 findOne()), Mongoose6 (6에서는 save())부터
findeOne(), save()는 callback문법이 지원하지 않습니다.
server.ts 수정된 코드 (findeOne() 수정)
import cookieParser from "cookie-parser";
app.use(cookieParser()); // 쿠키파서 사용 가능
app.post("/login", (req, res) => {
const { email, password } = req.body;
// 1. 요청된 이메일을 DB에 있는지 찾기
User.findOne({ email: email })
.then((user: any) => {
if (!user) {
return res.json({
loginSuccess: false,
message: "email을 다시 확인하세요.",
});
}
// 2. 요청된 이메일이 DB에 있다면 비밀번호가 맞는 비밀번호 인지 확인
user.comparePassword(password, (err: "empty", isMatch: boolean) => {
if (!isMatch)
return res.json({
loginSuccess: false,
message: "비밀번호가 틀렸습니다",
});
// 3. 비밀번호가 같다면 Token 생성
user.generateToken((err: "empty", user: any) => {
if (err) return res.status(400).send(err);
// 4. 생성된 토큰을 쿠키에 저장
res
.cookie("hasVisited", user.token)
.status(200)
.json({ loginSuccess: true, userId: user._id });
});
});
})
.catch((err) => {
return res.status(400).send(err);
});
});
app.listen(5000, () => {
console.log("server is running!");
});
User.ts 수정 코드(save() 수정)
userSchema.methods.comparePassword = function (plainPassword: string, cb: any) {
bcrypt.compare(plainPassword, this.password, function (err, isMatch) {
if (err) return cb(err);
cb(null, isMatch);
});
};
userSchema.methods.generateToken = function (cb: any) {
const user = this;
// jsonwebtoken을 사용해서 토큰 생성
const token = jwt.sign(user._id.toHexString(), "createToken");
user.token = token;
user
.save()
.then(() => {
cb(null, user);
})
.catch((err: "empty") => {
return cb();
});
};
export const User = model("User", userSchema);
드디어 성공...!
전체 코드
// server.ts
import express from "express";
import bodyParser from "body-parser";
import cookieParser from "cookie-parser";
import { User } from "./models/User";
import { config } from "./config/key";
const app = express();
app.use(bodyParser.urlencoded({ extended: true }));
app.use(bodyParser.json());
app.use(cookieParser()); // 쿠키파서 사용 가능
const mongoose = require("mongoose");
mongoose
.connect(config.mongoURI, {
useNewUrlParser: true,
useUnifiedTopology: true,
})
.then(() => console.log("MongoDB Connected..."))
.catch((err: "empty") => console.log(err));
app.get("/", function (req, res) {
res.send("Hello World");
});
// 회원가입 라우터
app.post("/register", async (req, res) => {
const user = new User(req.body);
const result = await user
.save()
.then(() => {
res.status(200).json({
success: true,
});
})
.catch((err: "empty") => {
res.json({ success: false, err });
});
return result;
});
app.post("/login", (req, res) => {
const { email, password } = req.body;
// 1. 요청된 이메일을 DB에 있는지 찾기
User.findOne({ email: email })
.then((user: any) => {
if (!user) {
return res.json({
loginSuccess: false,
message: "email을 다시 확인하세요.",
});
}
// 2. 요청된 이메일이 DB에 있다면 비밀번호가 맞는 비밀번호 인지 확인
user.comparePassword(password, (err: "empty", isMatch: boolean) => {
if (!isMatch)
return res.json({
loginSuccess: false,
message: "비밀번호가 틀렸습니다",
});
// 3. 비밀번호가 같다면 Token 생성
user.generateToken((err: "empty", user: any) => {
if (err) return res.status(400).send(err);
// 4. 생성된 토큰을 쿠키에 저장
res
.cookie("hasVisited", user.token)
.status(200)
.json({ loginSuccess: true, userId: user._id });
});
});
})
.catch((err) => {
return res.status(400).send(err);
});
});
app.listen(5000, () => {
console.log("server is running!");
});
User.ts
import { Schema, model, Model } from "mongoose";
import bcrypt from "bcrypt";
import jwt from "jsonwebtoken";
const saltRounds = 10;
const userSchema = new Schema({
name: {
type: String,
maxlength: 50,
},
email: {
type: String,
trim: true,
unique: 1,
},
password: {
type: String,
minlength: 5,
},
lastname: {
type: String,
maxlength: 50,
},
role: {
type: Number,
default: 0,
},
image: String,
token: {
type: String,
},
tokenExp: {
type: Number,
},
});
userSchema.pre("save", function (next) {
const user = this;
// 비밀번호를 변환될때만 bcrypt발동
if (user.isModified("password")) {
// 비밀번호를 암호화 시킨다.
bcrypt.genSalt(saltRounds, function (err, salt) {
if (err) return next(err);
if (!user.password) return null;
bcrypt.hash(user.password, salt, function (err, hash) {
if (err) return next(err);
user.password = hash;
next();
});
});
} else {
next();
}
});
userSchema.methods.comparePassword = function (plainPassword: string, cb: any) {
bcrypt.compare(plainPassword, this.password, function (err, isMatch) {
if (err) return cb(err);
cb(null, isMatch);
});
};
userSchema.methods.generateToken = function (cb: any) {
const user = this;
// jsonwebtoken을 사용해서 토큰 생성
const token = jwt.sign(user._id.toHexString(), "createToken");
user.token = token;
user
.save()
.then(() => {
cb(null, user);
})
.catch((err: "empty") => {
return cb();
});
};
export const User = model("User", userSchema);
참고자료
https://www.youtube.com/watch?v=HcAYHUHTNi4&t=50s
https://www.youtube.com/watch?v=yWRj4GxFcr8
[Node.js] Express + MongoDB part#4 로그인 기능 구현
로그인 기능을 구현하기 위해 login route 코드를 작성위 로직을 코드로 해석하면 아래와 같습니다.코드 해석DB에서 요청한 Email 찾기mongoDB에서 제공하는 findOne() 메소드를 사용DB에서 요청한 Email이
velog.io
model.findone() no longer accepts a callback 오류 - 인프런 | 질문 & 답변
findOne()도 save()와 마찬가지로 몽구스 5.0부터는 콜백함수를 지원하지 않기 때문에 기존에 app.post('/api/users/login',(req, res) =>{ // 요청된 이메일을 데이터베이스 찾기 User.findOne({email:...
www.inflearn.com
'모두의 이력서' 카테고리의 다른 글
[모두의 이력서_7일차] styled-components + Next.js + TS (0) | 2023.03.31 |
---|---|
[모두의 이력서_6일차] Auth 기능 및 로그아웃 기능 (0) | 2023.03.30 |
[모두의 이력서_5일차] Bcrypt로 비밀번호 암호화 하기 (0) | 2023.03.29 |
[모두의 이력서_4일차] NODE MON 설치, MONGO URI 정보를 보호하기 (0) | 2023.03.28 |
[모두의 이력서_4일차] 회원가입 기능 만들기 (몽고디비에서) (0) | 2023.03.28 |