728x90
목차
039
cors
cors (Cross-Origin Resource Sharing)란?
- 자신이 속하지 않은 도메인이나 다른 프로토콜, 다른 포트에 있는 리소스 요청을 요청하는 cross-origin HTTP요청 방식이다.
- 좌측의 웹 사이트는 domain-a.com으로 구동되고 있다. 파란색 이미지는 웹사이트가 구동 중인 domain-a.com 으로부터 자료를 요청하고 수신한다.
- 하지만 아래의 빨간색 이미지는 domain-b.com라는 다른 도메인에 자료를 요청하고 수신한다.
- 이러한 방식이 cross-origin HTTP요청 방식이다.
- 중요한 부분은 서버는 기본적으로 cors 방식을 제한해둔다.
- 특정 서버 리소스에 다른 임의의 웹 사이트들으 request를 보낼 수 있다면 악의적으로 특정 서버의 세션을 탈취하거나 서버에 무리가 가는 행위 등의 문제가 발생할 수 있기 때문이다.
- 따라서 다른 사이트에서 나의 서버에 요청하는 것을 허가하고 싶은 방법에는 모두에게 제한 없이 제공하는 방식과 본인이 직접 특정 도메인들만 허용하는 방식이 있다.
node.js_모두에게 허용하는 방식
- 별도의 값 없이 아래의 내용대로 입력을 하면 모든 도메인에서 제한 없이 서버에 요청을 보내고 응답을 받을 수 있다.
const cors = require('cors');
app.use(cors());
node.js_특정 도메인에게 허용하는 방식
- 자신이 허용할 도메인을 추가하면 해당 도메인은 제한없이 해당 서버에 요청과 응답을 보낼 수 있다.
app.use(cors({
// 도메인 허용 옵션
// 1. 접근을 허용할 도메인을 작성
// 여러개의 도메인을 허용하고 싶다면 배열의 형태로 넣어주면 된다.
origin : "http://127.0.0.1:5500",
// 클라이언트의 요청에 쿠키를 포함할지의 속성
credentials : true,
}))
회원가입, 로그인, 게시판 페이지 구현
#. 폴더의 경로는 다음과 같다.
1. app.js_ 사용할 모듈을 불러오고 웹 서버를 열어준다.
- 사용할 모듈을 저장 후 불러온다.
- npm i bcrypt cors dotenv express express-session jsonwebtoken mysql2 sequelize
const express = require("express");
const cors = require("cors");
const dot = require("dotenv").config();
const session = require("express-session");
- 서버 인스턴스를 생성한다.
const app = express();
- body 객체를 가져온다.
app.use(express.urlencoded({extended:false}));
- session을 사용하기 위한 설정을 한다.
app.use(session({
secret : process.env.SESSION_TOKEN_KEY,
resave : false,
saveUninitialized : false
}))
- sequelize 연결 매핑을 설정한다.
//forse: 데이터 베이스에 있으면 해당 값을 초기화 시킬지 유무를 설정
sequelize.sync({forse : false}).then(()=>{
console.log("연결성공")
}).catch((err)=>{
console.log(err)
})
- 서버를 대기시킨다.
app.listen(8080, ()=>{
console.log("서버열림")
})
- cors 모듈을 사용하여 도메인을 허용한다.
- 지금의 경우, Live server의 경로를 허용한다.
// 다른 도메인에서 악의적으로 접근할 수 없도록
// 도메인 접근 시 발생하는 보안 정책
// 다른 도메인과 통신을 안전하게 유지 시키기 위해서 보안 정책이 있다.
// cors 모듈을 가지고 도메인을 허용 해주자
// Access-control-Allow-origin을 헤더에 포함해서
// 접근을 허용하고 응답하고 브라우저에서 응답을 받은 뒤
// 헤어 값을 확인해서 접근을 허용 또는 차단한다.
app.use(cors({
// 도메인 허용 옵션
// 1. 접근을 허용할 도메인을 작성
// 여러개의 도메인을 허용하고 싶다면 배열의 형태로 넣어주면 된다.
origin : "http://127.0.0.1:5500",
// 클라이언트의 요청에 쿠키를 포함할지의 속성
credentials : true,
}))
2. .env_DATABASE의 정보키를 작성하고, SESSION_KEY와 ACCESS_TOKEN_KEY를 작성한다.
DATABASE_PASSWORD = 000000
DATABASE_USERNAME = root
DATABASE_NAME = test11
DATABASE_HOST = 127.0.0.1
ACCESS_TOKEN_KEY = myaccessToken
SESSION_TOKEN_KEY = mysessionToken
3. config/index.js_sequelize 객체 생성 시 들어갈 데이터를 설정한다.
const config = {
dev: {
username: process.env.DATABASE_USERNAME,
password: process.env.DATABASE_PASSWORD,
database: process.env.DATABASE_NAME,
host: process.env.DATABASE_HOST,
dialect: "mysql"
}
}
module.exports = config;
4. models/users.js_sequelize의 User의 모델을 설정한다.
- hasMany 메서드를 이용하여 Post 테이블의 참조키 user_id에 User테이블의 id 칼럼을 참조시킨다.
const Sequelize = require("sequelize");
class User extends Sequelize.Model{
static init(seq){
return super.init({
name: {
type: Sequelize.STRING(20),
allowNull: false
},
age : {
type: Sequelize.INTEGER,
allowNull: false
},
user_id : {
type: Sequelize.STRING(20),
allowNull: true
},
user_pw : {
type: Sequelize.STRING(64),
allowNull: true
}
}, {
sequelize : seq,
timestamps : true, // 추가 시간, 수정 시간 자동 생성
underscored : false, // 카멜 케이스 할건지 유무 설정
modelName : "User", // 노드 프로젝트에서 사용할 이름(조회했을 때 보임)
tableName : "users", // 데이터베이스 테이블의 이름
paranoid : false, // 삭제 시간 표기 유무
charset : "utf8", // 인코딩 방식
collate: "utf8_general_ci"
})
}
static associate(db){
db.User.hasMany(db.Post, {foreignKey : "user_id", sourceKey: "id"})
}
}
module.exports = User;
5. models/posts.js_sequelize의 Post의 모델을 설정한다.
- belongsTo 메서드를 이용하여 참조키 user_id에 User테이블의 id 칼럼을 참조한다.
const Sequelize = require("sequelize");
class Post extends Sequelize.Model{
static init(seq){
return super.init({
title : {
type: Sequelize.STRING(20),
allowNull: false
},
content : {
type : Sequelize.STRING(400),
allowNull : false
}
}, {
sequelize : seq,
timestamps : true,
underscored : false,
modelName : "Post",
tableName: "posts",
paranoid: false,
charset: "utf8",
collate: "utf8_general_ci"
})
}
static associate(db){
db.Post.belongsTo(db.User, {foreignKey: "user_id", targetKey:"id"})
}
}
module.exports = Post;
6. models/index.js_Sequelize 객체를 생성하고, Post와 User의 테이블을 담아 내보낸다.
const Sequelize = require("sequelize");
const config = require("../config");
const User = require("./users");
const Post = require("./posts")
const sequelize = new Sequelize(
config.dev.database,
config.dev.username,
config.dev.password,
config.dev
)
const db = {};
db.sequelize = sequelize;
db.User = User;
db.Post = Post;
// 테이블 초기화
User.init(sequelize);
Post.init(sequelize);
User.associate(db)
Post.associate(db)
module.exports = db;
7. signUpController.js_회원가입시 db의 User테이블에 데이터를 추가한다.
- 중복회원 가입을 막기위해 유저의 아이디는 고유값이어야 한다.
- findOne 메소드를 이용해 유저가 가입하고자 하는 아이디가 존재하는지 확인한다.
- 같은 값이 있으면 찾아진 값이 반환되므로, 같은 값이 없다면 null이 뜬다.
const { User } = require("../models");
const bcrypt = require("bcrypt");
exports.SignUp = async(req,res)=>{
try {
const { name, age, user_id, user_pw } = req.body;
const user = await User.findOne({where : {user_id}});
if(user != null){
// 유저가 조회된 것. 중복회원 가입을 막는다.
return res.send("이미 사용중인 아이디 입니다.");
}
const hash = bcrypt.hashSync(user_pw, 10);
await User.create({
name,
age,
user_id,
user_pw : hash
})
res.redirect("http://127.0.0.1:5500/frontEnd/login.html")
} catch (error) {
console.log(error);
}
}
res.redirect("http://127.0.0.1:5500/frontEnd/login.html")
- 프론트단에서 ejs 사용하면 res.redirect("/login") 형식으로 작성을 하게 되는데 html로 작업을 할 때와, ejs로 작업을 할 때 경로 설정에서 왜 이런 차이가 발생하는지 궁금해졌다.
결론적으로,
ejs는 서버에서 동적으로 생성되는 HTML페이지이고,
HTML은 정적인 클라이언트 측 리소스이므로, 완전한 URL 주소를 제공해주어야 한다는 것이다.
ejs의 경우
- ejs는 서버단의 코드와 함께 사용되며, 서버에서 렌더링 되어 클라이언트에게 전해진다.
html의 경우
- 서버의 경로와 상관없이, 서버에서 클라이언트로 직접 제공된다.
- 따라서 클라이언트 측에서 해당 파일을 찾을 수 있는 정확한 경로를 제공해야 하므로, 완전한 주소를 입력해야한다.
8. loginController.js_로그인 시, db에 저장되어 있는 id, pw를 유저가 입력한 내용과 비교한다.
- 사용자가 입력한 아이디 값과, User테이블에 저장된 내용 중 user_id 컬럼에서 같은 값을 가지고 있는 row가 있는지 확인한다.
- bcrypt의 compareSync를 사용하여, 유저가 입력한 패스워드와 User테이블에서 같은 id값으로 가진 비밀번호가 같은 값인지 비교한다.
- 만약 아이디와 비밀번호가 동일하다면, 세션에 토큰을 발급해주며, 로그인을 성공시킨다.
const { User } = require("../models");
//로그인 bcrypt jsonwebtoken
const bcrypt = require("bcrypt");
const jwt = require("jsonwebtoken");
exports.Login = async(req,res)=>{
try {
const { user_id, user_pw } = req.body;
console.log(user_id,user_pw);
const user = await User.findOne({where : {user_id}});
if(user == null){
return res.send("가입 안한 아이디임");
}
const same = bcrypt.compareSync(user_pw, user.user_pw);
const { name, age, id } = user;
if(same){
let token = jwt.sign({
name,
age,
id
}, process.env.ACCESS_TOKEN_KEY, {
expiresIn : "20m"
});
req.session.access_token = token;
// ' / ': 여기서 경로는 백엔드의 도메인 경로 루트이다.
// 때문에프론트의 경로를 작성해주자
// 이런 경우는 배포된 프론트의 경로이다.
return res.redirect("http://127.0.0.1:5500/frontEnd/main.html")
// 이렇게 리다이렉트를 할 게 아니면 프론트에서 응답 받은 값으로
// 조건 분기 처리해서 페이지를 전환시켜주면 된다.
// return res.send("로그인 완료");
}
else{
return res.send("비밀번호 틀림");
}
} catch (error) {
// console.log("난 이키보드 좋던뎅 ㅎ");
// console.log("안녕하세요 쑤잌쑤잌 아직안옴 판씨가 가지고있음 ㅎ");
console.log(error)
}
}
- 미들웨어로 받은 현재 로그인한 유저의 값을 가져와 User테이블에서 findOne메소드를 사용하여 해당 유저의 정보를 res.json으로 클라이언트 단으로 보내준다.
// 유저 정보를 확인할 수 있게함
exports.viewUser = async(req,res)=>{
const { acc_decoded } = req;
console.log("변경????????????????????",acc_decoded);
const user = await User.findOne({
// where : {name : acc_decoded.name} => where : {id : acc_decoded.id}
// userUpdate를 실행하면 User테이블의 name값이 변경되어 로그인 시 지장?생성?저장?된 acc_decoded의 name값과
// 새로 저장한 User테이블의 name값과 달라져 찾을 수 없으므로 고정값인 id로 변경하여 오류를 해결! 아현성생님
where : {id : acc_decoded.id}
})
// json 형태로 데이터를 응답
res.json({user})
}
- 현재 로그인한 유저의 정보를 변경한다.
- 미들웨어로 현재 로그인한 유저의 값을 가져와 where로 User테이블에서 해당 유저의 정보를 찾고 클라이언트 단에서 수정을 위해 입력한 유저의 이름과 나이의 값을 User.update 메소드를 사용하여 수정한다.
exports.userUpdate = async(req,res)=>{
const {acc_decoded} = req;
const {user_name, user_age} = req.body;
const user = await User.update(
{name: user_name, age: user_age},
{where : {id: acc_decoded.id}}
)
// console.log("----------------",user);
res.redirect("http://127.0.0.1:5500/frontEnd/mypage.html")
}
9. postController.js_모든 게시글을 조회, 내가 쓴 게시글 조회, 게시글 등록, 게시글 수정, 게시글 삭제에 관한 함수를 설정한다.
const { Post, User } = require("../models");
exports.PostAll = async(req,res)=>{
const post = await Post.findAll({})
res.json(post)
}
exports.myPost = async(req,res)=>{
const { acc_decoded } = req;
const post = await Post.findAll({where : {user_id : acc_decoded.id}})
res.json(post)
}
exports.PostCreate = async(req,res)=>{
const { acc_decoded } = req;
// console.log("acc_decoded",acc_decoded);
const { title, content } = req.body;
// console.log("%%%%%%%%%%%%%%%",title, content);
await Post.create({
title: title,
content: content,
user_id: acc_decoded.id
})
return res.redirect('http://127.0.0.1:5500/frontEnd/main.html')
}
exports.PostUpdate = async (req,res)=>{
const { id } = req.params;
const { postT, postC } = req.body;
await Post.update({title:postT, content: postC}, {where:{id : id}})
res.redirect('http://127.0.0.1:5500/frontEnd/main.html')
}
exports.PostDel = async (req,res)=>{
const { id } = req.params;
await Post.destroy({where: {id:id}})
res.redirect('http://127.0.0.1:5500/frontEnd/main.html')
}
10. loginMiddleware.js_로그린이 정상적으로 이루어지고 있는지 확인하는 미들웨어를 설정한다.
const jwt = require("jsonwebtoken");
exports.isLogin = (req,res,next) =>{
const {access_token} = req.session;
console.log("미들웨어", access_token);
jwt.verify(access_token, process.env.ACCESS_TOKEN_KEY, (err, acc_decoded)=>{
if(err){
res.send("다시 로그인 해주세요");
}
else{
req.acc_decoded = acc_decoded;
// 다음 미들웨어 실행
next();
}
})
}
11. app.js_각 라우터별로 기본 경로를 설정한다.
- cors로 허용해준 http://127.0.0.1:5500 경로는 기본경로이며, 도메인이다.
- http://127.0.0.1:5500/signUp : 회원가입 페이지로 이동된다.
- http://127.0.0.1:5500/login: 로그인 페이지로 이동된다.
- http://127.0.0.1:5500/Post: 게시판 페이지로 이동된다.
const loginRouter = require("./routers/login");
const signUpRouter = require("./routers/signUp");
const postRouter = require("./routers/Post");
app.use("/signUp", signUpRouter);
app.use("/login", loginRouter);
app.use("/Post", postRouter);
12. routers/signUp.js_http://127.0.0.1:5500/signUp ~ 접속 시, 컨트롤러를 연결한다.
- http://127.0.0.1:5500/signUp 으로 접속 시, signUpController의 Signup 컨트롤러가 실행된다.
const router = require("express").Router();
const { SignUp } = require("../controllers/signUpController");
router.post("/", SignUp);
module.exports = router;
13. routers/signUp.js_http://127.0.0.1:5500/login ~ 접속 시, 컨트롤러를 연결한다.
- http://127.0.0.1:5500/login으로 접속 시, loginController의 Login 컨트롤러가 실행된다.
- http://127.0.0.1:5500/login/view 와 http://127.0.0.1:5500/login/update 를 접속 시, isLogin 미들웨어를 실행 후, 각 컨트롤러의 함수를 실행한다.
const router = require("express").Router();
const { Login, viewUser, userUpdate } = require("../controllers/loginController");
const { isLogin } = require("../middleware/loginMiddleware")
router.post('/',Login);
router.get('/view', isLogin, viewUser);
router.post('/update', isLogin, userUpdate);
module.exports = router;
14. routers/signUp.js_http://127.0.0.1:5500/Post ~ 접속 시, 컨트롤러를 연결한다.
const router = require("express").Router();
const { PostAll, PostCreate, myPost, PostUpdate, PostDel } = require("../controllers/postController");
const { isLogin } = require("../middleware/loginMiddleware");
router.get('/posts', isLogin, PostAll);
router.get('/myposts', isLogin, myPost);
router.post('/', isLogin, PostCreate)
router.post('/postupdate/:id', isLogin, PostUpdate);
router.post('/postDelete/:id', isLogin, PostDel);
module.exports = router;
15. frontEnd/signUp.html_회원가입 페이지를 작성한다.
- 프론트 단에서 데이터를 get / post 할 때, 요청 경로를 서버를 열어주었던 주소로 설정하면 된다.
- 지금의 경우, app.js에서 8080으로 서버를 열었으므로, http://127.0.0.1:8080 ~ 가 되는 것이다.
<!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>
<h1>회원가입</h1>
<!-- 프론트랑 백을 나누었기 때문에 백엔드의 경로를 적어주어야 한다. -->
<!-- 백엔드를 배포해놨다고 가정할 때 백엔드의 요청 경로를 작성 해주면 된다. -->
<!-- action 속성에 요청 url을 작성한다. -->
<!-- 예를 들어, http://do.con/login -->
<form action="http://127.0.0.1:8080/signUp" method="post">
<input type="text" name="name" placeholder="이름"> <br>
<input type="text" name="age" placeholder="나이"> <br>
<input type="text" name="user_id" placeholder="아이디"> <br>
<input type="password" name="user_pw" placeholder="비밀번호"> <br> <br>
<button>회원가입</button>
</form> <br>
<a href="http://127.0.0.1:5500/frontEnd/login.html">로그인</a>
</body>
</html>
16. frontEnd/main.html_axios로 전체 게시글 내용과 로그인한 유저의 정보를 가져온다.
async function getAPI(){
try {
const {data} = await axios.get('http://127.0.0.1:8080/login/view',{
// 브라우저가 쿠키를 서버로 전달할 수 있는 옵션
withCredentials : true
})
const data2 = await axios.get('http://127.0.0.1:8080/Post/posts',{
// 브라우저가 쿠키를 서버로 전달할 수 있는 옵션
withCredentials : true
})
user_name.innerHTML = data.user.name;
user_age.innerHTML = data.user.age;
isLoading.classList.add("disable");
console.log("정보",data2.data);
let postArr = Object.entries(data2.data);
console.log("#################",postArr)
postArr.forEach((el, index) => {
console.log("sfsdfsdfsdf",el)
posts.innerHTML += `
<div>
제목: ${el[1].title} <br>
내용: ${el[1].content}
<br><br><br>
</div>
`
});
// console.log("정보",data.user.name);
} catch (error) {
console.log(error)
}
}
getAPI();
728x90
'블록체인_9기 > 💚 Node.js' 카테고리의 다른 글
41강_230531_Node.js(호스팅, 클라우드 서비스 모델(Iaas, Paas, SaaS), AWS 배포) (1) | 2023.06.02 |
---|---|
40강_230530_Node.js(multer) (0) | 2023.06.02 |
38강_230525_Node.js(socket을 이용하여 채팅방 만들기) (0) | 2023.06.02 |
37강_230524_Node.js(web socket, socket.io) (0) | 2023.05.31 |
34강_230519_(ORM, Sequelize, ajax, Sequelize모델, 모델간의 관계) (0) | 2023.05.24 |