CORS는 Cross Origin Reasource Sharing의 약자로 직역하면 교차 출처 리소스 공유라는 뜻이다.
CORS는 추가 HTTP 헤더를 사용하여, 한 출처에서 실행 중인 웹 애플리케이션이 다른 출처의 선택한 자원에 접근할 수 있는 권한을 부여하도록 브라우저에 알려주는 체제이다.
쉽게 말해 다른 출처의 리소스 공유에 대한 허용/비허용 정책이다.
여기서 교차 출처는 다른 출처를 말한다. 그럼 출처(Origin)는 무엇일까?
URL은 하나의 문자열이 아닌 여러 구성요소로 이루어져있다. 출처(Origin)는 Protolcol
, Host
, Port
를 합친 URL을 의미한다.
SOP는 동일 출처 정책이란 뜻으로 ‘동일한 출처에서만 리소스를 공유할 수 있다’ 라는 규칙을 가지고있다. 따라서 브라우저는 다른 출처의 자원에 접근 하는 것을 차단한다.
만약 SOP 정책이 없다면 해커가 CSRF나 XSS 등의 방법을 이용해 개인 정보를 탈취할 수 있다.
하지만 그렇다고 다른 출처로 리소스를 요청할 수 없는건 아니다. CORS 정책만 지킨다면 가능하다.
➕ CSRF (cross site request forgery attack)
사용자의 의지와 무관하게 공격자가 의도한 행동을 특정 웹페이지에 요청하게 하는 공격 방법이다.
➕ XSS (Cross-site Scripting)
공격자가 상대방의 웹 사이트에 악의적 스크립트를 삽입하는 공격 방법이다.
결론은 서버에서 Access-Control-Allow-Origin 헤더에 허용할 출처를 기재해서 클라이언트에 응답하면 된다.
CORS 동작 방식은 한가지가 아니라 3가지 시나리오 따라 변경된다.
브라우저는 본 요청 전 예비 요청을 통해 이 요청을 보내는 것이 안전한지 확인한다.
예비 요청으로 인해 실제 요청 시간 증가와 서버 요청이 배로 발생해 비용, 성능 문제가 생긴다.
하지만 이는 서버로 부터 Access-Control-Max-Age 응답 헤더를 받아 해당 시간동안 브라우저 캐시에 결과를 저장해 해결할 수 있다.
단순 요청은 예비 요청 없이 본 요청을 보낸 후, 서버가 이에 대한 응답의 헤더에 Access-Control-Allow-Origin 헤더를 보내주면 브라우저가 CORS정책 위반 여부를 검사하는 방식이다.
하지만 단순 요청은 3가지 경우를 만족해야 한다.
Accept
, Accept-Language
, Content-Language
, Content-Type
, DPR
, Downlink
, Save-Data
, Viewport-Width
, Width
헤더일 경우application/x-www-form-urlencoded
, multipart/form-data
, text/plain
중 하나위 조건은 까다롭고 대부분 HTTP API 요청은 text/xml
이나 application/json
으로 통신해서 Content-Type에 위반된다. 따라서 대부분 예비 요청이 일어난다.
인증된 요청은 클라이언트가 서버로 자격 인증 정보(쿠키, 토큰 등)를 실어 요청할때 사용되는 요청이다.
또한 예비 요청 처럼 preflight가 먼저 일어난다.
기본적으로 브라우저가 제공하는 요청 API 들은 인증 관련된 데이터를 요청 데이터에 담지 않도록 되어있지만 credentials 옵션으로 요청에 인증과 관련된 정보를 담을 수 있다.
이 옵션은 3가지의 값을 사용할 수 있다. 별도 설정을 하지 않으면 인증 정보는 서버에 자동 전송되지 않는다.
인증된 요청을 보내는 방법은 fetch 메서드나 axios, jQuery 라이브리리 등이 있다.
서버도 인증된 요청에 대해 일반적인 CORS 요청과는 다르게 대응해야 한다.
Access-Control-Allow-Origin
값에 와일드카드 문자("*")는 사용할 수 없고 분명한 Origin으로 설정해야 한다.Access-Control-Allow-Methods
, Access-Control-Allow-Headers
값에 와일드카드 문자("*")는 사용할 수 없다.서버측에서 Access-Control-Allow-Origin 헤더에 유효한 값을 포함해 브라우저에 응답하면 된다.
또한 Access-Control-Allow-Origin 값에 와일드카드 문자("*")를 사용하면 모든 출처의 요청을 받아 보안적 이슈가 발생하기에 분명한 Origin을 설정해야 한다.
// CORS관련 HTTP 헤더 값
Access-Control-Allow-Origin
Access-Control-Request-Methods
Access-Control-Allow-Headers
Access-Control-Max-Age
Access-Control-Allow-Credentials
Access-Control-Expose-Headers
요청해야 하는 URL 앞에 프록시 서버 URL을 붙여서 요청하게 된다. 프록시 서버를 사용하면 중간에 요청을 가로채서 HTTP 응답헤더에 Access-Control-Allow-Origin : * 를 설정해준다.
// heroku 프록시 서버 URL
https://cors-anywhere/herokuapp.com
// 요청
axois({
method: "GET",
url: `https://cors-anywhere/herokuapp.com/{주소}`,
header:{
'APIKey': ${API_key}
}
})
현재 무료 프록시 서비스들은 모두 악용 사례로 api 요청 횟수 제한을 두어 실전이 아닌 테스트용으로 사용해야 한다. 실전에서는 프록시 서버를 구축하여 사용해야 한다.
webpack-dev-server가 제공하는 프록시 기능을 사용한다. 이는 로컬에서만 가능하다.
아래의 설정을 하면 /api
로 시작하는 URL로 보내는 요청을 브라우저는 localhost:8000/api
로 요청한줄 알지만 실은 웹팩이 target값인 URL로 요청을 프록싱한다.
module.exports = {
devServer: {
proxy: {
'/api': {
target: 'https://api.evan.com',
changeOrigin: true,
pathRewrite: { '^/api': '' },
},
}
}
}
로컬 환경에서 사용하여 클라이언트단에서 쉽게 해결할 수 있다.
http-proxy-middleware
를 설치하고 src 폴더
안에setupProxy.js
파일을 만들고 아래 코드를 작성하면 된다. 그럼 로컬 환경에서 http://localhost:3000/api
로 시작하는 요청을 라이브러리가 http://localhost:5000/api
로 프록싱 해준다.
const { createProxyMiddleware } = require("http-proxy-middleware")
module.exports = function (app) {
app.use(
"/api",
createProxyMiddleware({
target: "http://localhost:5000",
changeOrigin: true,
})
)
}
CRA로 생성한 프로젝트에서는 package.json에 proxy 값을 설정해 proxy기능을 활성화할 수 있다.
{
//...
"proxy": "http://localhost:4000"
}
브라우저에서 css나 js 같은 리소스 파일들은 SOP 영향을 받지않아 외부 서버에서 읽어온 js 파일을 json으로 바꿔주는 일종의 편법이다. 단점은 GET 방식의 API만 요청이 가능하다.
https://inpa.tistory.com/entry/WEB-📚-CORS-💯-정리-해결-방법-👏
https://evan-moon.github.io/2020/05/21/about-cors
https://xiubindev.tistory.com/115