웹페이지에 로그인을 하면 웹 페이지는 다음과 같은 인가
의 기능을 가져야한다.
새로고침이나 화면이 전환되어도 로그인 중이라는 사실을 기억해야한다.
로그인 중인 사용자의 정보를 바탕으로 해당 사용자에게 특화된 정보를 제공해야한다.
이 기능들은 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
tore.dispatch(setHasSidCookie(hasSidCookie));
이 여기에서 쓰이고 있다.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;