Issue & Trouble Shooting

박정호·2022년 8월 18일
0

Portfolio

목록 보기
1/6
post-thumbnail
post-custom-banner

🧨CORS(Cross-Origin Resource Sharing)

CORS란?

  • CORS란 하나의 출처에서 실행 중인 웹이 도메인 또는 포트가 다른 서버의 자원에 접근할 수 있는 권한을 부여하도록 브라우저에 알려주는 일종의 정책.

  • 보안의 위험을 보완하기위해 CORS체제는 브라우저와 서버 간의 안전한 교차 출처 요청 및 데이터 전송을 지원.

  • 서버가 웹 브라우저에서 리소스를 로드할 때 다른 Origin을 통해 로드하지 못하게하는 HTTP 메커니즘 (즉, SOP를 지키기 위해서 나온 에러)

  • 프론트엔드 개발시 프론트엔드 서버를 만들어서 백엔드 서버와 통신할 때 주로 CORS 에러가 나는데, 이를 해결하기 위해 프론트엔드에서 프록시 서버를 만들기도 한다.

CORS 예시

프론트엔드에서는 127.0.0.1:3000으로 테스팅 , 백엔드 서버에서는 127.0.0.1:8000
-> 서로 포트번호가 다르기 때문에 CORS 에러 발생
프론트엔드앱에서 API백엔드서버(Django rest framework)에 리소스를 요청하지만, Django는 이를 정책위반이라 판단하여, 프론트엔드에게 데이터를 제공해주지 않게되고, 결국 프론트엔드앱은 웹 브라우저에 다음과 같은 에러

잠깐) 오리진 & SOP
오리진: 프로토콜과 호스트 이름, 포트의 조합을 말한다.
https://houya.com:12010/test 라는 주소에서 오리진은 https://houya.com:12010 을 뜻.

SOP: Same Origin Policy라는 뜻, 같은 오리진끼리 로드한다는 의미

상황: 사용자가 브라우저를 통해 네이버를 보는데, 네이버는 네이버의 서버에서 받아온 정보를 받아와서 화면단에 보이는 것. 만약 공격자가 하이재킹으로 서버의 정보를 빼내려한다면?
-> 이를 방지하기 위해 같은 오리진끼리만 정보 전달이 되게 설정한 것(SOP)

CORS 원리

  1. 클라이언트는 서버 리소스를 요청할 때, 즉 fetch로 api를 가져올 때는 Http를 사용합니다. 이 때 브라우저는 요청 header에 출처를 담아 origin이라는 필드에 보낸다.

  2. 서버가 이를 보고 응답을 할 때 header에 access-contrl-allow-orgin (에러 발생 시 떳던 내용)이라는 값에 접근하는 것이 허용된다는 것을 내려준다.

  3. 이 응답을 받은 클라이언트는 원래 요청의 origin과 서버 응답의 access-contrl-allow-orgin을 비교해본 후 이 응답이 유효한 응답인지 아닌지를 결정하는 것.

👍CORS 해결방법

1. 프론트엔드(React)에서 target 주소를 설정

프록시 서버를 둬서 프론트엔드 서버에서 요청되는 오리진을 127.0.0.1:8000로 변경

axios.get("/stocks/days")
module.exports = {
	devServer:{
    	proxy:{
        '^/stocks':{
        	target:'http://127.0.0.1:8000'
            }
           }
          }
        }

2. 서버(Django)에서 CORS 설정

1. Django settings.py에 corheader 패키지 설치

pip install django-cors-headers

2. setting.py 해당 코드 작성

# settings.py
INSTALLED_APPS = [
    ...
    # 추가
    'corsheaders',
]
MIDDLEWARE = [
    # 추가
    'corsheaders.middleware.CorsMiddleware',
    ...
]
CORS_ORIGIN_WHITELIST = [
    # 허용할 프론트엔드 도메인 추가 EX:
    'http://localhost:3000',
    'https://localhost:3000',
    'http://127.0.0.1:8000',
]
CORS_ORIGIN_ALLOW_ALL = True  
CORS_ALLOW_CREDENTIALS = True

참조:

🧨CSRF(Cross Site Request Forgery)

  • 웹사이트 취약점 공격의 하나로, 사용자가 자신의 의지와는 무관하게 공격자가 의도한 행위(수정, 삭제, 등록 등)를 특정 웹사이트에 요청하게 하는 공격

Django의 CSRF 방어

  • Django에서는 기본적으로 csrf token을 이용
  • POST 요청에 대해서만 csrf token을 발급하고 체크

CSRF token 동작과정

  1. 사용자가 해당 페이지에 접속하면 Django에서 자동으로 csrf_token을 클라이언트로 보내어 cookie에 저장
  2. 사용자가 form을 모두 입력한 후 제출버튼을 클릭한다.
  3. form과 cookie의 csrf_token을 함께 POST로 전송한다.
  4. 전송된 token의 유효성을 검증
  5. 유효한 요청이면 요청을 처리
    token이 유효하지 않거나(없거나 값이 잘못된 경우) 검증 오류 시에는 403 Forbidden Response 반환

👍CSRF 설정 IN React

리액트에서는 외부의 서버로 http 통신을 할때 주로 axios를 이용한다. axios로 call을 보내고 response를 받을때 csrf token을 설정해주어야 한다.

방법 1.index.js에 아래 코드 추가

axios.defaults.xsrfCookieName = 'csrftoken';
axios.defaults.xsrfHeaderName = 'X-CSRFTOKEN';

방법 2. getCookie('csrftoken') 함수 호출

export const createPost = async (data) => {

    function getCookie(name) {
        let cookieValue = null;
        if (document.cookie && document.cookie !== '') {
            const cookies = document.cookie.split(';');
            for (let i = 0; i < cookies.length; i++) {
                const cookie = cookies[i].trim();
                // Does this cookie string begin with the name we want?
                if (cookie.substring(0, name.length + 1) === (name + '=')) {
                    cookieValue = decodeURIComponent(cookie.substring(name.length + 1));
                    break;
                }
            }
        }
        return cookieValue;
    }

    let csrftoken = getCookie('csrftoken');
    let url = `http://localhost:8000/api/notes/`

    const response = await axios.post(url, data, {
        headers: {
            'X-CSRFToken': csrftoken,
        },
    })
    return response.data
}

출처:

COOP(Cross-Origin-Opener-Policy)

  • 외부 접속을 위해, 로컬IP주소(192.168.xx) 또는 글로벌IP주소(225.235.xxx)의 주소로 접속 시 다음과 같은 문제 발생. CORS,CSRF문제 해결되었지만 아직 COOP 에러에 대한 문제 해결 중.

🧨COOP 정의

  • top-level document가 cross-origin 상태의 document와 browsing context group을 공유하지 못하도록 제한하는 보안 정책이자 헤더
  • 이러한 헤더는 document를 격리시켜 XS-Leaks 같은 공격을 통해 document 내부를 엑세스할 수 없도록 제한
The Cross-Origin-Opener-Policy header has been ignored, because the URL's origin was untrustworthy. It was defined either in the final response or a redirect. Please deliver the response using the HT
TPS protocol. You can also use the 'localhost' origin instead.
해석)
URL의 출처를 신뢰할 수 없기 때문에 Cross-Origin-Opener-Policy 헤더가 무시되었습니다. 최종 응답 또는 리디렉션에서 정의되었습니다. HTTPS 프로토콜을 사용하여 응답을 전달하십시오. 대신 'localhost' 오리진을 사용할 수도 있습니다.

👍COOP 해결

  • Django setting.py에 다음과 같은 코드 추가
SECURE_CROSS_ORIGIN_OPENER_POLICY = None
  • 장고의 공식 문서에서 확인해보면 다음과 같은 문장이 있다.
    -> SECURE_CROSS_ORIGIN_OPENER_POLICY ¶ Default: 'same-origin'
    Unless set to None, the SecurityMiddleware sets the Cross-Origin Opener Policy header on all responses that do not already have it to the value provided.
    해석) None으로 설정하지 않는 한, SecurityMiddleware는 제공된 값에 대해 아직 가지고 있지 않은 모든 응답에 대해 Cross-Origin Opener Policy 헤더를 설정합니다.

https://web.dev/why-coop-coep/

🧨새로고침 시 state값 날아가는 현상

  • 해당 프로젝트는 redux 상태 관리 라이브러리를 사용하였다. 그 중 studyUpdata.js(수정페이지)에서 새로고침 시 state값이 날아가는 현상이 발생하였다.

이슈 부분

1. studyDetail.js(상세페이지)에서 Edit을 클릭하면 게시글에 대한 내용들이 (title,subtitle,category,body) dispatch를 통해 전역 store에 payload값으로 전달된다.

 let goToUpdate = () => {
        dispatch({ type: 'UPDATE_GO', payload: data })
        navigate(`/studyUpdate/${id}`)
    }
  • edit 클릭

2. payload 값은 getUpdateData라는 reducer를 거쳐가게 되고, return된다.

export const getUpdateData = (state = initialState, action) =>{
    switch(action.type){
        case UPDATE_GO:
           return action.payload
        default:
            return state
    }
}

3. 이 데이터들은 수정페이지에서 useSelector를 통해 전달된다.

    const state = useSelector((state) => state.getUpdateData)

4. 해당 state값들을 바로 form태그에 저장할 수 있지만, 해당 form태그는 onChange에 e.target.value를 통해 값을 입력하는 text type이다. 따라서, 데이터들을 state값에 저장하고 이이서 입력할 수 있는 기능을 구현하고 싶었다.

// 해당 form에 입력한 값들이 저장될 state
 const [inputs, setInputs] = useState({
        category: '',
        title: '',
        subTitle: '',
    })
// 컴포넌트 업데이트 시 setState를 통하여 데이터들이 state값에 저장
useEffect(() => {
        setInputs({
            title: state.title,
            subTitle: state.subTitle,
            category: state.category,
        })
      }, [])
    
const { category, title, subTitle } = inputs
 // input값에 입력하는 값들이 onChange를 통해 실시간으로 입력 
 onst onChange = useCallback(
        (e) => {
            const { value, name } = e.target // 우선 e.target 에서 name 과 value 를 추출
            setInputs({
                ...inputs,
                [name]: value, // name 키를 가진 값을 value 로 설정 (이때 [name]은 계산된 속성명 구문 사용)
            })
        },
        [inputs]
    )
  • 상세페이지의 데이터들이 수정페이지에 전달

5. 여기서 문제는 새로고침하면 redux의 값들이 날라가버린다는 것이다.
처음에는 해당 값들이 잘 전달된 이유는 상세페이지에서 dispatch로 값을 전달해주고 수정페이지에서 useSelector를 통해서 값을 받아오는 과정이 한번에 잘 수행됬기 때문이다.
하지만, 수정페이지에서 새로고침을 하면 reducer에 전달된 값들은 날아가버리게 되고, 수정페이지에는 전달된 값들이 존재하지 않기 때문에 컴포넌트 업데이트시 발생하는 setState 속 값들은 존재하지 않게 된다. 따라서, 에러가 나게 된다.

👍redux-persist

redux-persist는 reducer 값들을 localStorage 또는 session에 저장하여, 새로고침하여도 저장공간에 있는 데이터를 redux에 불러오는 형식이다

1. 설치

yarn add redux-persist

2. reducer에 persist store 정의

localStorage에 저장하고 싶으면 import storage from 'redux-persist/lib/storage
session Storage에 저장하고 싶으면 import storageSession from 'redux-persist/lib/storage/session

<rootreducer.js>

import {persistReducer} from 'redux-persist'
import storage from 'redux-persist/lib/storage'

const persistConfig = {
    key: "root",
    // localStorage에 저장합니다.
    storage,
    // auth, board, studio 3개의 reducer 중에 auth reducer만 localstorage에 저장합니다.
    whitelist: ["getUpdateData"]
    // blacklist -> 그것만 제외합니다
  };
  
const rootReducer = combineReducers({posts, creates, deletes, updates, getUpdateData});

export default persistReducer(persistConfig, rootReducer);

3. persist store 사용

<index.js>
import { persistStore } from "redux-persist";
import { PersistGate } from "redux-persist/integration/react";
...
const persistor = persistStore(store);

root.render(
    <React.StrictMode>
        <HashRouter>
            
        <Provider store={store}>
        <PersistGate loading={null} persistor={persistor}>

            <App />
            </PersistGate>

        </Provider>
        </HashRouter>
    </React.StrictMode>
)

참조: https://kyounghwan01.github.io/blog/React/redux/redux-persist/#persist-store-%E1%84%89%E1%85%A1%E1%84%8B%E1%85%AD%E1%86%BC

참조 및 참고하기 좋은 사이트

profile
기록하여 기억하고, 계획하여 실천하자. will be a FE developer (HOME버튼을 클릭하여 Notion으로 놀러오세요!)
post-custom-banner

0개의 댓글