app단에서 localhost:3000/users
로 연결시켜놨다.
models/newuser.js
const mongoose = require('mongoose');
const Schema = mongoose.Schema;
const userSchema = new Schema({
email: {
type: String,
required: true,
},
password: {
type: String,
required: true,
}
});
const userData = mongoose.model('newusers', userSchema);
module.exports = userData;
userSchema를 만들어준다.
간단하게 이메일과 비밀번호만 설정하고, newusers 모델을 만들어준다.
views/blog/login.ejs
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<h2>로그인하기</h2>
<form action="/users/login" method="post">
<input type="text" name="email" placeholder="이메일">
<br><br>
<input type="password" name="password" id="" placeholder="비밀번호">
<br><br>
<input type="submit" value="전송하기">
</form>
</body>
</html>
localhost:3000/users/login
으로 form data를 post method로 전송한다.
views/blog/auth.js
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<h2>가입하기</h2>
<form action="/users/signup" method="post">
<input type="text" name="email" placeholder="이메일">
<br><br>
<input type="password" name="password" id="" placeholder="비밀번호">
<br><br>
<input type="submit" value="전송하기">
</form>
</body>
</html>
localhost:3000/users/signup
으로 form data를 post method로 전송한다.
routes/users.js
통코드
var express = require('express');
const userSchema = require('../models/newuser');
const bcrypt = require('bcrypt');
const {body, validationResult} = require('express-validator');
const session = require('express-session');
const parseurl = require('parseurl');
var router = express.Router();
/* GET users listing. */
router.get('/', function(req, res, next) {
res.render('blog/auth');
});
// 정규 표현식으로 인증
//passport
router.get('/cookie', (req, res) => {
// key(변수-이름) ,value(저장하고싶은값)
res.cookie('drink', 'water');
res.send('set cookies');
});
// 프로그래밍 => 디지털 프랜스포메이션
// 실제 생활에 있는걸 => 컴퓨터로 옮기는 일
//
router.post('/signup'
, body('email').isEmail().withMessage('아이디는 email 형태를 따르셔야 합니다.')
, body('password').isLength({min:5}).withMessage('비밀번호는 최소 5글자 이상입니다.')
, async (req, res) => {
const email = req.body.email;
const password = req.body.password;
const errors = validationResult(req);
if (!errors.isEmpty()) {
return res.status(400).json({
errors: errors.array()
});
}
// 중복 가입
const findresult = await userSchema.findOne({email});
if (findresult) {
res.status(401).json({msg:'이미 가입된 계정입니다.'});
} else {
const salt = bcrypt.genSaltSync(10);
const bcryptpw = bcrypt.hashSync(password, salt);
// 복호화 할때 기준이 되는 메세지의 길이가 존재함.
userSchema.create({
email: email,
password: bcryptpw
}).then(result => {
// console.log(result);
res.status(200).json(result);
});
}
// 찾는 쿼리.
// 결과 존재 => 중복으로 가입이 되어 있는 경우.
// 결과가 X => 신규가입.
//
});
router.post('/login', async (req, res) => {
const email = req.body.email;
const password = req.body.password;
// 가입을 했던 유저인지 아닌지
const userdata = await userSchema.findOne({email}).exec();
if (!userdata) { // 유저 데이터가 없다면
return res.status(401).json({msg: '가입되지 않은 계정입니다.'});
} else { // 유저 데이터가 존재한다면 => 비밀번호가 매칭되는지
const pwMatch = bcrypt.compareSync(password, userdata.password);
if (pwMatch) {
res.status(200).json({msg:'OK'});
} else {
res.status(401).json({msg:'비밀번호가 일치하지 않습니다.'});
}
}
});
router.get('/login', (req, res) => {
res.render('blog/login');
});
// 쿠키와 세션
// 쿠키 => 사용자의 브라우저에 저장 데이터 모음 => JWT token => 정보 저쟝량 분산 => 비용 절감
// 보안 이슈의 문제로부터 훨씬 자유로움
// 쿠키로서 너의 정보를 너에게 저장. 그것이 잘못되는 것은 너의 책임이다.
// 세션 => 서버쪽에 저장하는 데이터 모음 => session 에 저장
// 각 나라마다 언어정보가 다름 ==> 번역된 텍스트를 각 나라에 맞게.
// 복붙
router.use(
session({
secret: "12345",
resave: false,
saveUninitialized: true
})
);
// users.js -> router구간에서만 사용 가능하게 함
// 프로젝트 전체구간에서는 어떻게해얗할지 각자 고민
// 조건부 렌더링
//세션으 활용해서 어떻게 핳ㄹ건가
router.use(function (req, res, next) {
if (!req.session.views) {
req.session.views = {}
}
// get the url pathname
var pathname = parseurl(req).pathname
// count the views
req.session.views[pathname] = (req.session.views[pathname] || 0) + 1
next()
})
//api 과부하: Apache JMeter
// 외래키를 타고, 타고, 타고, 3~4 가지의 개념을
// 하나의 큰 쿼리 경우들이 많이 생긴다.
// 1일 접속자 10만명단위
// node pm2 => 서버 과부하 있으면 서버 터짐, cpu 점유율이 초과되거나 메모리
// 1000명 을 25명단위로 자동으로 나누어서 과부하에 대한 영역을 줄여주고 안정을 높여주는 역할
// aws 로드밸런싱 : 컴퓨터 1대를 3대로
// pm2: cpu영역을 할당해서 안정성있게 처리해주는 역할
// React, vue, svelte
// single page application
// SPA => ReadableStreamDefaultController={, }
// Web => SPA => 모바일 => 업로드 반영 18시간
// SPA => 모바일
// Saas
router.get('/foo', function (req, res, next) {
res.send('you viewed this page ' + req.session.views['/foo'] + ' times')
})
module.exports = router;
var express = require('express');
var router = express.Router();
router.get('/', function(req, res, next) {
res.render('blog/auth');
});
localhost:3000/users/
로 접속하면 views/blog/auth.ejs
파일을 렌더링해서 보여준다.
var express = require('express');
const userSchema = require('../models/newuser');
const bcrypt = require('bcrypt');
const {body, validationResult} = require('express-validator');
var router = express.Router();
router.post('/signup'
, body('email').isEmail().withMessage('아이디는 email 형태를 따르셔야 합니다.')
, body('password').isLength({min:5}).withMessage('비밀번호는 최소 5글자 이상입니다.')
, async (req, res) => {
const email = req.body.email;
const password = req.body.password;
const errors = validationResult(req);
if (!errors.isEmpty()) {
return res.status(400).json({
errors: errors.array()
});
}
// 중복 가입
const findresult = await userSchema.findOne({email});
if (findresult) {
res.status(401).json({msg:'이미 가입된 계정입니다.'});
} else {
const salt = bcrypt.genSaltSync(10);
const bcryptpw = bcrypt.hashSync(password, salt);
// 복호화 할때 기준이 되는 메세지의 길이가 존재함.
userSchema.create({
email: email,
password: bcryptpw
}).then(result => {
// console.log(result);
res.status(200).json(result);
});
}
// 찾는 쿼리.
// 결과 존재 => 중복으로 가입이 되어 있는 경우.
// 결과가 X => 신규가입.
//
});
장고에서 사용하는 is_valid와 비슷하게 검증을 하려면 node.js에서는 express-validator 패키지를 다운받아서 사용한다.
body('email').isEmail().withMessage('문구')
로 req.body.email이 이메일형식을 갖추고 있는지, 만약 이메일 형식이 아니라면 메세지로 '문구'를 출력한다.
body('password').isLength({min:5}).withMessage('문구')
이것도 비슷한데, 비밀번호의 길이를 검증한다. min이 있따면 max로 있을듯.
이걸 미들웨어 형태로 검증을 하고 (req, res)로 넘겨준다.
const errors = validationResult(req)
에서 에러가 있으면 반환값이 있고, 에러가 없으면 반환값이 없다.
반환값이 있으면 (빈값이 아니라면) 400을 내보낸다.
이렇게 이메일과 비밀번호가 양식 조건을 충족했는지 검증을 한 후, 데이터베이스에 중복계정인지 또 검증한다.
중복계정이 아니라면 bcrypt로 비밀번호를 암호화를 한다.
const salt = bcrypt.genSaltSync(10);
const bcryptpw = bcrypt.hashSync(password, salt);
bcrypt.genSaltSync(숫자)
는 암호화길이를 숫자만큼이라고 정해주는것이고, bcrypt.hashSync(암호화할거, 길이)
를 통해 암호화한다.
원본 비밀번호는 냅두고 암호화된 비밀번호를 이메일과 함께 데이터베이스에 저장한다. 이제 이것은 본인 외에 아무도 모른다.
router.get('/login', (req, res) => {
res.render('blog/login');
});
var express = require('express');
const userSchema = require('../models/newuser');
const bcrypt = require('bcrypt');
var router = express.Router();
router.post('/login', async (req, res) => {
const email = req.body.email;
const password = req.body.password;
// 가입을 했던 유저인지 아닌지
const userdata = await userSchema.findOne({email}).exec();
if (!userdata) { // 유저 데이터가 없다면
return res.status(401).json({msg: '가입되지 않은 계정입니다.'});
} else { // 유저 데이터가 존재한다면 => 비밀번호가 매칭되는지
const pwMatch = bcrypt.compareSync(password, userdata.password);
if (pwMatch) {
res.status(200).json({msg:'OK'});
} else {
res.status(401).json({msg:'비밀번호가 일치하지 않습니다.'});
}
}
});
bcrypt.compareSync(입력받은 비밀번호, 데이터베이스에 저장된 비밀번호)
로 비밀번호가 일치하는지 확인한다.JWT (Json Web Token)
https://jwt.io/introduction
Debugger: https://jwt.io/#debugger-io
class UserAuthService {
async getUser({ email, password }) {
// 이메일 db에 존재 여부 확인
// if isPasswordEqual false, password not matches
if(!isPasswordEqual) {
const errorMessage = "비밀번호가 일치하지 않습니다.";
return { errorMessage };
}
여기서 errorMessage는 다른 파일에서 UserAuthService.getUser({email, pwd}).errorMessage
로 접근할 수 있다.
const secretKey = process.env.JWT_SECRET_KEY || "jwt-secret-key";
// jwt 의 sign 함수를 이용하여 토큰 생성, 이 때 위의 secretKey 사용
// jsonwebtoken은 비동기 처리 안함
const token = jwt.sign({user_id: user.id}, secretKey);
// jwt.verify 함수를 이용하여 정상적인 jwt인지 확인
const jwtDecoded = jwt.verify(userToken, secretKey);
// verify 함수로부터 반환된 결과에서 user_id 추출
const user_id = jwtDecoded.user_id;
jwt.verify(토큰, 시크릿키)
는 토큰이 유효하다면 토큰안에 저장해놓은 값이 반환된다. 여기에서는 jwt.sign({user_id:user.id})
로 토큰을 발급받았으므로 {user_id:user.id}
가 반환값이다.
참고: https://carrotweb.tistory.com/117
https://redbinalgorithm.tistory.com/692