sprint -- Session-based Authentication

FeelSoo·2022년 5월 17일
0

CodeStates

목록 보기
34/43

서버쪽 구현 간단 요약

로그인 요청이 성공적으로 인증되었다면 세션 id를 반환하고 이를 쿠키로 클라쪽에 내려준다.


< 로그아웃 >

  • 서버의 세션 정보를 삭제

  • 클라이언트의 쿠키를 갱신

세션 정보를 삭제하는 메서드로는 req.session.destroy(). 세션 정보가 무효화되면 자동으로 쿠키도 무효가 되므로 세션 정보를 삭제만 하면 된다

https://github.com/expressjs/session#reqsession --- 세션 객체 담기, 불러오기, 파괴하기



< 초기 셋팅 >

1. 환경 변수 설정 -- env.example를 .env로 변경

2. 데이터베이스 생성 -- Mysql 접속 후 .env의 DB NAME 참고하여 DB 생성 

3. DB 마이그레이션 -- DB 업데이트 추적 관리 위해 migration 설정. 

	- 서버 디렉토리로 이동 후 cli 열어 npx sequelize-cli db:migrate
    
4. mkcert로 발급받은 https 인증서 두개를 서버 폴더에 복사




https://sequelize.org/master/manual/migrations.html -- DB 마이그레이션 참고




server-session/users/login.js



// 해당 모델의 인스턴스를 models/index.js에서 가져옵니다.
const { Users } = require('../../models');

module.exports = {
post: async (req, res) => {
  // userInfo는 유저정보가 데이터베이스에 존재하고, 완벽히 일치하는 경우에만 데이터가 존재합니다.
  // 만약 userInfo가 NULL 혹은 빈 객체라면 전달받은 유저정보가 데이터베이스에 존재하는지 확인해 보세요

  //데이터베이스에 묻고 답 가져오는데 시간이 걸리므로 결과 나올때까지 await걸어줌
  //없으면 아직 찾지도 않았는데 다음 코드 진행해버림
  const userInfo = await Users.findOne({
    where: { userId: req.body.userId, password: req.body.password },
  });



  // TODO: userInfo 결과 존재 여부에 따라 응답을 구현하세요.
  // 결과가 존재하는 경우 세션 객체에 userId가 저장되어야 합니다.
  if (!userInfo) {
    res.status(404).send({ message: "not authorized" });
  } else {
    // your code here
    // HINT: req.session을 사용하세요.

      //세션에 userId, 바디에는 userInfo를 실어 보내기
    req.session.save(function () {
      req.session.userId = userInfo.userId;
      res.json({ data: userInfo, message: "ok" });
    });    
  }
}
}

server-session/users/logout.js



module.exports = {
post: (req, res) => {

  // TODO: 세션 아이디를 통해 고유한 세션 객체에 접근할 수 있습니다.
  // 앞서 로그인시 세션 객체에 저장했던 값이 존재할 경우, 이미 로그인한 상태로 판단할 수 있습니다.
  // 세션 객체에 담긴 값의 존재 여부에 따라 응답을 구현하세요.

  if (!req.session.userId) { //  //로그인된 상태에서 세션에 userId가 없는 경우 (세션 만료된 상황)
    res.status(400).send();
  } else {
    // your code here
    // TODO: 로그아웃 요청은 세션을 삭제하는 과정을 포함해야 합니다.
    res.status(200).send();
    req.session.destroy() // //로그아웃시 세션 자체가 사라져야 함
  }
},
};



server-session/users/userinfo.js



const { Users } = require('../../models');

module.exports = {
get: async (req, res) => {
  // console.log("req.session", req.session);
  // TODO: 세션 객체에 담긴 값의 존재 여부에 따라 응답을 구현하세요.
  // HINT: 세션 객체에 담긴 정보가 궁금하다면 req.session을 콘솔로 출력해보세요

  if (!req.session.userId) {
    res.status(400).send({ message: "not authorized" });
  } else {
    const result = await Users.findOne({
      where: { userId: req.session.userId },
    });
    res.status(200).json({ data: result, message: "ok" });
    // console.log("result", result);
    // TODO: 데이터베이스에서 로그인한 사용자의 정보를 조회한 후 응답합니다.
  }
},
};



server-session/index.js



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;

// TODO: express-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 }));

// TODO: CORS 설정이 필요합니다. 클라이언트가 어떤 origin인지에 따라 달리 설정할 수 있습니다.
// 메서드는 GET, POST, OPTIONS를 허용합니다.
const corsOptions = {
origin: 'https://localhost:3001',
methods: 'GET, POST, OPTIONS',
credentials: true

//cors규정때문에 블락당하지 않기 위해 셋팅
//쿠키값 받기위해 credentials를 true로
};
app.use(cors(corsOptions));
/**
* /users 요청에 대해서 라우터를 이용하기 때문에,
* 반드시 아래와 같은 주소와 메서드로 요청을 보내야 합니다.
*
* POST https://localhost:4000/users/login,
* POST https://localhost:4000/users/logout,
* GET https://localhost:4000/users/userinfo
*/
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;



client-session/scr/components/Login.js



import React, { Component } from "react";
import axios from "axios";

class Login extends Component {
constructor(props) {
  super(props);
  this.state = {
    username: "",
    password: "",
  };
  this.inputHandler = this.inputHandler.bind(this);
  this.loginRequestHandler = this.loginRequestHandler.bind(this);
}

inputHandler(e) {
  this.setState({ [e.target.name]: e.target.value });
}

loginRequestHandler() {
  // TODO: 로그인 요청을 보내세요.

  // 로그인에 성공하면
  // - props로 전달받은 함수를 호출해, 로그인 상태를 변경하세요.
  // - GET /users/userinfo 를 통해 사용자 정보를 요청하세요

  // 사용자 정보를 받아온 후
  // - props로 전달받은 함수를 호출해, 사용자 정보를 변경하세요.

  //로그인 POST 요청 보내기
  axios
  .post(
    "https://localhost:4000/users/login",
    {
      userId: this.state.username,
      password: this.state.password,
    },
    { withCredentials: true }
  )
  .then((res) => {
    // console.log(res.data);
    // if (res.data.message === "ok") {
    this.props.loginHandler();
    // }

    //결과값 then으로 넘겨주기 위해 return
    //역시 쿠키 보내기 위해 withCredentials는 true
    return axios.get("https://localhost:4000/users/userinfo", {
      withCredentials: true,
    });
  })
  .then((res) => {
    // console.log(res.data.data);
    let { userId, email } = res.data.data;
    this.props.setUserInfo({ userId, email });
  })
  .catch((err) => {
    console.error(err);
    throw err;
  });
}

render() {
  return (
    <div className="loginContainer">
      <div className="inputField">
        <div>Username</div>
        <input
          name="username"
          onChange={(e) => this.inputHandler(e)}
          value={this.state.username}
          type="text"
        />
      </div>
      <div className="inputField">
        <div>Password</div>
        <input
          name="password"
          onChange={(e) => this.inputHandler(e)}
          value={this.state.password}
          type="password"
        />
      </div>
      <div className="passwordField">
        <button onClick={this.loginRequestHandler} className="loginBtn">
          Login
        </button>
      </div>
    </div>
  );
}
}

export default Login;



client-session/scr/components/Mypage.js



import React from 'react';
import axios from "axios";

const FILL_ME_IN = 'FILL_ME_IN';

function Mypage(props) {
const handleLogout = () => {
  // TODO: 서버에 로그아웃 요청을 보낸다음 요청이 성공하면 props.logoutHandler를 호출하여 로그인 상태를 업데이트 해야 합니다.
  axios
  .post("https://localhost:4000/users/logout", null, {
    withCredentials: true, //쿠키 보내줘야 하기 때문에 withCredentials 쓰기
  })
  .then(() => props.logoutHandler())
  .catch((err) => {
    throw err;
  });
};

return props.userData === null ? (
  <div>Loading...</div>
) : (
  <div>
    <div className="mypageContainer">
      <div>
        <span className="title">Mypage</span>
        <button className="logoutBtn" onClick={handleLogout}>
          logout
        </button>
      </div>
      <hr />

      <div>
        안녕하세요. <span className="name">{props.userData.userId}</span>
        님! 로그인이 완료되었습니다.
      </div>
      <br />
      <div className="item">나의 유저 네임: {props.userData.userId}</div>
      <div className="item">나의 이메일 주소: {props.userData.email}</div>
    </div>
  </div>
);
}


export default Mypage;



client-session/package.json



..... 중략

"scripts": {
  "start": "HTTPS=true SSL_CRT_FILE='../server-session/cert.pem' SSL_KEY_FILE='../server-session/key.pem' react-scripts start",
  "build": "react-scripts build",
  "test": "./run.test.sh",
  "report": "mocha --require @babel/register '__tests__/client.test.js' --reporter @mochajs/json-file-reporter",
  "submit": "codestates"
  
}

..... 중략



profile
세상은 넓고 배울건 많다

0개의 댓글