Session-based Authentication 구현해보기(server & client)

🐶·2021년 8월 2일
0

미니 과제

목록 보기
13/15

쿠키와 세션은 서로 어떤 관계이며, 각각이 인증에 있어서 어떤 목적으로 존재하는지 이해하기 위하여 스프린트를 진행해보았다.

스프린트 시작 전 아래와 같이 상황을 설명하고 있다.

쿠키를 학습하고, 브라우저에 상태를 저장할 수 있다는 사실을 알게 된 주니어 개발자 김코딩은, 쿠키에 인증 정보를 넣어 로그인 상태를 유지할 수 있음을 알게 되었습니다.

그러나, 선배 개발자 박코딩은, 쿠키만으로 인증을 구현하는 것은 언제든 쿠키가 클라이언트에 의해 변조가 가능하기 때문에 위험하다는 사실을 알려줍니다. 이에 따라 서버에도 쿠키를 검증할 수 있는 수단을 마련하는 세션 인증 방식을 제안합니다.

기억해야 할 것.

쿠키옵션들을 지정한 다음(domain, httpOnly, sameSite ..등등) 서버에서 클라이언트로 쿠키를 처음 전송하게 된다면 헤더에 Set-Cookie라는 프로퍼티에 쿠키를 담아 쿠키를 전송하게 된다.

이후 클라이언트 혹은 서버에서 쿠키를 전송해야 한다면 클라이언트는 헤더에 Cookie라는 프로퍼티에 쿠키를 담아 서버에 쿠키를 전송하게 된다.

Server

서버 최상단(index.js)

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 PORT = process.env.PORT || 4000;

///////// 세션 옵션을 아래와 같이 설정하였다
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의 default 옵션이 아닌 아래와 같이 세부 옵션을 설정해주었다
const options = {
  origin: "http://localhost:3000",
  methods: "GET,POST,OPTIONS",
  credentials: true // 다른 도메인 간 쿠키 주고받을 수 있게 (서버에서 설정)
};
app.use(cors(options));

app.use('/users', usersRouter);


///////////인증서 파일 유무를 기준으로 https or http 프로토콜 사용여부를 판단한다.
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

아래와 같은 요청을 처리하는 코드를 작성하였다.
1. controller/login.js (POST /users/login)

  • request로부터 받은 userId, password와 일치하는 유저가 DB에 존재하는지 확인합니다.(--> sequlize의 findOne 메소드 사용하여 조회)
  • 일치하는 유저가 없을 경우:
    로그인 요청을 거절합니다.
  • 일치하는 유저가 있을 경우:
    세션에 userId를 저장합니다.(--> 세션객체의 저장메소드를 사용하고 & 세션객체에서 userId 속성 생성한다)
req.session.save(function () {
  req.session.userId = userInfo.userId;
  res.json({ data: userInfo, message: 'ok' }); // save 비동기라서 여기안에 있어야함
});

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

  • 세션 객체에 저장한 값이 존재하면
    세션을 삭제합니다.(자동으로 클라이언트 쿠키는 갱신됩니다.)
    (-->세션객체의 destroy 메소드를 사용하여 세션 삭제)
  req.session.destroy();
      res.json({ data: null, message: 'ok' });

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

  • 세션 객체에 저장한 값이 존재하면
    사용자 정보를 데이터베이스에서 조회한 후 응답으로 전달합니다.(--> sequlize의 findOne 메소드 사용하여 조회한다. where 조건에 { userId: req.session.userId }를 작성한다)
  • 세션 객체에 저장한 값이 존재하지 않으면
    요청을 거절합니다.

Client

  1. 클라이언트의 package.json 파일의 FILL_ME_IN을 서버에서 사용했던 인증서 파일의 상대경로로 변경
    "start": "HTTPS=true SSL_CRT_FILE=../server-cookie.cert.pem SSL_KEY_FILE=../server-cookie.key.pem react-scripts start",
  2. components/login.js 에서 axios (fetch처럼 통신응답객체를 받아온다) 이용하여 로그인 POST요청을 보낸다. 로그인에 성공하면 props로 전달받은 함수를 호출해, 로그인 상태를 변경한다. 그리고 나서 userinfo GET요청을 보내 사용자 정보를 받아온다. 그 사용자 정보를 props로 전달받은 함수에 전달인자로 전달하고 호출해, 사용자 정보를 변경한다.

AJAX 요청 시에 쿠키를 같이 보낼건지를 결정하는 옵션인 withCredentials: true를 꼭 명시해준다.

loginRequestHandler() {
  
 //로그인 POST 요청 보내기
  axios.post('https://localhost:4000/users/login', {
      userId: this.state.username,
      password: this.state.password
    }, {
      credentials: 'include',
      withCredentials: true
    })
    .then(res => {
      this.props.loginHandler(); //로그인 상태를 true로 변경시키는 함수 호출
    })
    .catch(err => {
     console.log(err);
    })
//userinfo 받아오는 GET 요청 보내기
   axios.get('https://localhost:4000/users/userinfo', {
      credentials: 'include',
      withCredentials: true
    })
    .then(res => {
      this.props.setUserInfo(res.data.userInfo); //userData를 업데이트하는 setState 함수 호출
    })
    .catch(err => {
     console.log(err);
    })
  }
  1. components/mypage.js 에서 서버에 로그아웃 요청을 보내면, 요청이 성공했을 시 props.logoutHandler를 호출하여 로그인 상태를 업데이트 해야 한다.
axios.post('https://localhost:4000/users/logout', {}, {
      credentials: 'include',
      withCredentials: true
    })
    .then(res => {
      props.logoutHandler() //로그인 상태를 false로 변경시키는 함수 호출
    })
    .catch(err => {
      console.log(err);
    })

(사진: 로그인 화면)
(사진: 로그인 성공 시 화면)

profile
우당탕탕 개발일기📝🤖

0개의 댓글