10주차 실습. Web Login

변현섭·2023년 11월 3일
0

데이터베이스설계

목록 보기
15/22

1. Web Login 적용 실습

"/", "/select", "/delete"를 각 엔드포인트로 로그인 화면, 조회 화면, 데이터 삭제 화면을 구성해보도록 하겠다.

1) MySQL

① 메모장을 열고, 아래의 내용을 입력한다.

CREATE DATABASE week10;

USE week10;

CREATE TABLE user (
    Id VARCHAR(255) PRIMARY KEY,
    Password VARCHAR(255),
    Role VARCHAR(255)
);

CREATE TABLE department (
    Id int PRIMARY KEY,
    Dname VARCHAR(255),
    Dnumber int
);

INSERT INTO user (Id, Password, Role)
VALUES
('admin', 'admin1234', 'super'),
('student1', 'st1234', 'student');

INSERT INTO department (Id, Dname, Dnumber)
VALUES
(0, 'Information and Communication Engineering', 1),
(1, 'Electrical Engineering', 2),
(2, 'Computer Engineering', 3),
(3, 'Electronics Engineering', 4);

② 메모장을 week10.sql이라는 이름으로 cmd의 working 디렉토리 안에 저장한다.

  • 여기서는 바탕화면에 있는 db > Database 안에 week10을 생성하고 그 안에 week10.sql을 저장하였다.

③ cmd에서 working 디렉토리로 이동한 후 아래의 명령을 입력한다.

npm init
npm install express mysql2 body-parser nodemon morgan dotenv express-session
npm install @babel/node @babel/core @babel/preset-env
npm link hbs

④ mysql을 실행하고 아래의 명령을 입력한다.

source ./week10.sql
use week10
show tables; // 데이터베이스가 잘 생성되었는지 확인한다.

2) VSCode

① VSCode에서 해당 파일을 열고 아래와 같이 파일을 생성한다.

② package.json의 scripts 부분을 아래와 같이 수정한다.

"scripts": {
  "test": "echo \"Error: no test specified\" && exit 1",
  "start" : "nodemon --exec babel-node index.js"
},

③ sql.js에 아래의 내용을 입력한다.

  • 비밀번호를 알맞게 변경해야 한다.
import mysql from 'mysql2';

require("dotenv").config();

const pool = mysql.createPool({
    host: 'localhost',
    port: 3306,
    user: 'root',
    password: '{비밀번호}',
    database: 'week10',
});

const promisePool = pool.promise();

export const selectSql = {
    getUser: async () => {
        const sql = `select * from user`;
        const [result] = await promisePool.query(sql);
        return result;
    },
    getDepartment: async () => {
        const sql = `select * from department`;
        const [result] = await promisePool.query(sql);
        return result;
    },
}

export const deleteSql = {
    deleteDepartment: async (data) => {
        console.log('delete department Dnumber =', data);
        const sql = `delete from department where Dnumber=${data.Dnumber}`
        console.log(sql);
        await promisePool.query(sql);
    },
};

④ delete.js에 아래의 내용을 입력한다.

import express from 'express';
import { selectSql, deleteSql } from '../database/sql';

const router = express.Router();

router.get('/', async (req, res) => {
    if (req.session.user != undefined && req.session.user.role === 'super') {
        const department = await selectSql.getDepartment();
        res.render('delete', {
            title: "Delete",
            department,
        });
    } else{
        res.redirect('/');
    }
});

router.post('/', async (req, res) => {
    console.log("delete :", req.body.delBtn);
    const data = {
        Dnumber: req.body.delBtn,
    };

    await deleteSql.deleteDepartment(data);

    res.redirect('/delete');
});

module.exports = router;
  • router.get('/', async (req, res): / 경로의 HTTP GET 요청에 대한 라우팅 핸들러이다. index.js에서 end point에 대한 prefix를 정의하고 있기 때문에 사실은 /select에 대한 라우팅 핸들러이다.
  • if문
    • 사용자 세션이 정의되어 있고, 그 사용자의 role이 super인지 확인한다.
    • 조건문이 참일 경우 delete 뷰 템플릿을 렌더링하고 거짓일 경우, 로그인 페이지로 리다이렉션된다.
  • req.body.delBtn
    • delete.hbs에서 버튼의 value에 Dnumber를 할당해두었다.
    • 버튼을 눌렀을 때 버튼에 대응되는 Dnumber를 deleteDepartment 쿼리의 인자로 전달한다.

⑤ login.js에 아래의 내용을 입력한다.

import express from "express";
import { selectSql } from "../database/sql";

const router = express.Router();

router.get('/', (req, res) => {
    res.render('login');
});

router.post('/', async (req, res) => {
    const vars = req.body;
    const users = await selectSql.getUser();

    users.map((user) => {
        console.log('ID :', user.Id);
        if (vars.id === user.Id && vars.password === user.Password) {
            console.log('login success!');
            req.session.user = { id: user.Id, role: user.Role, checkLogin: true };
        }
    });

    if (req.session.user == undefined) {
        console.log('login failed!');
        res.send(`<script>
                    alert('login failed!');
                    location.href='/';
                </script>`)
    } else if (req.session.user.checkLogin && req.session.user.role === 'super') {
        res.redirect('/delete');
    } else if (req.session.user.checkLogin && req.session.user.role === 'student') {
        res.redirect('/select');
    }
});

module.exports = router;
  • users.map((user) => {: users 배열을 반복하며 각 사용자 정보에 접근한다.
  • post 요청 객체에 담긴 ID와 PW가 DB에 존재하는 값이면, 로그인이 성공한 것으로 간주하고, req.session.user에 사용쟈 정보를 저장한다. 여기서 req.session.user은 로그인 한 유저의 접근인지를 파악하기 위한 용도로 사용된다.
  • 로그인에 실패하면 HTML 스크립트를 전송하여 login failed!라는 알림 메시지를 전송한다.
  • 로그인에 성공한 경우에 대해 admin이면, /delete 페이지로, student이면, /select 페이지로 각각 리다이렉트된다.

⑥ select.js에 아래의 내용을 입력한다.

import express from 'express';
import { selectSql } from '../database/sql';

const router = express.Router();

router.get('/', async (req, res) => {
    if (req.session.user == undefined) {
        res.redirect('/');
    } else if (req.session.user.role === 'student' || req.session.user.role === 'super') {
        const department = await selectSql.getDepartment();
        res.render('select', {
            title: "IT Engineering",
            department,
        });
    } else {
        res.redirect('/');
    }
});

module.exports = router;

⑦ index.js에 아래의 내용을 입력한다.

import express from 'express';
import logger from 'morgan';
import path from 'path';
import expressSession from "express-session";

import loginRouter from '../routes/login';
import selectRouter from '../routes/select';
import deleteRouter from '../routes/delete';

const PORT = 3000;

const app = express();

app.use(express.static(path.join(__dirname, '/src')));
app.use(express.urlencoded({ extended: false }))
app.use(express.json());
app.use(
    expressSession({
        secret: "my key",
        resave: true,
        saveUninitialized: true,
    })
);

app.set('views', path.join(__dirname, '../views'));
app.set('view engine', 'hbs');

app.use(logger('dev'));

app.use('/', loginRouter);
app.use('/select', selectRouter);
app.use('/delete', deleteRouter);

app.listen(PORT, () => {
    console.log(`Server is running at http://localhost:${PORT}`)
});

⑧ delete.hbs에 아래의 내용을 입력한다.

<h1>{{ title }}</h1>

<table>
  <tr>
    <td>Dname</td>
    <td>Dnumber</td>
  </tr>
  {{#each department}}
    <form method="post">
      <tr>
        <td>{{Dname}}</td>
        <td>{{Dnumber}}</td>
        <td style="border: none; margin-left: 10px;">
          <button 
            style="margin-left: 10px;" 
            name='delBtn' 
            type="submit" 
            value="{{Dnumber}}" 
            formaction="/delete"
          >
          delete
          </button>
        </td>
      </tr>    
    </form>
  {{/each}}
</table>

⑨ layout.hbs에 아래의 내용을 입력한다.

<!DOCTYPE html>
<html>
  <head>
    <title>{{title}}</title>
    <style type="text/css">
      table{border-collapse:collapse}
      th,td{border:1px solid black; width: 150px; height: 30px}
  </style>
  </head>
  <body>
    {{{body}}}
  </body>
</html>

⑩ login.hbs에 아래의 내용을 입력한다.

<style>
    body {
        height: 75vh;
        display: flex;
        justify-content: center;
        align-items: center;
        flex-direction: column;
    }
    .frame {
        border-radius: 15px;
        box-sizing: border-box;
        border: 1px solid #757575;
        display: flex;
        justify-content: center;
        align-items: center;
        flex-direction: column;
        width: 350px;
    }
    form {
        display: flex;
        flex-direction: column;
        justify-content: center;
        align-items: center;
    }
    #id, #passwd, .btn {
        font-size: 25px;
    }
    .pwd {
        margin-top: 15px;
    }
    .btn {
        width: 322px;
        margin-top: 20px;
        margin-bottom: 20px;
    }
</style>

<div class="frame">
    <h1>Login</h1>
    <form id="deparment" method="post" action='/'>
        <div class="id">
            <input id="id" name="id" type="text" required placeholder="ID">
        </div>
        <div class="pwd">
            <input id="passwd" name='password' type="password" required  placeholder="Password">
        </div>
        <button class='btn' type="submit">Login</button>
    </form>
</div>

⑪ select.hbs에 아래의 내용을 입력한다.

<h1>{{ title }}</h1>

<table>
  <tr>
    <td>Dname</td>
    <td>Dnumber</td>
  </tr>
  {{#each department}}
  <tr>
    <td>{{Dname}}</td>
    <td>{{Dnumber}}</td>
  </tr>
  {{/each}}
</table>

⑫ Babel.config.json에 아래의 내용을 입력한다.

{
    "presets": ["@babel/preset-env"]
}

⑬ Terminal에 npm run start를 입력한 후 localhost:3000에 접속한다.

⑭ 데이터베이스에 저장되지 않은 ID/PW로 로그인을 시도하면, 아래와 같은 알림창이 나타난다.

⑮ student, st1245로 로그인하면 /select 페이지로 리다이렉션되고, admin, admin1234로 로그인하면 /delete 페이지로 리다이렉션된다.

2. Inha Database에 Web Login 적용하기

저번 실습에서 구축한 Inha Database에 Web Login 기능을 적용해보도록 하겠다. 학생의 학번을 ID로, 학생의 전화번호를 비밀번호로 사용하기로 한다.

1) 로그인 성공 시 학생 정보 조회 페이지로 리다이렉트하기

① sql.js에서 database를 아래와 같이 변경한다.

  • week5 데이터베이스로 변경한다.
  • 쿼리 내용을 알맞게 수정한다.
import mysql from 'mysql2';

require("dotenv").config();

const pool = mysql.createPool({
    host: 'localhost',
    port: 3306,
    user: 'root',
    password: 'wdrsus0520',
    database: 'week5',
});

const promisePool = pool.promise();

export const selectSql = {
    getStudent: async () => {
        const sql = `select * from student`;
        const [result] = await promisePool.query(sql);
        return result;
    },
    getDepartment: async () => {
        const sql = `select * from department`;
        const [result] = await promisePool.query(sql);
        return result;
    },
    getClass: async (id) => {
        const sql = `select c.id, c.name from class c, student s, student_has_class sc where s.id = sc.student_id and c.id = sc.Class_id and s.id = ${id}`;
        const [result] = await promisePool.query(sql);
        return result;
    },
}

export const deleteSql = {
    deleteDepartment: async (data) => {
        console.log('delete department Dnumber =', data);
        const sql = `delete from department where Dnumber=${data.Dnumber}`
        console.log(sql);
        await promisePool.query(sql);
    },
    deleteClass: async (data, id) => {
        console.log('delete class id =', data);
        const sql = `delete from student_has_class where Class_id=${data.id} and student_id =${id}`
        console.log(sql);
        await promisePool.query(sql);
    },
};

② login.js를 아래와 같이 수정한다.

import express from "express";
import { selectSql } from "../database/sql";

const router = express.Router();

router.get('/', (req, res) => {
    res.render('login');
});

router.post('/', async (req, res) => {
    const vars = req.body;
    const students = await selectSql.getStudent();

    students.map((student) => {
        console.log('ID :', student.id);
        console.log('PW :', vars.password);
        console.log('Phone :', student.phone_number);
        if (vars.id == student.id && vars.password === student.phone_number) {
            console.log('login success!');
            req.session.user = { id: student.id, checkLogin: true };
        }
    });

    if (req.session.user == undefined) {
        console.log('login failed!');
        res.send(`<script>
                    alert('login failed!');
                    location.href='/';
                </script>`)
    }
    else if (req.session.user.checkLogin) {
        res.redirect('/select');
    }
});

module.exports = router;

③ select.js를 아래와 같이 수정한다.

import express from 'express';
import { selectSql } from '../database/sql';

const router = express.Router();

router.get('/', async (req, res) => {
    if (req.session.user != undefined) {
        const student = await selectSql.getStudent();
        res.render('select', {
            title: "Student Info",
            student,
        });
    } else {
        res.redirect('/');
    }
});

module.exports = router;

④ login.hbs를 아래와 같이 수정한다.

<style>
    body {
        height: 75vh;
        display: flex;
        justify-content: center;
        align-items: center;
        flex-direction: column;
    }
    .frame {
        border-radius: 15px;
        box-sizing: border-box;
        border: 1px solid #757575;
        display: flex;
        justify-content: center;
        align-items: center;
        flex-direction: column;
        width: 350px;
    }
    form {
        display: flex;
        flex-direction: column;
        justify-content: center;
        align-items: center;
    }
    #id, #passwd, .btn {
        font-size: 25px;
    }
    .pwd {
        margin-top: 15px;
    }
    .btn {
        width: 322px;
        margin-top: 20px;
        margin-bottom: 20px;
    }
</style>

<div class="frame">
    <h1>Login</h1>
    <form id="deparment" method="post" action='/'>
        <div class="id">
            <input id="id" name="id" type="text" required placeholder="ID">
        </div>
        <div class="pwd">
            <input id="passwd" name='password' type="password" required  placeholder="Password">
        </div>
        <button class='btn' type="submit">Login</button>
    </form>
</div>

⑤ select.hbs를 아래와 같이 수정한다.

<h1>{{ title }}</h1>

<table>
  <tr>
    <td>Student ID</td>
    <td>Student Name</td>
    <td>Email</td>
    <td>Phone Number</td>
  </tr>
  {{#each student}}
  <tr>
    <td>{{id}}</td>
    <td>{{name}}</td>
    <td>{{email}}</td>
    <td>{{phone_number}}</td>
  </tr>
  {{/each}}
</table>

이제 코드를 실행시켜보자. localhost:3000으로 접속한 후, Student 테이블의 id 값을 ID로, 전화번호 값을 PW로 입력하여 로그인할 수 있다. 로그인에 성공했을 때, 아래와 같이 학생 정보 조회 페이지로 리다이렉트된다.

2) 로그인 성공 시 수강 포기 페이지로 리다이렉트하기

리다이렉트되는 페이지에 나타나는 수업 목록은 해당 학생이 현재 수강 중인 수업이어야 한다.

① delete.js를 아래와 같이 수정한다.

import express from 'express';
import { selectSql, deleteSql } from '../database/sql';

const router = express.Router();

router.get('/class', async (req, res) => {
    if (req.session.user != undefined) {
        const classes = await selectSql.getClass(req.session.user.id);
        res.render('delete', {
            title: "Delete",
            classes,
        });
    } else{
        res.redirect('/');
    }
});

router.post('/class', async (req, res) => {
    console.log("delete :", req.body.delBtn);
    const data = {
        id : req.body.delBtn,
    };

    await deleteSql.deleteClass(data, req.session.user.id);

    res.redirect('/delete/class');
});

module.exports = router;

② login.js를 아래와 같이 수정한다.

import express from "express";
import { selectSql } from "../database/sql";

const router = express.Router();

router.get('/', (req, res) => {
    res.render('login');
});

router.post('/', async (req, res) => {
    const vars = req.body;
    const students = await selectSql.getStudent();

    students.map((student) => {
        console.log('ID :', student.id);
        console.log('PW :', vars.password);
        console.log('Phone :', student.phone_number);
        if (vars.id == student.id && vars.password === student.phone_number) {
            console.log('login success!');
            req.session.user = { id: student.id, checkLogin: true };
        }
    });

    if (req.session.user == undefined) {
        console.log('login failed!');
        res.send(`<script>
                    alert('login failed!');
                    location.href='/';
                </script>`)
    } else if (req.session.user.checkLogin) {
         res.redirect('/delete/class');
    }
});

module.exports = router;

③ delete.hbs에 아래의 내용을 입력한다.

<h1>{{ title }}</h1>

<table>
  <tr>
    <td>Class Id</td>
    <td>Class Name</td>
  </tr>
  {{#each classes}}
    <form method="post">
      <tr>
        <td>{{id}}</td>
        <td>{{name}}</td>
        <td style="border: none; margin-left: 10px;">
          <button 
            style="margin-left: 10px;" 
            name='delBtn' 
            type="submit" 
            value={{id}} 
            formaction="/delete/class">
          delete
          </button>
        </td>
      </tr>    
    </form>
  {{/each}}
</table>

이제 코드를 실행해보자. 로그인에 성공했을 때 아래와 같이 수강 포기 페이지로 리다이렉트된다.

위 결과는 2번 학생으로 로그인했을 때 나타나는 결과이다. 실제 DB에서 2번 학생이 수강하고 있는 수업을 확인해보자.

실제로도 2번 학생은 java_programming과 management_theory를 수강하고 있다. 이제 java_programming class 옆에 있는 delete 버튼을 클릭해보자.

삭제가 잘 반영되는 것을 확인해볼 수 있다. 이 결과는 cmd에서도 동일하게 확인 가능하다.

이제 2번 학생은 3번 수업인 management_theory만 수강하는 상태가 되었다.

참고로 두 경우 모두, 로그인을 하지 않은 상태에서 "/select", "/delete/class"로 바로 오는 요청은 "/"로 리다이렉트하여 로그인을 유도하고 있다. 즉, 학생 로그인을 진행하지 않은 경우, 학생의 정보를 조회할 수 없고, 수강 포기도 할 수 없는 것이다.

profile
Java Spring, Android Kotlin, Node.js, ML/DL 개발을 공부하는 인하대학교 정보통신공학과 학생입니다.

0개의 댓글