이 게시물에는 코드작성이 포함되어 있습니다. 소스코드를 받으신 후 진행해 주세요. MEAN Stack/개발 환경 구축에서 설명된 프로그램들(git, npm, atom editor)이 있어야 아래의 명령어들을 실행할 수 있습니다.
이 게시물의 소스코드는 게시판 만들기(2016) / 게시판 - Post-User 관계(relationship) 만들기에서 이어집니다.
board.git 을 clone 한 적이 있는 경우: 터미널에서 해당 폴더로 이동 후 아래 명령어들을 붙여넣기합니다. 폴더 내 모든 코드가 이 게시물의 코드로 교체됩니다. 이를 원치 않으시면 이 방법을 선택하지 마세요.
board.git 을 clone 한 적이 없는 경우: 터미널에서 코드를 다운 받을 폴더로 이동한 후 아래 명령어들을 붙여넣기하여 board.git 을 clone 합니다.
- Github에서 소스코드 보기: https://github.com/a-mean-blogger/board/tree/92c43d33481edbdb223eba41d3405d4cb161c46f
/* * 이 강의는 2020년 버전으로 update되었습니다. -> 2020년 버전 보기 */
이제까지의 강의로 웹사이트가 현재 로그인된 사용자를 기억하며, 게시물 작성시 작성자를 기록하게 되었습니다.
이번 강의에서는 로그인을 하지 않았을 경우 글 작성을 제한하고, 자신이 작성한 글이 아닌 경우에는 글의 수정 및 삭제를 제한하며, 마지막으로 자신이 아닌 사용자 정보의 수정을 제한하도록 합니다.
접근제한은 두가지 단계로 이루어 지는데, 첫번째로 front-end에서의 해당 버튼들이 보이지 않게하여 아예 접근이 불가능하도록 해야 하며, 두번째로 back-end에서 해당 요청이 오는 경우 사용자를 비교하여 error를 return하도록 해야 합니다.
// util.js util.isLoggedin = function(req, res, next){ if(req.isAuthenticated()){ next(); } else { req.flash("errors", {login:"Please login first"}); res.redirect("/login"); } } util.noPermission = function(req, res){ req.flash("errors", {login:"You don't have permission"}); req.logout(); res.redirect("/login"); }
모든 route에서 공용으로 사용될isLoggedin
, noPermission
함수를 util.js에 만듭니다.
isLoggedin
은 사용자가 로그인이 되었는지 아닌지를 판단하여 로그인이 되지 않은 경우 사용자를 에러 메세지("Please login first")와 함께 로그인 페이지로 보내는 함수입니다.
route에서 callback으로 사용될 함수이므로req
, res
, next
를 받습니다. 로그인이 된 상태라면 다음 callback함수를 호출하게 되고, 로그인이 안된 상태라면 로그인 페이지로 redirect합니다.
noPermission
은 어떠한 route에 접근권한이 없다고 판단된 경우에 호출되어 에러 메세지("You don't have permission")와 함께 로그인 페이지로 보내는 함수입니다.req
, res
가 있지만 callback으로 사용하지는 않고 일반 함수로 사용할 예정입니다. isLoggedin
과 다르게 접근권한이 있는지 없는지를 판단하지는 않는데, 상황에 따라서 판단 방법이 다르기 때문입니다.
// routes/posts.js var express = require("express"); var router = express.Router(); var Post = require("../models/Post"); var util = require("../util"); // Index ... // New router.get("/new", util.isLoggedin, function(req, res){ // 2 // ... }); // create router.post("/", util.isLoggedin, function(req, res){ // 2 // ... }); // show ... // edit router.get("/:id/edit", util.isLoggedin, checkPermission, function(req, res){ // 2, 3 // ... }); // update router.put("/:id", util.isLoggedin, checkPermission, function(req, res){ // 2, 3 // ... }); // destroy router.delete("/:id", util.isLoggedin, checkPermission, function(req, res){ // 2, 3 // ... }); module.exports = router; // private functions // 1 function checkPermission(req, res, next){ Post.findOne({_id:req.params.id}, function(err, post){ if(err) return res.json(err); if(post.author != req.user.id) return util.noPermission(req, res); next(); }); }
1. Post에서checkPermission
함수는 해당 게시물에 기록된 author와 로그인된 user.id
를 비교해서 같은 경우 통과, 만약 다르다면 util.noPermission
함수를 호출합니다.
2. new, create, edit, update, destroy에 util.isLoggedin
를 사용해서 로그인이 된 경우에만 다음 callback을 호출합니다.
3. edit, update, destroy에 checkPermission
를 사용해서 본인이 작성한 글에만 다음 callback을 호출합니다.
// routes/users.js var express = require("express"); var router = express.Router(); var User = require("../models/User"); var util = require("../util"); // Index router.get("/", util.isLoggedin, function(req, res){ // 1 // ... }); // New ... // create ... // show router.get("/:username", util.isLoggedin, function(req, res){ // 1 // ... }); // edit router.get("/:username/edit", util.isLoggedin, checkPermission, function(req, res){ // 1, 3 // ... }); // update router.put("/:username", util.isLoggedin, checkPermission, function(req, res, next){ // 1, 3 // ... }); module.exports = router; // private functions // 2 function checkPermission(req, res, next){ User.findOne({username:req.params.username}, function(err, user){ if(err) return res.json(err); if(user.id != req.user.id) return util.noPermission(req, res); next(); }); }
1. User에서checkPermission
함수는 해당 user의 id와 로그인된 user.id
를 비교해서 같은 경우 통과, 만약 다르다면 util.noPermission
함수를 호출합니다.
2. index, show, edit, update에util.isLoggedin
를 사용해서 로그인이 된 경우에만 다음 callback을 호출합니다.
3. edit, update에 checkPermission
를 사용해서 본인의 정보에만 다음 callback을 호출합니다.
<!-- views/posts/index.ejs --> <div class="buttons"> <% if(isAuthenticated){ %> <!-- 1 --> <a class="btn btn-default" href="/posts/new">New</a> <% } %> <!-- 1 --> </div>
1. index.ejs에서 new 버튼은 로그인된 경우에만 보이게 됩니다. 참고로isAuthenticated
는 게시판 - Login 기능 추가강좌에서 index.js속에 만들었던 함수입니다. req.locals
에 들어있어서 ejs에서 바로 사용할 수 있습니다.
<!-- views/posts/show.ejs --> <div class="buttons"> <a class="btn btn-default" href="/posts">Back</a <% if(isAuthenticated && currentUser.id == post.author.id){ %> <!-- 1 --> <a class="btn btn-default" href="/posts/<%= post._id %>/edit">Edit</a> <form action="/posts/<%= post._id %>?_method=delete" method="post"> <a class="btn btn-default" href="#" onclick="confirm('Do you want to delete this?')?this.parentElement.submit():null;">Delete</a> </form> <% } %> <!-- 1 --> </div>
1. 로그인이 된 상태이고, 게시물의 작성자 id(post.author.id
)와 현재 로그인된 사용자의 id(currentUser.id
)가 일치하는 경우에만 edit, delete 버튼을 보여줍니다. currentUser
역시 req.locals
에 들어있어서 ejs에서 바로 사용할 수 있습니다.
<!-- views/users/show.ejs --> <div class="buttons"> <a class="btn btn-default" href="/users">Back</a> <% if(isAuthenticated && currentUser.id == user.id){ %> <!-- 1 --> <a class="btn btn-default" href="/users/<%= user.username %>/edit">Edit</a> <% } %> <!-- 1 --> </div>
1. 로그인이 된 상태이고, 해당 user id(user.id
)와 현재 로그인된 사용자의 id(currentUser.id
)가 일치하는 경우에만 edit 버튼을 보여줍니다.
index view에 new 버튼이 없습니다.
show view에 edit, delete 버튼이 없습니다.
new 버튼이 보입니다.
자신이 작성한 게시물의 경우 edit, delete 버튼이 보입니다.
로그인 하지 않은 상태에서 강제로 /posts/new로 가려고 하는 경우,
에러메세지와 함께 로그인화면으로 redirect됩니다.
이제 1. data의 CRUD 구현, 2. data 간의 관계 형성, 3. 로그인 구현, 4. 접근 제한까지 웹사이트 제작의 큰 틀은 모두 익히셨습니다!
나머지는 위의 내용의 확장 혹은 일반적인 문제해결 들입니다.(예를 들어 댓글기능, 검색기능, 관리자 기능 등등.. 지금까지 배운 내용을 응용하고 일반적인 프로그래밍 방법을 활용하면 구현가능합니다.)
게시판이 완벽하진 않지만 이 강좌는 위 4가지 주제를 익히기 위한 것으로 게시판 강좌는 여기까지 입니다. 나중에 기회가 있다면 게시판을 완전히 완성하는 강좌를 따로 해보도록 하겠습니다.
수고하셨습니다!
댓글
이 글에 댓글을 다시려면 SNS 계정으로 로그인하세요. 자세히 알아보기