인증/보안 - session 기반 인증 구현하기 실습

2021. 11. 25. 16:22·SE Bootcamp 내용 정리

session 기반 인증(session-based authentication) 구현 실습

학습 목표

* 세션의 개념을 이해
-> 접속 상태를 서버가 가지는(stateful) 인증 방식

* 쿠키와 세션은 서로 어떤 관계이며, 각각이 인증에 있어서 어떤 목적으로 존재하는지 이해하기
-> 쿠키는 단순히 무상태성을 보완해주는 도구일 뿐, 인증이 아니다
-> 세션은 인증 과정에 쿠키를 이용해서 인증 작업을 하고 세션을 열어준다고 보면 된다
-> 접속 상태와 권한 부여를 위해 세션 아이디를 쿠키로 전송하므로...

* 세션의 한계를 이해
-> 서버에서 인증 정보를 가지므로 서버가 부담을 가짐(성능 저하 이슈)
-> 분산에 불리
-> 여전히 쿠키를 사용하는 방식이므로 XSS공격에 취약점 존재

getting started

본격적인 시작에 앞서 환경 변수 설정 및 데이터 마이그레이션 작업을 선행해야 코드 작업이 가능하다
  • 환경 변수 설정: .env 파일 생성 및 관련 환경 변수 설정하기(각 환경에 맞게 변경)
  • 데이터 마이그레이션 작업: sequelize 를 이용하여 데이터 마이그레이션 및 시드 작업을 진행하자

https://sequelize.org/master/manual/migrations.html

 

Manual | Sequelize

Migrations Just like you use version control systems such as Git to manage changes in your source code, you can use migrations to keep track of changes to the database. With migrations you can transfer your existing database into another state and vice ver

sequelize.org

서버 작업

index.js 작업

발급 받은 인증서 및 키를 해당 디렉토리로 옮겨 넣거나, 알맞는 경로로 설정해 주자
쿠키 관련 설정은 https를 사용할 것이므로 해당 관련 옵션을 설정하는 것이 필요(secure: true)
cors 설정도 필요함(cors는 어떤 origin인지에 따라 달리 설정할 수 있으나, 여기서는 단일 오리진에 대해서만 설정)

// express-session 라이브러리를 이용해 쿠키 설정을 해줄 수 있으므로 이를 이용

app.use(
  session({
    secret: '주어진시크릿값',
    resave: false,
    saveUninitialized: true,
    cookie: {    // 쿠키 관련 설정
      domain: 'localhost',    // 해당 도메인명
      path: '/',    // 세부 경로
      maxAge: 24 * 6 * 60 * 10000,
      sameSite: 'none',    // 교차 오리진에 대한 설정을 할 것이라 none으로 함
      httpOnly: true,    // 브라우저에서만 접근가능(자바스크립트 변조x)
      secure: true,        //https 프로토콜 사용
    },
  })

cors 설정 작업도 해주자

  // TODO: CORS 설정이 필요합니다. 클라이언트가 어떤 origin인지에 따라 달리 설정할 수 있습니다.
// 메서드는 GET, POST, OPTIONS를 허용합니다.

// app.use(cors());
//! 기본값으로 포함하고 있긴하나 해당 메서드만 허용하려면 다음과 같이 쓰자

// ! 추가된 부분
app.use(cors({
  origin: 'https://localhost:4000',
  methods: ['POST', 'GET', 'OPTIONS'],
  credentials: true    // 암호화 부분(기밀성 on)
}))

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

기본적으로 만들어야 하는 로직은 다음과 같다
request로부터 받은 userId, password와 일치하는 유저가 DB에 존재하는지 확인
-> 일치하는 유저가 없을 경우:로그인 요청을 거절
-> 일치하는 유저가 있을 경우: 세션에 userId를 저장

   // TODO: userInfo 결과 존재 여부에 따라 응답을 구현하세요.
   // 결과가 존재하는 경우 세션 객체에 userId가 저장되어야 합니다.

    // 위에서 userInfo 라는 변수 명으로 DB에 존재하는 User 테이블에서 해당 조건에 맞는 사용자를 가져옴(await Users.findOne())

    if (!userInfo) {    // 일치하는 유저가 없을 경우
      res.status(401).send({message : '알맞은 에러 메시지 쓸것'  });
    } else {

      // console.log(req.body);
      //! res.session.save 를 이용
      // save 객체 안에서 res도 처리해야 한다
      req.session.save(function() {    // session saved    // 자세한 용법은 req.session에 대한 내용을 찾아보면 된다(인자 등)
        req.session.userId = userInfo.userId;    // 세션에 userId를 저장
        res.status(201).json({data: 응답 결과값, message : '잘 처리 되었다는 응답 메시지' });
        //!주의점: res(응답)를 반드시 save 구문 안에 넣어야 한다
      })

    }

express-session 모듈과 관련된 설명은 공식 문서를 참고하자
https://www.npmjs.com/package/express-session

 

express-session

Simple session middleware for Express

www.npmjs.com

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

기본적인 로직은 세션 객체에 저장한 값이 존재하면 세션을 삭제하도록 구현 (자동으로 클라이언트 쿠키는 갱신되도록)

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

    if (!req.session.userId) {    // 세션 객체에 저장된 값이 존재하지 않는 경우
      // res.send에 알맞은 메시지를 담아서 전달하면 됨
       // your code here

    } else {    // 세션 객체에 저장된 값이 존재하는 경우
      // your code here
      // TODO: 로그아웃 요청은 세션을 삭제하는 과정을 포함해야 합니다.
      //! res.session.destroy를 활용
      req.session.destroy();    // ! 그냥 이렇게 하면 현재 세션이 삭제됨    공식문서 참고
      res.sendStatus(200) // res 부분은 디스트로이 안에 넣으나 안넣으나 상관없는듯 하다(차이점)?

    }

controller/userinfo.js (GET /users/userinfo) 작업

기본적인 로직은 다음과 같다

  • 세션 객체에 저장한 값이 존재하면
  • 사용자 정보를 데이터베이스에서 조회한 후 응답으로 전달
  • 세션 객체에 저장한 값이 존재하지 않으면
  • 요청을 거절함
  // TODO: 세션 객체에 담긴 값의 존재 여부에 따라 응답을 구현하세요.
    // HINT: 세션 객체에 담긴 정보가 궁금하다면 req.session을 콘솔로 출력해보세요
    // console.log(req.session) 찍어보기!

    if (!req.session.userId) {    //세션 객체에 저장된 값이 존재하지 않는 경우
      // your code here
     // res.send 로 요청 거절 메시지를 작성하자

    } else {    // 세션 객체에 저장된 값이 존재하는 경우    

      // TODO: 데이터베이스에서 로그인한 사용자의 정보를 조회한 후 응답합니다.
      //! sequelize의 findOne 메서드를 활용하여 구현 가능!
      const userInfo = await Users.findOne({  // DB의 Users 테이블에서 조회하여 응답
        where: { userId: req.session.userId},
      }).catch(err => res.json(err));    //에러이면 에러
      res.status(200).json({data: userInfo, message: '잘되었다는 메시지'})    // 정상적으로 진행되면, res로 응답
    }

클라이언트 작업

클라이언트 작업 구현 전에 package.json 의 scripts.start 부분을 알맞게 수정해 줘야 한다(환경 변수 경로 지정)

components/Mypage.js 작업

app.js에서 Mypage로 props를 전달하는데, logoutHandler 함수와 userData 상태(state)값을 전달해 주고 있다

// Mypage.js의 해당 함수 부분에서 코드 작업

const handleLogout = () => {
    // TODO: 서버에 로그아웃 요청을 보낸다음 요청이 성공하면 props.logoutHandler를 호출하여 로그인 상태를 업데이트 해야 합니다.
    //! axios 모듈을 이용하여 요청을 처리함
    //axios.post(첫번째인자: url, 두번째인자:data, 세번째 인자: config) 인데, 자세한 용법은 찾아볼 것
    axios
    .post('https://해당url', null, {    
      //config 부분
      'Content-type': 'application/json',
      withCredentials: true,  //암호화 부분?
    })
    .then((re) => props.logoutHandler())  // 성공시 props.logoutHandler를 호출하여 로그인 상태를 업데이트 
    .catch(e => alert(e))
  };

components/Login.js 작업

마찬가지로 app.js에서 Login.js로 props를 전달하는데, loginHandler 함수와 setUserInfo 함수를 전달해 주고 있다

//Login.js의 해당 함수 부분에서 작업

  loginRequestHandler() {
    // TODO: 로그인 요청을 보내세요.
    // 로그인에 성공하면
    // - props로 전달받은 함수를 호출해, 로그인 상태를 변경하세요.
    // - GET /users/userinfo 를 통해 사용자 정보를 요청하세요
    //
    // 사용자 정보를 받아온 후
    // - props로 전달받은 함수를 호출해, 사용자 정보를 변경하세요.

    //! 마찬가지로 axios를 통해 처리

    axios
    .post('https://localhost:4000/users/login',{  // 로그인 정보를 전달
      //data 부분
      username: this.state.name,
      password: this.state.password,
    }, {
      // config 부분
      'Content-type': 'application/json',
      withCredentials: true,  //암호화 부분?
    })  
    .then(res => {  // 로그인에 성공하면, 로그인 상태를 변경
      this.props.loginHandler(true) //app.js의 함수를 호출해서 상태를 변경
      return axios.get('https://localhost:4000/users/userinfo', { // get 을 통해 사용자 정보를 요청
        //config 설정
        withCredentials: true,
      })
    })
    .then(res => {  // 받아온 정보를 통해서
        let {userId, email} = res.data.data;  // 구조분해
        this.props.setUserInfo({  // 구조분해를 이용해 사용자 정보 변경
          userId,
          email
        })
      })
      .catch(err => alert(err));  // 에러있으면 에러 알람
  }
저작자표시 (새창열림)

'SE Bootcamp 내용 정리' 카테고리의 다른 글

인증/보안 - OAuth 인증 구현  (0) 2021.11.26
인증/보안 - 기초 2: CSRF, Token  (0) 2021.11.25
인증/보안 - 기초 1: HTTPS, Hashing, Cookie, Session  (0) 2021.11.24
데이터베이스 - MVC 패턴 연습  (0) 2021.11.19
데이터베이스 - MVC 패턴 기초  (0) 2021.11.16
'SE Bootcamp 내용 정리' 카테고리의 다른 글
  • 인증/보안 - OAuth 인증 구현
  • 인증/보안 - 기초 2: CSRF, Token
  • 인증/보안 - 기초 1: HTTPS, Hashing, Cookie, Session
  • 데이터베이스 - MVC 패턴 연습
레실이
레실이
  • 레실이
    레실이의 티스토리
    레실이
  • 전체
    오늘
    어제
    • 분류 전체보기 (91)
      • SE Bootcamp 내용 정리 (63)
      • 알고리즘 연습 (7)
      • Project 주저리 (4)
      • 기술 면접 source (3)
      • 개발 일상 (12)
      • 생성 AI 활용 (1)
  • 블로그 메뉴

    • 홈
    • 태그
    • 방명록
  • 링크

  • 공지사항

  • 인기 글

  • 태그

    mongoDB
    Ajax
    Linux
    useState
    state
    JS
    IT
    fastapi
    react
    객체
    MVC
    CSR
    node
    ubuntu
    인증/보안
    Python
    데이터베이스
    CSS
    useRef
    JavaScript
    알고리즘
    DOM
    PickAndDrink
    ORM
    promise
    CORS
    문자열
    node.js
    비동기
    자료구조
  • 최근 댓글

  • 최근 글

  • hELLO· Designed By정상우.v4.10.3
레실이
인증/보안 - session 기반 인증 구현하기 실습
상단으로

티스토리툴바