HTTPS - 세션 기반 인증 실습(Server)

김도영·2022년 6월 7일
0

사전 작업

  1. 환경 변수 설정
    .env 파일 생성 후 데이터 베이스 관련 환경변수 설정

  2. 데이터베이스 생성 및 마이그레이션
    시퀄라이즈 마이그레이션 및 시드를 적용한다.

mysql > DATABASE CREATE authentication
npx sequelize-cli db:migrate // 시퀄라이즈 마이그레이션
  1. https 사설 인증서 발급 및 디렉토리에 복사
// mkcert 설치
sudo apt install libnss3-tools
wget -O mkcert https://github.com/FiloSottile/mkcert/releases/download/v1.4.3/mkcert-v1.4.3-linux-amd64
chmod +x mkcert
sudo cp mkcert /usr/local/bin/

// 인증서 생성
mkcert -install
mkcert -key-file key.pem -cert-file cert.pem localhost 127.0.0.1 ::1
// 127.0.0.1(IPv4), ::1(IPv6)

서버 구현

index.js

  • express-session 라이브러리를 이용해 쿠키 설정
  • CORS 옵션 설정
  • const express = require('express');
    const cors = require('cors');
    const session = require('express-session');
    const logger = require('morgan');
    const fs = require('fs');
    const https = require('https');
    const usersRouter = require('./routes/user');
    
    const app = express();
    
    const FILL_ME_IN = 'FILL_ME_IN';
    
    const PORT = process.env.PORT || 4000;
    
    // Texpress-session 라이브러리를 이용해 쿠키 설정
    app.use(
      session({
        secret: '@codestates',
        resave: false,
        saveUninitialized: true,
        cookie: {
          domain: "localhost",
          path: "/",
          maxAge: 24 * 6 * 60 * 10000,
          sameSite: 'None',
          httpOnly: true,
          secure: true,
        },
      })
    );
    app.use(logger('dev'));
    app.use(express.json());
    app.use(express.urlencoded({ extended: false }));
    
    // CORS 설정이 필요
    // 메서드는 GET, POST, OPTIONS를 허용
    app.use(cors({
      origin : 'https://localhost:4000',
      method : ['GET', 'POST', 'OPTIONS'],
      credentials : true // 쿠키 응답 메시지 설정
    }));
    
    app.use('/users', usersRouter);
    
    let server;
    
    // 인증서 파일들이 존재하는 경우에만 https 프로토콜을 사용하는 서버를 실행
    // 만약 인증서 파일이 존재하지 않는경우, http 프로토콜을 사용하는 서버를 실행
    // 파일 존재여부를 확인하는 폴더는 서버 폴더의 package.json이 위치한 곳
    if (fs.existsSync("./key.pem") && fs.existsSync("./cert.pem")) {
      server = https
        .createServer(
          {
            key: fs.readFileSync(__dirname + `/` + 'key.pem', 'utf-8'),
            cert: fs.readFileSync(__dirname + `/` + 'cert.pem', 'utf-8'),
          },
          app
        )
        .listen(PORT);
    } else {
      server = app.listen(PORT)
    }
    module.exports = server;
    

    controller/login.js (POST /users/login)

  • request로부터 받은 userId, password와 일치하는 유저가 DB에 존재하는 확인
  • 일치하는 유저가 없는 경우 : 로그인 거절
  • 일치하는 유저가 있는 경우 : 세션에 userId를 저장
  • index.js파일은 클라이언트에서 로그인 요청이 들어오면, 요청된 id와 password가 DB와 일치하게 되면 응답을 돌려주게 된다. 데이터를 session이라는 곳에 저장하고 session_id를 클라이언트에 전달하면, 클라이언트에서는 데이터를 요청할 때 마다 id를 확인하고 서버에 데이터를 받게 된다. 또한, 현재 실습에서는 session에 저장한 데이터가 모두 지워지게 되는 일회성 방식인 express-session 미들웨어를 사용한다.

    // 해당 모델의 인스턴스를 models/index.js에서 가져온다.
    const { userinfo } = require('.');
    const { Users } = require('../../models');
    
    module.exports = {
      post: async (req, res) => {
        // userInfo는 유저정보가 데이터베이스에 존재하고, 완벽히 일치하는 경우에만 데이터가 존재
        // 만약 userInfo가 NULL 혹은 빈 객체라면 전달받은 유저정보가 데이터베이스에 존재하는지 확인
        const userInfo = await Users.findOne({
          where: { userId: req.body.userId, password: req.body.password },
        });
    
        // userInfo 결과 존재 여부에 따라 응답
        // 결과가 존재하는 경우 세션 객체에 userId가 저장
        if (!userInfo) {
          res.status(400).send({ data: null, message: 'not authorized'})
        } else {
          req.session.userId = userInfo.userId;
          res.status(200).json({ data: userInfo, message: 'ok' })
        }
      }
    }

    controller/logout.js (POST /users/logout)

  • 세션 객체에 저장한 값이 존재하면 -> 세션을 삭제(자동으로 클라이언트 쿠키는 갱신)
  • login과 같은 방식으로 구현한다.

    module.exports = {
      post: (req, res) => {
    
        // 세션 아이디를 통해 고유한 세션 객체에 접근가능
        // 앞서 로그인시 세션 객체에 저장했던 값이 존재할 경우, 이미 로그인한 상태로 판단
        // 세션 객체에 담긴 값의 존재 여부에 따라 응답 구현
    
        if (!req.session.userId) {
          res.status(400).send({ data: null, message: 'not authorized' })
        } else {
          // 로그아웃 요청은 세션을 삭제하는 과정을 포함
          req.session.destroy();
          res.status(200).json({ data: null, message: 'ok'})
        }
      },
    };
    

    controller/userinfo.js (POST /users/userinfo)

  • 세션 객체에 저장한 값이 존재하면 -> 사용자 정보를 데이터베이스에 조회한 후 응답으로 전달
  • 세션 객체에 저장한 값이 존재하지 않으면 -> 요청 거절
  • req.session에 들어오는 값이 일치하면, DB에서 값을 찾아 클라이언트에게 보내주게 된다.

    const { Users } = require('../../models');
    
    module.exports = {
      get: async (req, res) => {
    
        // 션 객체에 담긴 값의 존재 여부에 따라 응답을 구현
        if (!req.session.userId) {
          res.status(400).send({ data: null, message: 'not authorized' })
        } else {
          // 데이터베이스에서 로그인한 사용자의 정보를 조회한 후 응답
          const result = await Users.findOne({
            where: { userId: req.session.userId }
          });
          res.status(200).json({ data: result, message: 'ok' })
        }
      },
    };
    
    profile
    Blockchain Developer

    0개의 댓글