Sid쿠키 유무를 감시 및 세션정보 받아오기

방충림·2023년 4월 15일
4

Redux

목록 보기
1/4
post-thumbnail

인가(Authorization)에 필요한 기능

웹페이지에 로그인을 하면 웹 페이지는 다음과 같은 인가의 기능을 가져야한다.

  1. 새로고침이나 화면이 전환되어도 로그인 중이라는 사실을 기억해야한다.

  2. 로그인 중인 사용자의 정보를 바탕으로 해당 사용자에게 특화된 정보를 제공해야한다.

이 기능들은 session 또는 JWT로 구현할 수 있는데, 여기에서는 session에 관련된 내용만 다루어 보겠다.

인증과 인가의 차이

세션의 원리와 리덕스의 필요성

로그인을 하면 서버는 계정과 비밀번호를 확인해서 로그인을 진행해주는 동시에 DB에서 꺼내온 해당 회원의 정보를 서버의 세션 객체라는 곳에 저장한다. 그리고 이 정보를 참조할 수 있는 일종의 키인 sid쿠키를 브라우저로 전송하여 저장한다.

(DB에서 꺼내온 회원의 정보를 클라이언트로 바로 가져와서 클라이언트의 변수에 저장하는 것이 아니라, 세션 객체에 저장해놓고 필요할 때마다 꺼내오는 이유는 보안성 때문이다.)

클라이언트는 현재 로그인한 회원의 정보가 필요할 때마다, 자신의 가진 sid쿠키를 요청에 실어 서버로 전달한다. 그러면 서버는 그 sid쿠키와 일치하는 정보를 클라이언트로 보내준다.

이때! 웹페이지의 수많은 다양한 곳에서 쿠키의 여부와 세션의 정보가 필요할 것이다.
이 곳마다 모두 쿠키를 읽어들이고, fetch문을 작성하는 것은 번거로우므로 이것을 리덕스에서 구현하고 편하게 가져다 쓸 수 있도록 하겠다.

리덕스 코드 살펴보기

src 디렉토리의 하위에 다음과 같은 디렉터리 구조를 생성하였다.


types.js

export const SET_HAS_SID_COOKIE = "SET_HAS_SID_COOKIE"; // 쿠키 유무정보를 저장할 액션
export const SET_SESSION = "SET_SESSION"; // 세션객체를 저장할 액션

actions.js

  • setHasSidCookie : 브라우저에 저장되어 있는 sid쿠키를 확일할 액션이다. 이 액션은 store에서 1초 마다 실행되어 쿠키의 유무를 확인할 것이다.
  • getSession : 세션객체를 받아오는 fetch문을 액션에 작성해놓았다.
    이를 통해 사용할 컴포넌트에서 디스패치로 이 함수를 호출하면 세션정보를 사용할 수 있다.
import { SET_HAS_SID_COOKIE, SET_SESSION } from "./types";
import axios from "axios";

export const setHasSidCookie = (hasSidCookie) => ({
  type: SET_HAS_SID_COOKIE,
  payload: hasSidCookie,
});

// 세션객체를 받아오는 액션
export const getSession = () => (dispatch) => {
  axios
    .get("http://localhost:5000/users/user-info", {
      // 브라우저에 저장되어있는 쿠키를 참조해서 권한 획득
      headers: {
        Authorization: `Bearer ${getCookie("sid")}`,
      },
      // 서버와 포트가 달라 CORS를 사용했기 때문에 withCredentials 명시해야함.
      //이 속성을 true로 설정하면 브라우저는 쿠키를 포함한 인증 정보를 서버에게 전달
      withCredentials: true,
    })
    .then((res) => {
      dispatch({ type: SET_SESSION, payload: res.data });
    })
    .catch((err) => {
      console.log(err);
    });
};

// 브라우저에 저장된 쿠키를 받아오는 함수  =========================
function getCookie(name) {
  const value = `; ${document.cookie}`;
  const parts = value.split(`; ${name}=`);
  if (parts.length === 2) return parts.pop().split(";").shift();
}
//=================================================================

reducer.js

import { SET_HAS_SID_COOKIE, SET_SESSION } from "./types";

const initialState = {
  hasSidCookie: false,
  session: null,
};

const sidCookieReducer = (state = initialState, action) => {
  switch (action.type) {
    case SET_HAS_SID_COOKIE:
      return {
        ...state,
        hasSidCookie: action.payload,
      };
    case SET_SESSION:
      return {
        ...state,
        session: action.payload,
      };
    default:
      return state;
  }
};

export default sidCookieReducer;

store.js

  • redux-thunk를 아래와 같이 적용해준다. thunk가 있음으로써 action.js에서 디스패치를 리턴해줄 수 있는 기능을 사용할 수 있게 되는 것이다.
  • 쿠키를 확인하여 boolean값을 반환하는 함수를 정의해놓고, 1초마다 실행시킨다.
    아까 action에서 말한 tore.dispatch(setHasSidCookie(hasSidCookie));이 여기에서 쓰이고 있다.
  • 1초마다 쿠키를 확인한다는 것이 프로젝트의 성능을 해치지않을까 걱정했는데, 알아보니 이는 통상적으로 쿠키에 변경을 확인하는 데 사용되는 방법이고, 통신을 하는 것은 아니기 때문에 1초 마다 반복하는 것 정도는 무리가 되지 않는다고 한다.
import { createStore, applyMiddleware } from "redux";
import thunk from "redux-thunk";
import sidCookieReducer from "./user/reducer";
import { setHasSidCookie, getSession } from "./user/actions";

const store = createStore(sidCookieReducer, applyMiddleware(thunk));

// 초기 상태 설정 시 세션객체를 받아오는 함수 호출
store.dispatch(getSession());

const checkSidCookie = () => {
  // 브라우저에 저장된 쿠키를 받아오는 함수
  const sidCookie = (name) => {
    const value = `; ${document.cookie}`;
    const parts = value.split(`; ${name}=`);
    if (parts.length === 2) return parts.pop().split(";").shift();
  };

  const hasSidCookie = Boolean(sidCookie("sid"));
  store.dispatch(setHasSidCookie(hasSidCookie));
};

// 1초마다 checkSidCookie 함수 호출
setInterval(checkSidCookie, 1000);

export default store;

사용할 컴포넌트.jsx

import { useEffect } from "react";
import { useSelector, useDispatch } from "react-redux"; // 1. useSelector, useDispatch 가져오기
import { getSession } from "../../redux/user/actions"; // 2. getSession 가져오기

const Test = () => {
  const dispatch = useDispatch(); // 3. dispatch변수에 useDispatch() 함수 할당
	// 4. 쿠키 여부 상태가 저장되는 변수임. boolean타입을 반환함
  const hasSidCookie = useSelector((state) => state.hasSidCookie); 
	// 5. 세션 객체가 저장되는 변수임. 객체타입 {success: true userInfo: {no: 1 ...}} 을 반환함.
  const session = useSelector((state) => state.session); 

  useEffect(() => {
  // 6. 세션 객체를 받아오는 함수 호출
	// 꼭 useEffect 안에 있어야하는 것은 아닙니다. 하지만 대부분의 경우 이렇게 사용될 듯 합니다.
    dispatch(getSession());
  }, [hasSidCookie]); // <= 이건 빈칸[]으로 두어도 상관 없는 듯 합니다. 혹시몰라 넣었습니다.

 // 7. UI에 활용하기
  return (
    <div>
      {hasSidCookie ? <p>쿠키가 있습니다.</p> : <p>쿠키가 없습니다.</p>} 
      {session ? (// <= 원래는 session으로 해도되는데, 버그(하단 참고)가 있어 일단 hasSidCookie로 사용해주세요
        <p>{session.userInfo.name}, 환영합니다!</p>
      ) : (
        <p>로그인이 필요합니다.</p>
      )}
    </div>
  );
};

export default Test;
profile
최선이 반복되면 최고가 된다.

0개의 댓글