Node.JS)04.25( ExpressServer - 라우터 분리, 쿠키/세션 암호화, nunjucks(넌적스)템플릿 )
Programming/JS

Node.JS)04.25( ExpressServer - 라우터 분리, 쿠키/세션 암호화, nunjucks(넌적스)템플릿 )

728x90

 

목차

 


 

2022.04.24 - [Programming/BACKEND] - Node.js)04.22( ExpressServer ( 기초, 라우터, 미들웨어, 추가모듈, 쿠키, 세션( express-session ), multer ) )

 

 

1. 라우터 분리

1.1. routers 폴더 생성

아래 이미지와 같이 폴더와 js파일을 구성한다.

 

1.1.1. routers/index.js

const express = require('express');

// const app = express();
const router = express.Router();

// app.get('/', (req, res)=>{});
router.get('/', (req, res)=>{
    res.send("<h1>Hello, Express router - index - '/'</h1>");
});

router.get('/about', (req, res)=>{
    res.send("<h1>Hello, Express router - index - '/about'</h1>");
});

module.exports = router;

 

1.1.2. routers/users.js

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

router.get('/', (req, res) => {
    res.send("<h1>Hello, Express router - USERS '/'</h1>");
});

router.get('/search', (req, res) => {
    res.send("<h1>Hello, Express router - USERS '/search'</h1>");
});

module.exports = router;

 


 

1.2. 라우터 설정

1.2.1. 라우터 설정

// 라우터 설정
const indexRouter = require('./routers');  //  index.js는 자동인식된다. (상대경로 ./)
const userRouter = require('./routers/users');

 

1.2.2. 분리된 라우터 연결

// 라우터 설정
const indexRouter = require('./routers');  //  index.js는 자동인식된다. (상대경로 ./)
const userRouter = require('./routers/users');

// 라우팅
// 현재 파일에서 사용한 '/' 와 indexRouter  에 있는 '/' 와 조합이 됩니다
// '//' 가 '/' 로 사용이 됩니다
app.use('/', indexRouter);   
// indexRouter의 '/' 요청 -> http://localhost:3000/
// indexRouter의 '/about' 요청 -> http://localhost:3000/about

// 현재 파일에서 사용한 '/users' 와 userRouter 에 있는 '/' 와 조합되어
// '/users/' 가 사용됩니다.
app.use('/users', userRouter);
// userRouter '/' 요청 -> http://localhost:3000/users
// userRouter '/search' 요청 -> http://localhost:3000/users/search

 


 

1.3. 전체 코드 ( app.js )

const express = require('express');

const app = express();
app.set('port', process.env.PORT || 3000);


// 라우터 설정
const indexRouter = require('./routers'); 
const userRouter = require('./routers/users'); 


// 라우팅
app.use('/', indexRouter);   

app.use('/users', userRouter);


app.listen(app.get('port'), () => {
    console.log(app.get('port'), '번 포트에서 대기 중');
});

'/' 일 경우 index.js
'/about'일 경우 index.js
'/users'일 경우 users.js
'/users/search'일 경우 users.js

 


2. 쿠키와 세션의 암호화

  • 쿠키와 세션은 '보안상 중요'하기 때문에 절대 소스코드에 노출하면 안 되고, 환경변수 파일에서 따로 관리하도록 한다.

 

2.1. dotenv 모듈

  • 디폴트로 현재 디렉토리에 위치한 .env 파일로 부터 환경 변수를 읽어냅니다.
  • 따라서, .env 파일을 생성하고, 그 안에 필요한 환경 변수를 키=값의 포멧으로 나열한다.
  • 최상위에 dotenv모듈을 임포트(require)한 후 config() 함수를 호출한다.
onst express = require('express');

// dotenv모듈 import
const dotenv = require('dotenv');  

// 비밀키 비공개를 위한 기본 환경 구성
dotenv.config();

 

2.1.1. .env 환경변수 파일 저장

COOKIE_SECRET=nodejsdotenv

 


 

2.2. 쿠키 / 세션 암호화 설정

// 쿠키 암호화(환경변수파일에서 키 받아옴)
app.use(cookieParser(process.env.COOKIE_SECRET));  // 추가3 

...

// 세션 설정
app.use(session({
    resave: false,
    saveUninitialized: false,
    //secret: 'nodejsdotenv', // 세션을 발급할 때 사용되는 키입니다.
    secret: process.env.COOKIE_SECRET,  // 세션 암호화(환경변수파일에서 키 받음)
    cookie: {
      httpOnly: true,
      secure: false,
    },
    name: 'session-cookie',
}));

secret : 세션을 발급할 때 사용되는 키 설정.

 


 

2.3. 전체 코드

2.3.1. app.js

const express = require('express');

// 추가모듈
const cookieParser = require('cookie-parser');
const session = require('express-session');
const dotenv = require('dotenv');  // 추가1
const path = require('path');

// 비밀키 비공개를 위한 기본 환경 구성
dotenv.config();  // 추가2  

const app = express();
app.set('port', process.env.PORT || 3000);

// 쿠키 암호화(환경변수파일에서 키 받아옴)
app.use(cookieParser(process.env.COOKIE_SECRET));  // 추가3 

app.use(express.json());
app.use(express.urlencoded({ extended: false }));

// 세션 설정
app.use(session({
    resave: false,
    saveUninitialized: false,
    //secret: 'nodejsdotenv', // 세션을 발급할 때 사용되는 키.
    secret: process.env.COOKIE_SECRET,  // 세션 암호화(환경변수파일에서 키 받음)
    cookie: {
      httpOnly: true,
      secure: false,
    },
    name: 'session-cookie',
}));


// 라우팅
app.get('/', (req, res)=>{
    if( req.session.userid ){
        res.send(`${req.session.userid} 님 반갑습니다.` + '<a href="/logout">로그아웃</a>');
    }else{
        res.sendFile(path.join(__dirname, '/login.html'));
    }
});

app.post('/login', (req, res)=>{
    const id = req.body.id;
    const pw = req.body.pw;
    if( id=='scott' && pw=='tiger'){
        req.session.userid = id;
        return res.send('ok'); 
    }else if(id!='scott'){
        return res.send('없는 아이디입니다');
    }else if(pw!='tiger'){
        return res.send('비밀번호가 맞지 않습니다');
    }else{
        return res.send('알수없는 이유로 로그인 안됩니다.');
    }
});

app.get('/logout', (req, res)=>{
    req.session.destroy(function(){ 
        req.session;
    });
    res.redirect('/');
});


app.listen(app.get('port'), () => {
    console.log(app.get('port'), '번 포트에서 대기중입니다');
});

 

2.3.2. login.html

<!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>login.html</title>
</head>
<body>
    <form method="post" id="form">
        <input type="text" name="id" ><br /> <input type="password" name="pw" ><br />
        <input type="submit" value="로그인"><br /> <div id="message"></div>
    </form>

    <script  src="https://unpkg.com/axios@0.25.0/dist/axios.min.js"></script>
    
    <script type="text/javascript">
        document.getElementById("form").addEventListener( 'submit', async (e)=>{
            e.preventDefault(); 
            const id = e.target.id.value;
            const pw = e.target.pw.value;
            if( id == '' ){
                alert('이름을 입력하세요');  return;
            }else if( pw == '' ){
                alert("비밀번호를 입력하세요");    return;
            }
            try{
                const res = await axios.post('/login', { id, pw });
                if(res.data == 'ok') location.href='/';
                document.getElementById('message').innerHTML = res.data;
            }catch(err){
                console.error(err);
            }
            e.target.id.value='';
            e.target.pw.value='';
        });
    </script>

</body>
</html>

 


 

3. nunjucks(넌적스) 템플릿 엔진

넌적스는 템플릿 엔진 중 하나로 모질라 재단에서 만든 템플릿

3.1. nunjucks 템플릿 모듈

  • 넌적스는 npm 으로 설치할 수 있습니다.
  • configure 의 첫 번째 인수로 'views 의 경로'를 전달해 줍니다.
  • 그리고 두 번째 인수로 '옵션'을 지정해 줍니다.
const nunjucks = require('nunjucks');  // 템플릿엔진 사용을 위한  require

...

// 넌적스 템플릿 엔진을 사용하기 위한 설정
app.set('view engine', 'html');
nunjucks.configure('views', {
    express: app,
    watch: true,
});

configures('views' ... )가 요놈

 


 

3.2. 미들웨어에서의 nunjucks

res.render 호출 시 보내는 변수를 nunjucks 가 처리한다.

  • 넌적스를 이용해서 html 파일을 클라이언트에 보낼때 그 파일에 전달해줄 데이터를 위와 같이 객체형식으로 하나이상 같이 태워 보낼수가 있다.   
    ( 스프링에서의 request.setAttribute, model.addAttribute, mav.addObject 와 비슷한 기능 )
  • 파일을 클라이언트에 응답으로 보낼때는 'render()' 라는 메서드를 사용한다
app.get('/', (req, res)=>{
    res.render( 'index' , {title:'Express'} );
});

app.get('/include', (req, res)=>{
    res.render( 'main' , {title:'Express'} );
});

app.get('/extends', (req, res)=>{
    res.render( 'extends' , {title:'Express'} );
});

 


 

3.3. nunjucks 문법

3.3.1. 변수

<!-- 내부변수 -->
{% set node = 'Node - Js' %}
{% set js = 'JavaScript' %}
<h2> {{node}} 와 {{js}} </h2>

<br><hr>
<!-- 꺽쇠괄호 태그의 적용 유무(safe) -->
<h3>{{'<strong>이스케이프</strong>'}}</h3>
<h3>{{'<strong>이스케이프 하지 않음</strong>' | safe}}</h3>

 

3.3.2. 반복문

<br><hr>
<!-- 반복문 - 중괄호-퍼센트 안에 for in 작성. index 는  loop.index -->
{% set fruits=['사과','배','오렌지','바나나','복숭아'] %}  <!--넌적스 배열 생성-->
<ul>
    {% for item in fruits %}
        <li>{{item}}</li>
    {% endfor %}
</ul>

<ul>
    {% for item in fruits %}
        <li>{{loop.index}} 번째 {{item}}</li>
    {% endfor %}
</ul>

 

3.3.3. 조건문

<br><hr>
<!-- 조건문 -->
{% set isLogged = true %}
{% if isLogged %}
<h3>로그인 되었습니다</h3>
{% else %}
<h3>로그인이 필요합니다</h3>
{% endif %}


{% set fruit = '' %}
{% if fruit === 'apple' %}
<h3>사과입니다</h3>
{% elif fruit === 'banana' %}
<h3>바나나입니다</h3>
{% elif fruit === 'orange' %}
<h3>오렌지입니다</h3>
{% else %}
<h3>아무것도 아닙니다</h3>
{% endif %}

 

3.3.4. include

main.html에 header.html과 footer.html을 include한다.

header.html

<header>
    <a href="/">HOME</a>&nbsp;&nbsp;
    <a href="/about">About</a>&nbsp;&nbsp;
    이것은 header
    <br><hr>
</header>

 

footer.html

<footer>
    <hr>
    <h3>이것은 footer</h3>
</footer>

 

main.html

{% include "header.html" %}

<main>
    <h1>메인파일</h1>
    <h2>여기가 메인입니다. 헤더와 푸터가 인클루드 되었습니다</h2>
</main>

{% include "footer.html" %}

 

3.3.5. extends & block

  • 특이한 점은 레이아웃이 되는 html이 불러와진다는 것!
  • 본체가 블럭이다. ( 미들웨어에서 호출을 블럭으로 해야한다 )

 

extends.html

{% extends 'layout.html' %}

{% block content %}
    <h1>여기는 Block Content 의 본 내용 입니다</h1>
    <h2> 현위치에 쓰여진 Block Content 의 본내용이 layout.html 에 전달되어 표시되는 거 같아 보이지만<br>
        실제는 layout.html 에 담긴 전체 페이지 양식이 이곳에 와서 주위에 배치된 형식입니다</h2>
{% endblock %}

 

layout.html

<!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>
    <main>
        <h1>layout.html 파일</h1>
        <h4>여기가 layout.html의 본파일 내용입니다. <br>
        아래에 추가 내용이 확장되어 최종 파일이 완성될 예정입니다 예정입니다</h4>
        <h4>layout.html 파일은 특정 위치의 내용을 상활별로 달리하지만 양식은 하나로 유지해야할때 사용합니다</h4>
        <h2>여기서부터 확장 내용 시작입니다.</h2> 
        <hr>
            {% block content %}
            {% endblock %}
        <hr>
        <h2>여기까지가 확장 내용 끝입니다.</h2>
        <h4>다시 layout.html이 시작합니다</h4>
        <h4>include 는 다른 파일의 내용을 이곳으로 불러와서 표시하는 형식이지만, extends 는 현재파일이 다른 애용에 제공되어 양식을 구성하는 형식입니다. 그래서 app.js 에서 render 할때도 layout.html 파일을 지목하지 않고, block content 의 내용을 이루는 파일을 지목합니다.</h4>
    </main>
</body>
</html>

 

 

 

300x250