CORS란 하나의 출처에서 실행 중인 웹이 도메인 또는 포트가 다른 서버의 자원에 접근할 수 있는 권한을 부여하도록 브라우저에 알려주는 일종의 정책.
보안의 위험을 보완하기위해 CORS체제는 브라우저와 서버 간의 안전한 교차 출처 요청 및 데이터 전송을 지원.
서버가 웹 브라우저에서 리소스를 로드할 때 다른 Origin을 통해 로드하지 못하게하는 HTTP 메커니즘 (즉, SOP를 지키기 위해서 나온 에러)
프론트엔드 개발시 프론트엔드 서버를 만들어서 백엔드 서버와 통신할 때 주로 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)
클라이언트는 서버 리소스를 요청할 때, 즉 fetch로 api를 가져올 때는 Http를 사용합니다. 이 때 브라우저는 요청 header에 출처를 담아 origin이라는 필드에 보낸다.
서버가 이를 보고 응답을 할 때 header에 access-contrl-allow-orgin (에러 발생 시 떳던 내용)이라는 값에 접근하는 것이 허용된다는 것을 내려준다.
이 응답을 받은 클라이언트는 원래 요청의 origin과 서버 응답의 access-contrl-allow-orgin을 비교해본 후 이 응답이 유효한 응답인지 아닌지를 결정하는 것.
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
참조:
리액트에서는 외부의 서버로 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
}
출처:
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' 오리진을 사용할 수도 있습니다.
SECURE_CROSS_ORIGIN_OPENER_POLICY = None
https://web.dev/why-coop-coep/
1. studyDetail.js(상세페이지)에서 Edit을 클릭하면 게시글에 대한 내용들이 (title,subtitle,category,body) dispatch를 통해 전역 store에 payload값으로 전달된다.
let goToUpdate = () => {
dispatch({ type: 'UPDATE_GO', payload: data })
navigate(`/studyUpdate/${id}`)
}
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는 reducer 값들을 localStorage 또는 session에 저장하여, 새로고침하여도 저장공간에 있는 데이터를 redux에 불러오는 형식이다
yarn add redux-persist
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);
<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://velog.io/@lethe/nginx%EC%97%90%EC%84%9C-static%ED%8C%8C%EC%9D%BC%EC%9D%84-s3%EB%A1%9C-%EC%97%B0%EB%8F%99%ED%95%98%EB%8A%94-%EB%B0%A9%EB%B2%95
- https://medium.com/codex/deploying-react-through-djangos-static-files-part-1-dev-setup-8a3a7b93c809
- https://sangmin802.github.io/Study/Think/react-router-problem/