출처(Origin)는 URL의 Protocol, Host, Port로 정의 된다. 두 객체의 Protocol, Host, Port가 일치할 경우 같은 출처라고 결론 내릴 수 있다.
개발자 도구의 콘솔 창에location.origin
를 실행하면 출처를 확인할 수 있다.
동일 출처 정책을 지키면 외부 리소스를 가져오지 못해 불편하지만, 동일 출처 정책은 CSRF(Cross-Site Request Forgery)나 XSS(Cross-Site Scripting) 등의 보안 취약점을 노린 공격을 방어할 수 있다. 하지만 현실적으로는 외부 리소스를 참고하는 것은 필요하기 때문에 CORS 조항을 통해 외부 리소스를 가져올 수 있게 한다.
🌈 외부 리소스를 사용하기 위한 SOP의 예외 조항이 CORS이다. 클라이언트의 요청이 있을 경우, 서버와 클라이언트가 정해진 헤더를 통해 서로 요청이나 응답에 반응할지 SOP 또는 CORS 정책에 따라 웹브라우저가 평가하고 결정한다.
CORS의 동작의 기본적인 flow를 살펴보면 아래와 같다.
CORS 동작은 위와같은 기본적인 flow로 흘러가지만, 이제 부터 소개할 Request 방식에 따라 flow는 변경된다.
😰 세 가지의 조건 중 첫 번째 조건은 쉬운 조건 이지만 두 번째, 세 번째 조건은 충족시키기 어려운 조건이다. 두 번째 조건은 사용자 인증에 사용되는 Authorization 헤더도 포함되지 않아 충족시키기 어려운 조건이며, 세 번째 조건은 많은 REST API들이 Content-Type으로 application/json을 사용하기 때문에 만족시키기 어려운 조건이다.
OPTION
method가 사용된다. OPTIONS
method로 서버에 예비 요청을 먼저 보내면, 서버는 Access-Control-Allow-Origin 헤더를 포함한 응답을 브라우저에 보낸다. 브라우저는 단순 요청과 동일하게 Access-Control-Allow-Origin 헤더를 확인해서 CORS 동작을 수행할지 판단한다.Credentialed Request는 CORS의 기본 방식이라기 보단 다른 출처 간 통신의 보안을 좀 더 강화할때 사용하는 방법이다.
기본적으로 브라우저가 제공하는 비동기 리소스 요청 API인 XMLHttpRequest 객체나 fetch API는 별도의 옵션 없이 브라우저의 쿠키 정보나 인증과 관련된 헤더를 함부로 요청에 담지 않는다. 이때 요청에 인증과 관련된 정보를 담을 수 있게 해주는 옵션이 바로 credentials 옵션이다.
👉 credentials 옵션에는 아래와 같은 3가지 값을 사용할 수 있다.
same-origin (기본값) : 같은 출처 간 요청에만 인증 정보를 담을 수 있다
include : 모든 요청에 인증 정보를 담을 수 있다
omit : 모든 요청에 인증 정보를 담지 않는다
예시)
fetch('https://velog.io/@lck0827', { credentials: 'include', // Credentials 옵션 변경 });
👉 동일 출처 여부와 상관없이 무조건 요청에 인증 정보가 포함되도록 설정한 것이다.
위와 같은 옵션을 사용하여 리소스 요청에 인증 정보가 포함된다면, 브라우저는 다른 출처의 리소스를 요청할 때 단순히 Access-Control-Allow-Origin만 확인하는 것이 아니라 좀 더 까다로운 검사 조건을 추가하게 된다.
요청에 인증 정보가 담겨있는 상태에서 다른 출처의 리소스를 요청하게 되면 브라우저는 CORS 정책 위반 여부 검사시 아래의 두 가지 룰을 추가하게 된다.
- Access-Control-Allow-Origin에는 *를 사용할 수 없으며, 명시적인 URL이어야한다.
- 응답 헤더에는 반드시 Access-Control-Allow-Credentials: true가 존재해야한다.
CORS 에러 해결의 가장 대표적인 방법은, 서버에서 Access-Control-Allow-Origin 헤더에 알맞은 값을 세팅해주는 것이다.
*
로 설정해 주면 모든 출처의 요청을 허가하여 편하긴 하지만 보안적인 이슈가 발생할 수 있으므로 가급적이면 Access_Control_Allow_Origin:https://velog.io/@lck0827
와 같이 출처를 명시해 주는 것이 좋다.
이 헤더는 Nginx나 Apache와 같은 서버 엔진의 설정에서 추가할 수도 있지만, 복잡하므로 소스 코드 내에서 응답 미들웨어 등을 사용하여 세팅하는 것이 편리하다.
Spring, Express, Django와 같이 대중적인 백엔드 프레임워크의 경우에는 모두 CORS 관련 설정을 위한 세팅이나 미들웨어 라이브러리를 제공하여 세팅하기가 용이하다.
django에서는 CORS를 해결하기위한 패키지를 제공하고 있다.
pip install django-cors-headers
설치 후, settings.py의 INSTALLED_APP과 MIDDLEWARE에 아래 정보를 추가한다.
INSTALLED_APPS = [
...
'corsheaders'
]
MIDDLEWARE = [
...
'corsheaders.middleware.CorsMiddleware',
]
##CORS
CORS_ORIGIN_ALLOW_ALL=True
# 모든 호스트를 허용시키기 위해 CORS_ORIGIN_ALLOW_ALL을 True로 지정
CORS_ALLOW_CREDENTIALS = True
# CORS_ALLOW_CREDENTIALS은 쿠키가 cross-site HTTP 요청에 포함될 수 있게하기 위한 설정이고 Default는 False임.
CORS_ALLOW_METHODS = (
'DELETE',
'GET',
'OPTIONS',
'PATCH',
'POST',
'PUT',
)
CORS_ALLOW_HEADERS = (
'accept',
'accept-encoding',
'authorization',
'content-type',
'dnt',
'origin',
'user-agent',
'x-csrftoken',
'x-requested-with',
)
CORS_ORIGIN_ALLOW_ALL=True
설정을 삭제하고 CORS_ORIGIN_WHITELIST를 추가한다. ##CORS
CORS_ORIGIN_WHITELIST = (
"https://example.com",
"https://sub.example.com",
"http://localhost:8080",
"http://127.0.0.1:9000"
)
CORS_ALLOW_CREDENTIALS = True
CORS_ALLOW_METHODS = (
'DELETE',
'GET',
'OPTIONS',
'PATCH',
'POST',
'PUT',
)
CORS_ALLOW_HEADERS = (
'accept',
'accept-encoding',
'authorization',
'content-type',
'dnt',
'origin',
'user-agent',
'x-csrftoken',
'x-requested-with',
)
npm i cors --save
const express = require('express');
const cors = require('cors');
const app = express();
app.use(cors());
미들웨어 설치 후 App.js에 미들웨어를 추가하면 세팅 완료된다.
위와 같이 아무런 옵션없이 app.use(cors());로 설정한다면 모든 cross-origin 요청에 대해 응답을 해주게 된다.
만약 모든 요청에 응답하는 것이 아닌 보안상 특정 도메인 요청만 받아야 하는 경우 혹은 특정 요청에만 응답하는 경우에는 아래와 같이 옵션들을 다양하게 설정해 줄 수 있다.
1. 특정 도메인 접근 허용
const corsOptions = {
origin: 'https://velog.io', // 접근 권한을 부여하는 도메인
credentials: true, // 응답 헤더에 Access-Control-Allow-Credentials 추가
optionsSuccessStatus: 200 // 응답 상태 200으로 설정
};
app.use(cors(corsOptions));
2. 특정 요청 접근 허용
app.get('/products/:id', cors(), function (req, res, next) {
res.json({msg: 'This is CORS-enabled for a Single Route'})
});
이외에도 특정 method 등을 옵션으로도 설정할 수도 있다.
📝 Reference
1. https://developer.mozilla.org/ko/docs/Web/Security/Same-origin_policy
2. https://beomy.github.io/tech/browser/cors/
3. https://evan-moon.github.io/2020/05/21/about-cors/