Vue.js 3 & NodeJS/사내교육 - 점심 메뉴

[8] Backend 개발 - 사용자 API 개발, 인증 처리, Session + HTTP Cookie

carrotweb 2026. 8. 4. 15:28
728x90
반응형

인증 처리를 위한 사용자 API 개발

사용자 인증 처리를 위한 테이블을 생성합니다.

 

Table 생성

테이블 생성 쿼리 실행

use lunchdb;

-- tb_member table이 있으면 삭제
DROP TABLE IF EXISTS tb_member;

-- tb_member table 생성
CREATE TABLE tb_member (
    seq int NOT NULL AUTO_INCREMENT COMMENT '사용자 일련번호',
    mbr_id varchar(50) NOT NULL COMMENT '사용자 ID',
    mbr_pw varchar(125) NOT NULL COMMENT '사용자 비밀번호',
    mbr_name varchar(50) NOT NULL COMMENT '사용자 이름',
    mbr_roles varchar(250) NOT NULL COMMENT '사용자 롤',
    reg_dtm datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '등록일',
    CONSTRAINT tb_member_pk PRIMARY KEY (seq)
)
ENGINE=InnoDB
DEFAULT CHARSET=utf8mb4
COLLATE=utf8mb4_general_ci
COMMENT='사용자';

 

초기 데이터 등록 쿼리 실행

INSERT INTO tb_member (mbr_id, mbr_pw, mbr_name, mbr_roles, reg_dtm) VALUES('test1', 'test1', '테스트1', 'ROLE_USER', '2025-03-06 14:12:00.000');

 

 

사용자 API 개발

 

1. src\db\mappers\mariadb\lunchmenu에 member-sql.xml 파일을 생성합니다.

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">

<mapper namespace="apiserver.db.mappers.mariadb.lunchmenu.member">
    <select id="selectMember">
        SELECT *
          FROM tb_member
         WHERE mbr_id = #{id}
    </select>
</mapper>

 

2. src\api\v1에 member.js 파일 생성

const express = require('express');
const router = express.Router();
const getDBConnection = require('../../db/mariadb/connection');
const mybatisMapper = require('mybatis-mapper');

// XML 파일 경로는 상대 경로가 아닌 절대 경로(Root 경로)로 './' 또는 '/' 없이 입력해야 합니다.
mybatisMapper.createMapper(['./src/db/mappers/mariadb/lunchmenu/member-sql.xml']);

// SQL 언어 형식과 들여쓰기를 설정
const format = {language: 'mariadb', indent: '  '};

// 사용자 ID로 검색하여 사용자를 인증하고 리턴합니다.
router.post('/login', async function(req, res, next) {
    console.log(`REST API Post Method - Member Login.`);

    const result = {
        result: 'success',
        code: 200,
        message: '',
        data: {}
    };

    if (req.body.id == undefined || req.body.id == "") {
        result.result = "fail";
        result.code = 400;
        result.message = "id가 없습니다."
        res.status(result.code).json(result);
        return;
    } else if (req.body.password == undefined || req.body.password == "") {
        result.result = "fail";
        result.code = 400;
        result.message = "password가 없습니다."
        res.status(result.code).json(result);
        return;
    }

    const params = {
        id : req.body.id,
        password : req.body.password
    };

    //const query = "SELECT * FROM tb_member WHERE mbr_id = ? AND mbr_pw = ?";
    const query = mybatisMapper.getStatement('apiserver.db.mappers.mariadb.lunchmenu.member', 
        'selectMember', params, format);

    let conn;
    try {
        conn = await getDBConnection();
        //data = await conn.query(query, [req.body.id, req.body.password]);
        data = await conn.query(query);
        if (data.length == 0) {
            result.result = "fail";
            result.code = 401;
            result.message = "사용자가 없습니다.";
        } else {
            if (data[0].mbr_pw != params.password) {
                result.result = "fail";
                result.code = 401;
                result.message = "비밀번호가 동일하지 않습니다."
            } else {
                result.message = "사용자가 인증되었습니다.";
            }
        }
    } catch (error) {
        result.result = "fail";
        result.code = 500;
        if (error.code == "ER_NO_SUCH_TABLE") {
            result.message = "테이블이 없습니다.";
        } else {
            result.message = "서버 오류가 발생하였습니다.";
        }
    } finally {
        if (conn) {
            conn.release();
        }
    }

    res.status(result.code).json(result);
});

module.exports = router;

 

3. index.js 파일에 BODY를 JSON 데이터로 변환, URL 인코딩, 요청 리소스 허용 처리

const express = require('express');
const app = express();
const port = 9000;

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

:

// 점심 메뉴 연결
app.use('/api/v1/lunchmenus', require('./src/api/v1/lunchmenu'));
// 사용자 연결
app.use('/api/v1/member', require('./src/api/v1/member'));

app.listen(port, () => {
    console.log(`APIServer started on port ${port}`);
});

 

사용자 API 테스트 (POSTMAN에서 확인)
사용자 로그인

 

 

인증 처리

HTTP Cookie (쿠키)

사용자가 로그인하면 서버에서 사용자를 확인하여 쿠키를 생성하고 응답 HTTP Header에 쿠키를 포함시켜 클라이언트에게 전달하는 방식입니다. (Set-Cookie: <cookie-name>=<cookie-value>)

클라이언트는 요청을 보낼 때 요청 HTTP Header에 쿠키를 포함하여 전달하면 서버는 전달 받은 쿠키에서 세션을 가져와서 검증하고 인증된 사용자인지 확인합니다. (Cookie: <cookie-name>=<cookie-value>)

 

장점

  • 클라이언트에 정보를 저장하기 때문에 서버에 부하가 낮아집니다.

단점

  • 쿠키의 사이즈가 4KB이어서 많은 정보를 담을 수 없습니다.
  • 일반적으로 쿠키 값은 암호화 되지 않기 때문에 안전하지 않습니다.
  • 쿠키가 탈취 당할 수 있습니다.  보안성이 취약함

 

HTTP Cookie Process (쿠키 프로세스)

 

cookie-parser

(https://expressjs.com/en/resources/middleware/cookie-parser.html)

 

1. 쿠키 파서를 사용하기 위해 cookie-parser 설치 (터미널에서 실행합니다.)

npm install cookie-parser --save

 

2. index.js 파일 수정

const express = require('express');
const cookieParser = require('cookie-parser');
const app = express();
const port = 9000;

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

:

// Cookie 설정
app.use(cookieParser());

// 점심 메뉴 연결
app.use('/api/v1/lunchmenus', require('./src/api/v1/lunchmenu'));
// 사용자 연결
app.use('/api/v1/member', require('./src/api/v1/member'));

app.listen(port, () => {
    console.log(`APIServer started on port ${port}`);
});

 

3. src\api\v1\member.js 파일 수정

    :
    let conn;
    try {
        conn = await getDBConnection();
        //data = await conn.query(query, [req.body.id, req.body.password]);
        data = await conn.query(query);
        if (data.length == 0) {
            result.code = 401;
            result.message = "사용자가 없습니다.";
        } else {
            if (data[0].mbr_pw != params.password) {
                result.result = "fail";
                result.code = 401;
                result.message = "비밀번호가 동일하지 않습니다."
            } else {
                result.message = "사용자가 인증되었습니다.";
                res.cookie("id", params.id);
                res.cookie("password", params.password);
            }
        }
    } catch (error) {
    :

 

res.cookie로 정보를 쿠키에 저장합니다.

그러면 응답 HTTP Header에 쿠키를 포함시켜 클라이언트에게 전달됩니다.

 

res.appendHeader(
    "Set-Cookie", ["id=" + params.id, "password=" + params.password]
);

 

사용자 API 테스트 (POSTMAN에서 확인)
사용자 로그인

 

 

인증 미들웨어(Middleware) 생성

1. src\api\v1에 authMiddleware.js 파일 생성

const getDBConnection = require('../../db/mariadb/connection');
const mybatisMapper = require('mybatis-mapper');

// XML 파일 경로는 상대 경로가 아닌 절대 경로(Root 경로)로 './' 또는 '/' 없이 입력해야 합니다.
mybatisMapper.createMapper(['./src/db/mappers/mariadb/lunchmenu/member-sql.xml']);

// SQL 언어 형식과 들여쓰기를 설정
const format = {language: 'mariadb', indent: '  '};

const authMiddleware = async (req, res, next) => {
    const id = req.cookies.id;
    const password = req.cookies.password;

    const result = {
        result: 'fail',
        code: 400,
        message: '인증이 실패하였습니다.',
        data: {}
    };

    if ((id == undefined || id == "") || (password == undefined || password == "")) {
        res.status(result.code).json(result);
    } else {
        const params = {
            id : id,
            password : password
        };

        const query = mybatisMapper.getStatement('apiserver.db.mappers.mariadb.lunchmenu.member', 
            'selectMember', params, format);

        let conn;
        try {
            conn = await getDBConnection();
            data = await conn.query(query);
            if (data.length == 1) {
                if (data[0].mbr_pw == params.password) {
                    next();
                }
            }
        } catch (error) {
            result.code = 500;
            if (error.code == "ER_NO_SUCH_TABLE") {
                result.message = "테이블이 없습니다.";
            } else {
                result.message = "서버 오류가 발생하였습니다.";
            }
            res.status(result.code).json(result);
        } finally {
            if (conn) {
                conn.release();
            }
        }
    }
}

module.exports = authMiddleware;

 

2. index.js 파일 수정

const express = require('express');
const cookieParser = require('cookie-parser');
const app = express();
const port = 9000;

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

:

// Cookie 설정
app.use(cookieParser());

// 인증 설정
app.use('/api/v1/lunchmenus', require('./src/api/v1/authMiddleware'));
// 점심 메뉴 연결
app.use('/api/v1/lunchmenus', require('./src/api/v1/lunchmenu'));
// 사용자 연결
app.use('/api/v1/member', require('./src/api/v1/member'));

app.listen(port, () => {
    console.log(`APIServer started on port ${port}`);
});

 

사용자 API 테스트 (POSTMAN에서 확인)

1. 기존 쿠키 삭제

 

2. 전체 점심 메뉴 조회

 

3. 사용자 로그인

 

4. 전체 점심 메뉴 조회

 

 

Session + HTTP Cookie(세션 + 쿠키)

사용자가 로그인하면 서버에서 사용자를 확인하여 세션을 생성하고 사용자 정보를 세션에 저장한 후 응답 HTTP Header에 쿠키를 포함시켜 클라이언트에게 전달하는 방식입니다.

클라이언트는 요청을 보낼 때 Cookie HTTP Header에 세션을 포함하여 전달하면 서버는 전달 받은 쿠키에서 세션을 가져와서 검증하고 인증된 사용자인지 확인합니다.

 

장점

  • 서버에서 세션을 관리하기 때문에 안전합니다. → 보안성이 높음

단점

  • 세션 정보를 관리하기 위해 서버 메모리가 많이 사용됩니다.
  • 서버를 확장할 경우 세션 관리가 어려워집니다. → 서버 이중화를 위해 (In-Memory)DB를 사용
  • 쿠키가 탈취 당할 수 있습니다. → 보안성이 취약함
  • 모든 자원을 접근할 수 있습니다. → 별도의 접근 권한 처리 필요

 

Session + HTTP Cookie Process (세션 + 쿠키 프로세스)

 

express-session
(https://expressjs.com/en/resources/middleware/session.html)

 

1. 섹션을 사용하기 위해 express-session 설치 (터미널에서 실행합니다.)

npm install express-session --save

 

2. index.js 파일 수정

const express = require('express');
const cookieParser = require('cookie-parser');
const session = require('express-session');
const app = express();
const port = 9000;

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

:

// Cookie 설정
app.use(cookieParser());

// 세션 설정
app.use(session({
    secret: "test", // 세션 ID 쿠키에 서명하는데 사용되는 비밀번호입니다.
    resave: false, // 세션을 세션 저장소에 다시 저장하도록 강제합니다.
    saveUninitialized: true, // "초기화되지 않은" 세션을 강제로 저장소에 저장합니다.
    name: "sessionid" // 응답에 설정할 세션 ID 쿠키의 이름입니다.
}));

// 인증 설정
app.use('/api/v1/lunchmenus', require('./src/api/v1/authMiddleware'));
:
});

 

3. src\api\v1\member.js 파일 수정

    :
    let conn;
    try {
        conn = await getDBConnection();
        //data = await conn.query(query, [req.body.id, req.body.password]);
        data = await conn.query(query);
        if (data.length == 0) {
            result.code = 401;
            result.message = "사용자가 없습니다.";
        } else {
            if (data[0].mbr_pw != params.password) {
                result.result = "fail";
                result.code = 401;
                result.message = "비밀번호가 동일하지 않습니다."
            } else {
                result.message = "사용자가 인증되었습니다.";
                req.session.userid = params.id;
                req.session.userpassword = params.password;
            }
        }
    } catch (error) {
    :

req.session로 정보를 세션에 저장합니다.

 

4. src\api\v1\authMiddleware.js 파일 수정

const getDBConnection = require('../../db/mariadb/connection');
const mybatisMapper = require('mybatis-mapper');

// XML 파일 경로는 상대 경로가 아닌 절대 경로(Root 경로)로 './' 또는 '/' 없이 입력해야 합니다.
mybatisMapper.createMapper(['./src/db/mappers/mariadb/lunchmenu/member-sql.xml']);

// SQL 언어 형식과 들여쓰기를 설정
const format = {language: 'mariadb', indent: '  '};

const authMiddleware = async (req, res, next) => {
    const id = req.session.userid;
    const password = req.session.userpassword;

    const result = {
        result: 'fail',
        code: 400,
        message: '인증이 실패하였습니다.',
        data: {}
    };
   :
}

 

 

사용자 API 테스트 (POSTMAN에서 확인)
1. 기존 쿠키 삭제

 

2. 전체 점심 메뉴 조회

 

3. 사용자 로그인

 

4. 전체 점심 메뉴 조회

 

 

사용자 id와 password 대신 데이터로 저장

1. src\api\v1\member.js 파일 수정

    :
    let conn;
    try {
        conn = await getDBConnection();
        //data = await conn.query(query, [req.body.id, req.body.password]);
        data = await conn.query(query);
        if (data.length == 0) {
            result.code = 401;
            result.message = "사용자가 없습니다.";
        } else {
            if (data[0].mbr_pw != params.password) {
                result.result = "fail";
                result.code = 401;
                result.message = "비밀번호가 동일하지 않습니다."
            } else {
                result.message = "사용자가 인증되었습니다.";
                req.session.member = data[0];
            }
        }
    } catch (error) {
    :

 

2. src\api\v1\authMiddleware.js 파일 전체 수정

const authMiddleware = async (req, res, next) => {
    const member = req.session.member;

    const result = {
        result: 'fail',
        code: 400,
        message: '인증이 실패하였습니다.',
        data: {}
    };

    if (member == undefined || member == "") {
        res.status(result.code).json(result);
    } else {
        next();
    }
}

module.exports = authMiddleware;
728x90
반응형