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