26~27강. 사용자 인증하기, 블로그 앱 만들기 - 레이아웃

한시현·2024년 4월 8일

UDR 백엔드 야생형

목록 보기
12/15

Section 3.

26강. 사용자 인증하기

사용자 인증하기

사용자가 db에 저장되어 있는지, 되어 있다면 연락처 목록을 보여주고, 그렇지 않은 사람이면 로그인에 실패했다고 연결해 줄 것이다. 이 때 사용하는 방법이 여러가지가 있는데, 여기에서는 JWT 라는 것을 이용해 볼 것입니다.

JWT (JSON Web Token)

JWT 토큰의 형태는 마침표를 기준으로 크게 3가지로 나뉘어져 있다.
헤더에는 토큰이 어떤 알고리즘을 사용해서 암호화시켰는지가 나온다.
페이로드에는 실제 암호화된 내용(로그인 하겠다고 보낸 내용)이 들어가게 된다.
서명비밀키라고 생각하면 된다. 토큰을 사용할 수 있는 사람인지 아닌지를 이 비밀키로 확인한다.

토큰 방식이란

JWT라는 토큰을 사용해서 어떤 순서로 사용자를 인증하는지 간략하게 보도록 하자.

쿠키 : 웹사이트에서 사용자가 접속했을 때 사이트에서 사용자 컴퓨터로 보내는 작은 텍스트 조각

쿠키는 사용자가 사이트에 몇 번 접속했는지, 어디서 접속했는지 또는 그 외 여러 정보들을 텍스트 형태로 만들어 웹브라우저에 저장한다. 사용자가 사이트에 재접속 하면 쿠키 정보를 가져다가 쓰는 것이다. JWT 토큰 역시 클라이언트로 넘겨지면서 쿠키에 저장된다.
그 후 클라이언트가 서버에게 요청 시 쿠키를 뒤져서 토큰이 있는지 확인하고 그 토큰을 함께 서버로 보내게 된다(5).
서버에서는 그 토큰이 내가 발급한 토큰이 맞는지 확인한다(6).

JWT로 사용자 인증 방법

클라이언트에서 서버로 사용자를 인증해달라는 요청이 오면 JWT.sign함수를 이용해서 토큰을 만든다.
토큰을 만들 때 첫 번째 인자는 페이로드(암호화될 내용), 두 번째는 비밀키(내가 발급한 토큰인지 아닌지를 구별하기 위함), 세 번째는 옵션이나 콜백이 오는데 이는 생략할 수도 있다.

토큰을 만든 후 서버에서 클라이언트로 토큰을 전송하고, 클라이언트는 토큰을 쿠키에 저장한다. 이후 사용자 인증 필요한 요청을 보낼 때 클라이언트에서 쿠키를 뒤져서 토큰을 함께 전송한다. 서버에서는 비밀키를 통해 토큰을 검증한다.

클라이언트에서 id, pw를 입력하고 로그인을 눌러서 서버쪽으로 요청을 보내면, 서버에서는 위 과정을 거치고 나서(id, pw가 모두 일치한다면) 사용자 인증이 끝났다는 토큰을 만든다.

이제 직접 코딩을 해보며 알아보자.
코딩에 앞서서 우리가 JSON Web Token이라는 모듈을 사용해야 하고, 쿠키에서 토큰을 가져와야 하므로 쿠키 정보를 읽어주는 cookie-parser 모듈을 설치해 줘야 한다.

모듈 설치 : cookie-parser, jsonwebtoken

npm i cookie-parser jsonwebtoken

JWT는 헤더, 페이로드와 서명, 비밀키 세 가지 요소로 구성된다고 했는데 우선 비밀키가 있어야 한다. 서버에서 내가 발행한 토큰이 맞는지 확인하는 용도로만 사용되므로, 어떤 내용을 사용해도 상관 없다. 다만, 외부로 드러나면 안된다.
그렇기 때문에 .env 파일에 환경 변수로 넣어놓고 가져다가 사용한다.

.env

JWT_SECRET = 12345

(값은 어떤 내용이어도 상관 없다.)

컨트롤러로 돌아와서 .env 파일에서 환경 변수를 가져와야 하기 때문에 .env를 실행하고 환경 변수를 가져와야 한다.

loginController.js 추가

require("dotenv").config();
const jwtSecret = process.env.JWT_SECRET;
const jwt = require("jsonwebtoken");

.env 파일에 있던 JWT_SECRET라는 환경 변수 값을 가져와서 jwtSecret이라는 변수에 할당해주게 된다.
JWT도 가져오자.

이제 토큰을 발행해보자. id와 pw를 서버로 post 요청한 부분을 수정하자.

loginController.js 수정

const loginUser = asyncHandeler(async(req, res) => {
    const { username, password } = req.body;
    const user = await User.findOne({username}); // 사용자 이름을 이용해서 데이터베이스에서 찾아온다.
    if (!user) {
        return res.json({message : '일치하는 사용자가 없습니다.'}); // user 없을 시 출력
    }
    const isMatch = await bcrypt.compare(password, user.password); // user 있을 시 db에 저장된 pw와 입력한 pw를 비교한다.
    if (!isMatch) {
        return res.json({message : '비밀번호가 맞지 않습니다.'}); // pw 틀릴 시 출력
    }
    // 위 조건 만족 시 토큰 생성
    const token = jwt.sign({id: user._id}, jwtSecret); // (id 값, 비밀키) 넣는다.
    res.cookie("token", token, {httpOnly: true}); // (쿠키에 저장할 이름, 저장할 내용, http 프로토콜을 통해서만 접속할 수 있게 함)
     // 쿠키에 저장해야 하므로 토큰을 응답에 담아서 보낸다.
    res.redirect("/contacts"); // contact 경로로 get 요청을 보내도록 한다.
});

이제 실행해보자.

로그인 정보를 입력 후 로그인하면

이렇게 바로 연락처 목록으로 넘어간다. 여기서 우리는 입력한 정보가 사용자 db 안에 들어 있었고, 비교했을 때 id와 pw가 모두 일치해서 서버에서 토큰을 발행해 줬다는 걸 알 수 있다.

그렇다면 클라이언트에 정말 토큰이 저장이 되어 있는지 확인해 보겠다.

해당 커맨드를 이용해서 웹 개발자 도구을 열자.

이렇게 애플리케이션에서 쿠키를 확인하면 우리가 token이라는 이름으로 저장한 토큰에 해당하는 값이 보인다.
아래를 보면 마침표(.)를 기준으로 세 영역으로 나눠져 있는 것을 확인할 수 있다. 헤더, 페이로드, 비밀키가 있는 것이다.

사용자 인증 후 서버에서 토큰을 만들어주고, 그 토큰을 클라이언트로 보내서 쿠키에 저장한다는 것을 알 수 있다.
이외에도 이 쿠키와 토큰을 이용해서 애플리케이션 안에서 계속 로그인을 체크할 수 있는 미들웨어도 만들 수 있다.

Section 4.

27강. 블로그 앱 만들기 - 레이아웃

블로그 애플리케이션 - 레이아웃 만들기

지금까지 공부했던 Node.js와 Express를 사용해서 블로그 애플리케이션을 만들어 보자.

블로그 애플리케이션 기획하기

애플리케이션을 제작하기 전 생각해야 할 것은 누가 이 애플리케이션을 사용하는지 결정하는 것이다.

우리가 만들어 볼 블로그 애플리케이션은 아래처럼 구성해 볼 것이다.

일반 사용자 기능

관리자 기능

이제 새로운 애플리케이션을 만들기 때문에 새 작업 폴더를 만들어야 한다.
(Do it! Node.js의 실습폴더 myblog을 열자)

가장 먼저 터미널을 열고, package.json을 만들어 줘야 하므로 초기화해주자.

npm init -y

-y를 붙여주면 옵션마다 들어가는 항목을 직접 입력하지 않고 기본값이 들어간다.

만들어진 package.json

{
  "name": "myblog",
  "version": "1.0.0",
  "description": "",
  "main": "app.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "keywords": [],
  "author": "",
  "license": "ISC"
}

메인 파일을 index.js가 아니라 app.js 라고 해주자.

express를 사용할 것이므로 express 모듈과 .env 모듈을 설치해주자.

모듈 설치 : express, dotenv

npm i express dotenv

package.json에서 해당 모듈들이 설치된 것을 확인할 수 있다.

이제 간단하게 서버를 만들어 보겠다. 새 파일 app.js를 만들자.

app.js

require("dotenv").config();
const express = require("express");

const app = express();
const port = process.env.PORT || 3000;
// env 파일에 포트가 지정되어있으면 그 포트 번호를 가져다 쓰고, 없다면 3000번이란 포트를 쓰겠다.

app.get("/", (req,res) => {
    res.send("Hello World!");
})

app.listen(port, () => {
    console.log(`App listening on port ${port}`); // 몇 번 포트에서 실행중인지 알려준다.
})

우리는 아직 env 파일을 만들지 않았기 때문에 여기서는 3000번 포트를 사용하게 될 것이다.
누군가가 이 코드를 이용해서 앱을 만든다고 할 때, 그 사람은 env 파일을 따로 만들어서 포트 번호를 지정할 수 있다.

서버가 잘 동작하는지 확인해보자.

nodemon app 실행 시

App listening on port 3000

썬더 클라이언트로 가서 send로 보내보자.

서버가 정상적으로 동작하고 있다는 것을 확인 할 수 있다.

get 요청 방식을 처리하는 부분을 삭제하고 라우트 코드를 만들어서 직접 연결해 보도록 하자.
라우트 코드와 관련된 파일을 저장할 routes 폴더를 만들자.

이 routes 폴더 안에 main.js 파일을 만들고, app.js의 get부분을 잘라서 main에 붙여넣자.
그 후 코드를 좀 더 추가하겠다.

main.js

const express = require("express");
const router = express.Router();

app.get("/", (req,res) => {
    res.send("Hello World!");
})

module.exports = router;

이렇게 라우터를 만들고 밖으로 내보내 줬다.
이제 이 라우터를 사용하겠다고 app.js에게 알려주자.

app.js 코드 추가

app.use("/", require("./routes/main"));

필요한 폴더 만들기

작업 폴더에 몇 가지 폴더를 만들어 보자.

위 사진에서 설명이 부족한 폴더 용도를 좀 더 보충하자면,
/models : db에서 스키마, 모델링 담당
/public : css, js 이미지 등 정적 파일을 넣어둠

이제 ejs 엔진의 레이아웃을 한번 공부해 보도록 하겠다.

기본 레이아웃 만들기

이전에는 ejs 파일에서 중복되는 헤더와 푸터 부분을 모듈로 만들어 include 시켜서 사용했다면, 이번에 볼 레이아웃은 틀을 전체적으로 만들고 내용이 바뀌는 부분만 변경을 해준다.

위의 사진이 우리가 최종적으로 만들게 될 모습인데, 위에 헤더 부분이 있고, 아래에는 내용이 바뀌는 부분이 있다.
위에서 반복되는 부분을 레이아웃 파일로 저장해놓고 바뀌는 부분만 가져다 끼워 넣을 것이다.
이 방법은 레이아웃이 중심이 되고, 레이아웃 안에 바뀌는 내용을 끼워 넣는 형태이다.

ejs 모듈을 사용할 것이기 때문에 ejs 모듈도 설치를 해 주고, 레이아웃을 위한 모듈도 설치해 줄 것이다.

모듈 설치 : ejs, express-ejs-layouts

npm i ejs express-ejs-layouts

express-ejs-layouts 모듈은 익스프레스에서 ejs 레이아웃을 사용하기 위한 모듈이다.

설치 후에 app.js에서 이 모듈을 사용할 것이라고 작성해줘야 한다.

app.js 코드 추가

const expressLayouts = require("express-ejs-layouts")
app.use(expressLayouts);
app.set("view engine", "ejs");
app.set("views", "./views");

이 코드를 통해 expressLayouts을 사용, view engine은 ejs를 사용할 것이다. 즉, 템플릿 파일은 view라는 폴더 안에 저장해 놓겠다고 작성했다.

이제 레이아웃 파일을 만들어보자.
블로그 애플리케이션에서는 일반 사용자와 관리자로 로그인 했을 때 보는 서로 다른 레이아웃 2가지가 있을 것이다.
레이아웃은 다른 ejs 파일과 구별이 쉽도록 layouts라는 폴더를 만들어서 그곳에 저장을 하게 된다. views 폴더 안에 layouts 폴더를 새로 만들자.
그리고 layouts 폴더에 main.ejs라는 레이아웃을 하나 생성하자.

! + enter 를 누르면 vscode에서 웹 문서를 위한 기본 코드를 제공한다.

기본적인 형태의 웹 문서 (! + Enter)

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>
<body>
    
</body>
</html>

이것을 우리가 레이아웃으로 사용 해 볼 것이다. 레이아웃에서 내용이 바뀌는 부분은 body 부분에 넣어 보겠다.

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>
<body>
    <%- body %> // 이렇게 지정
</body>
</html>

ejs 태그를 사용해서 이렇게 지정 해 주면 이 태그가 있는 부분이 변경되는 내용이 표시되는 것이다. 어떻게 그렇게 되는지 알아보자.

일단 이 웹문서에서 ejs 태그 넣은 부분에 바뀌는 내용만 넣어 달라고 지정하자.

home으로 요청하면 index.ejs 파일을 넣어서 보여 줄 것이고, about으로 요청하면 about.ejs 파일을 넣어서 보여 줄 것이다.

index.ejs와 about.ejs를 만들어보자.

views 폴더에 파일을 생성하자. 이 때 layouts 폴더에 들어가지 않도록 주의해야 한다.

index.ejs

<h1>Home</h1>

여기엔 간단하게 Home이라는 제목 하나만 표시할 것이다.

about.ejs

<h1>About</h1>

마찬가지로 이렇게 간단하게 표시할 것이다.

이제 요청 경로에 따라 파일들을 보여달라고 해야 하기 때문에 라우트 코드를 작성해야 한다.

main.js

우리는 ejs 파일을 보여줘야 하기 때문에 렌더링을 해 줄 것이다.
이 때 index.ejs 파일에는 내용만 있기 때문에 여기에 레이아웃을 같이 포함시켜 줘야 한다.

const express = require("express");
const router = express.Router();
const mainLayout = "../views/layouts/main.ejs"; // layout 경로

router.get("/home", (req,res) => {
    res.render("index", {layout: mainLayout})
})

router.get("/about", (req,res) => {
    res.render("about", {layout: mainLayout})
})

module.exports = router;

home 경로로 get 요청이 들어왔을 때 mainLayout을 레이아웃으로 사용해서 index.ejs를 표시 해달라는 뜻이다.
같은 방법으로 about에 대해서도 저장할 수 있겠다.

잘 작동하는지 확인해보자.

ejs 태그를 제외한 나머지 부분이 layout이다. ejs 파일과 레이아웃이 제대로 적용된 것을 볼 수 있다. about의 경우도 마찬가지이다.

이렇게 레이아웃을 사용할 때는 렌더링할 ejs 파일과 적용할 레이아웃 파일 이렇게 지정해 주면 된다.

한가지 추가로 설명하자면 요청 경로가 다 다르지만 같은 index.ejs 파일을 보여주고 싶을 경우,

router.get(["/", "/home"], (req,res) => {
    res.render("index", {layout: mainLayout})
})

이렇게 대괄호로 묶고 /도 같이 표현해주면 된다.
이러면 /로 요청할 때도 index.js 를 렌더링하고, home으로 요청할 때도 그렇다.

이번에는 변수 값을 넘기는 경우를 살펴보겠다.
문서의 제목(브라우저 탭 부분)은 레이아웃의 title 태그에서 바꿀 수 있다. 이 부분을 index.ejs, about.ejs 에 따라 내용을 바꾸도록 하고 싶다.

이렇게 내용은 Home이라고 표시되지만 제목은 Document로 되어있는 것을 확인할 수 있다.

어떻게 바꿀까? 라우트 코드로 가서 요청 시 변수를 하나 넘겨주도록 코드를 갱신하겠다.

main.js

router.get(["/", "/home"], (req,res) => {
    const locals = {
        title: "Home"
    }
    res.render("index", {layout: mainLayout})
})

router.get("/about", (req,res) => {
    const locals = {
        title: "About"
    }
    res.render("about", {layout: mainLayout})
})

그리고 렌더링 할 때 변수를 넘겨주면 된다. ejs 파일로 변수를 넘기는 것은 {}안에 넣어주면 된다.

res.render("index", {locals: locals, layout: mainLayout})

이 때 넘겨주는 변수 이름이 현재 사용하는 변수와 이름이 같다면 다음과 같이 줄일 수 있다.

res.render("index", {locals, layout: mainLayout})

about도 똑같이 해주자.

router.get("/about", (req,res) => {
    const locals = {
        title: "About"
    }
    res.render("about", {locals: locals, layout: mainLayout})
})

이제 title 태그를 수정하자. 이는 레이아웃 안에 있다.

<title><%= locals.title %></title>

잘 작동하는지 확인해보자.

잘 작동하는 것을 볼 수 있다.

이렇게 레이아웃에서도 기존의 ejs 파일로 변수 넘겨주는 방식으로 똑같이 넘겨줄 수 있고, 그것을 받아서 처리해 줄 수 있다.

0개의 댓글