CORS란? 개념 정리 및 동작 방식
📌 SOP(Same Origin Policy) 정책
- 동일한 출처에서만 리소스를 공유할 수 있다. 👉 동일한 출처에 대한 정책
- 동일 출처 서버에 있는 리소스는 자유롭게 가져올 수 있지만, 다른 출처 서버에 있는 이미지나 유튜브 영상 같은 리소스들은 상호작용이 불가능하다.
SOP는 2011년, RFC 6454에서 처음 등장한 보안 정책으로 말 그대로 "같은 출처에서만 리소스를 공유할 수 있다" 라는 규칙을 가진 정책이다.
✅ 동일 출처 정책이 필요한 이유? "CSRF 공격!"
만일 제약이 없다면, 해커가 CSRF(Cross-Site Request Forgery) 나 XSS(Cross-Site Scripting)등의 방법을 이용하여 우리가 만든 애플리케이션에서 해커가 심어놓은 코드가 실행되어 개인 정보들을 가로 챌 수 있다.
1. 악의적인 마음을 품은 해커가 웹사이트를 구축
2. 해당 웹사이트를 가리키는 링크를 담은 메일을 사용자 A에게 전송
(A는 웹사이트에 로그인 되어 있고, 브라우저 단에 인증 정보가 존재한다고 가정)
3. A가 실수로 해당 링크를 클릭하여 해커의 웹사이트에 접속하면 해커가 심어둔 JavaScript 코드가 실행되어
자기도 모르게 해커의 웹사이트로 개인 정보를 조회하는 API 요청을 보냄
4. A의 브라우저 단에는 인증 정보가 존재하기 때문에 이것이 해당 요청과 함께 실어서 전송되면 서버는 인증된 요청이라 생각하여 개인 정보를 응답해줌 👉 해커는 개인정보를 빼돌릴 수 있게 됨
✅ CORS 정책을 지킨 리소스 요청은 허용!
그러나 웹이라는 오픈스페이스 환경에서 다른 출처에 있는 리소스를 가져와 사용하는 일은 매우 흔한 일이라 무작정 막을 수 없는 노릇이니 몇 가지 예외 조항을 두고 이 조항에 해당하는 리소스 요청은 출처가 다르더라도 허용하기로 했는데, 그 중 하나가 "CORS 정책을 지킨 리소스 요청"이다.
📌 CORS(Cross-Origin Resource Sharing)
- 교차 출처 리소스 공유 정책 👉 다른 출처의 리소스 공유에 대한 허용/비허용 정책
- 현재 실행 중인 Origin에서 다른 Origin의 리소스에 접근 할 수 있도록 브라우저에게 알려주는 것
SOP 정책에 따라 보안을 지키는 것도 중요하지만, 개발을 하다 보면 기능상 어쩔 수 없이 다른 출처 간의 상호작용을 해야하는 케이스가 있고, 실무적으로 다른 회사의 서버 API를 이용해야 하는 상황도 존재한다. 따라서 이와 같은 예외 사항을 두기 위해 CORS 정책을 허용하는 리소스에 한해 다른 출처라도 받아들인다는 것
"다른 출처의 리소스를 공유할 수 있는 해결책"
📌 출처(Origin)
- Protocol + Host + Port 를 합친 URL를 의미
👉 서버의 위치를 찾아가기 위해 필요한 가장 기본적인 것들을 합쳐놓은 것 - 출처 내 Port 번호는 생략이 가능함
👉 웹에서 사용하는 HTTP, HTTPS 프로토콜의 기본 포트 번호가 정해져 있기 때문
✅ 다른 출처(Origin) 란?
https://www.example.com:3000 에 대한 동일 출처 비교 표이다.
비교 대상 URL | 동일 출처 여부 | 이유 |
https://www.example.com:3000/about | O | 프로토콜, 호스트, 포트 번호 동일 |
https://www.example.com:3000/about?username=gyeongeee | O | 프로토콜, 호스트, 포트 번호 동일 |
http://www.example.com:3000 | X | 프로토콜 다름 |
https://www.another.co.kr:3000 | X | 호스트 다름 |
https://www.example.com:8000 | X | 포트 번호 다름 |
https://www.example.com | X | 포트 번호 다름 (:80이 생략) |
📌 CORS 브라우저 동작 방식
- HTTP Header 사용
- 한 Origin에서 실행 중인 API가 다른 Origin의 선택한 자원에 접근할 수 있는 권한을 부여하도록 브라우저에 알려줌
- Request Header의 Orgin 응답값과 Response Header의 Access-Control-Allow-Orgin이 같은 Origin인지 비교하여 CORS에 성립하는지 아닌지를 확인
✅ Request Header
- 어떤 Origin에서 요청이 왔는지를 나타내는 헤더
- 따로 설정하지 않고 브라우저에서 설정됨
✅ Response Header
- Access-Control-Allow-Origin 헤더에 와일드카드(*)로 모두 허용을 해주거나 접근하고자 하는 Origin을 적어 주면됨
- 해당 값에는 한개의 Origin만 입력 가능
1. 클라이언트에서 HTTP 요청의 헤더에 Origin을 담아 전달
- 기본적으로 웹은 HTTP 프로토콜을 이용하여 서버에 요청을 보냄
- 이때, 브라우저는 요청 헤더에 Origin 이라는 필드에 출처를 함께 담아 보냄
2. 서버는 응답헤더에 Access-Control-Allow-Origin이라는 필드를 추가하고 값으로 이 리소스를 접근하는 것이 허용된 출처 URL을 내려보냄
3. 클라이언트에서 Origin과 서버가 보내준 Access-Control-Allow-Origin을 비교
- 이후 응답을 받은 브라우저는 자신이 보냈던 요청의 Origin과 서버가 보내준 응답 Access-Control-Allow-Origin을 비교해본 후 차단할지 말지를 결정
- 만약 유효하지 않으면 그 응답을 사용하지 않고 버린다 ( CORS 에러 발생 )
결국, 서버에서 Access-Control-Allow-Origin 헤더에 허용할 출처를 기재하여 클라이언트에 응답해줘야하는 것 👉 CORS는 백엔드에서 고침
📌 CORS 작동 방식
1. 예비 요청 ( Preflight Request )
- 브라우저는 요청을 보낼 때 한번에 바로 보내지 않고, 먼저 예비 요청을 보내 서버와 잘 통신되는지 확인한 후 본 요청을 보낸다.
- 본 요청을 보내기 전 브라우저 스스로 안전한 요청인지 미리 확인하는 것
- 예비 요청의 HTTP 메소드는 GET이나 POST가 아닌 OPTIONS라는 요청이 사용됨
1-1. 자바스크립트의 fetch() 메소드를 통해 리소스를 받아오려고 한다.
1-2. 브라우저는 바로 HTTP OPTIONS 메소드로 예비 요청(Preflight)을 먼저 보낸다.
- Origin 헤더에 자신의 출처를 넣는다.
- Access-Control-Request-Method 헤더에 실제 요청에 사용할 메소드를 설정한다.
- Access-Control-Request-Headers 헤더에 실제 요청에 사용할 헤더들을 설정한다.
1-3. 서버는 이 예비 요청에 대한 응답으로 어떤 것을 허용하고 어떤것을 금지하고 있는지에 대한
헤더 정보를 담아서 브라우저로 보내준다.
- Access-Control-Allow-Origin 헤더에 허용되는 Origin들의 목록을 설정
- Access-Control-Allow-Methods 헤더에 허용되는 메소드들의 목록을 설정
- Access-Control-Allow-Headers 헤더에 허용되는 헤더들의 목록을 설정
- Access-Control-Max-Age 헤더에 해당 예비 요청이 브라우저에 캐시 될 수 있는 시간을 초 단위로 설정
1-4. 이후 브라우저는 보낸 요청과 서버가 응답해준 정책을 비교하여, 해당 요청이 안전한지 확인하고 본 요청을 보내게 된다.
1-5. 서버가 본 요청에 대한 응답을 하면 최종적으로 이 응답 데이터를 자바스크립트로 넘겨준다.
✅ 예비 요청의 문제점과 캐싱
요청을 보내기 전 OPTIONS 메소드로 예비 요청을 보내 보안을 강화하는 목적은 좋지만,
실제 요청에 걸리는 시간 늘어나고, 서버요청을 배로 보내게 되기 때문에 비용적인 측면에서 폐가 된다.
이 때, 브라우저 캐시(Cache)를 이용하여 Access-Control-Max-Age 헤더에 캐시 될 시간을 명시해주면 이 Preflight 요청을 캐싱 시켜 최적화 시켜 줄 수 있다.
추후 동일한 요청이 오면 캐싱을 확인하여 예비 요청이 아닌 본 요청을 바로 보낸다.
2. 단순 요청 ( Simple Request )
- 예비 요청을 생략하고 바로 서버에 직행으로 본 요청을 보내는 방식
- 다만, 특정 조건을 만족해야 가능
2-1. 요청의 메소드는 GET / HEAD / POST 중 하나 일 경우
2-2. Accept / Accept-Language / Content-Language / Content-Type / DPR / Downlink / Save-Data / Viewport-Width / Width 헤더 일 경우
2-3. Content-Type 헤더가 application/X-www-form-urlencoded, multipart/form-data, text/plain 중 하나일 경우
다소 까다로운 조건들이 많기 때문에 위 조건을 모두 만족되어 단순 요청이 일어나는 상황은 드물다. 대부분의 HTTP API 요청은 text/xml 이나 application/json으로 통신하기 때문에 3번을 위반한다.
따라서 대부분의 API 요청은 그냥 예비 요청으로 이루어진다고 생각하면됨
3. 인증 된 요청( Credentialed Request )
- 클라이언트 👉 서버 : 자격 인증 정보를 실어 요청할 때 사용
- 자격 인증 정보 ( Credential ) 란?
- 세션 ID가 저장되어 있는 쿠키(Cookie)
- Authorization 헤더에 설정하는 토큰(Token)
3-1. 클라이언트에서 인증 정보를 보내도록 설정하기
- 기본적으로 브라우저가 제공하는 요청 API 들은 별도의 옵션 없이는 브라우저에 쿠키와 같은 인증과 관련된 데이터를 함부로 요청 데이터에 담지 않도록 되어있다. 👉 credentials 옵션을 사용하여 별도의 설정을 해주어야함
- 만일 이러한 별도의 설정을 해주지 않으면 쿠키 등의 인증 정보는 자동으로 서버에게 전송되지 않는다.
옵션 값 | 설명 |
same-origin(default) | 같은 출처 간 요청에만 인증 정보를 담을 수 있다. |
include | 모든 요청에 인증 정보를 담을 수 있다. |
omit | 모든 요청에 인증 정보를 담지 않는다. |
- 서버에 인증된 요청을 보내는 방법은 여러가지가 있다. (fetch 메소드, axios, jQuery 라이브러리 등)
// fetch 메서드
fetch("https://example.com:1234/users/login", {
method: "POST",
credentials: "include", // 클라이언트와 서버가 통신할때 쿠키와 같은 인증 정보 값을 공유하겠다는 설정
body: JSON.stringify({
userId: 1,
}),
})
// axios 라이브러리
axios.post('https://example.com:1234/users/login', {
profile: { username: username, password: password }
}, {
withCredentials: true // 클라이언트와 서버가 통신할때 쿠키와 같은 인증 정보 값을 공유하겠다는 설정
})
// jQuery 라이브러리
$.ajax({
url: "https://example.com:1234/users/login",
type: "POST",
contentType: "application/json; charset=utf-8",
dataType: "json",
xhrFields: {
withCredentials: true // 클라이언트와 서버가 통신할때 쿠키와 같은 인증 정보 값을 공유하겠다는 설정
},
success: function (retval, textStatus) {
console.log( JSON.stringify(retval));
}
});
3-2. 서버에서 인증된 요청에 대한 헤더 설정하기
- Access-Control-Allow-Credentials 항목을 true로 설정
- Access-Control-Allow-Origin 의 값에 와일드카드 문자(*)는 사용할 수 없다.
- Access-Control-Allow-Methods 의 값에 와일드카드 문자(*)는 사용할 수 없다.
- Access-Control-Allow-Headers 의 값에 와일드카드 문자(*)는 사용할 수 없다.
응답의 헤더에 와일드카드(*)가 아닌 분명한 출처 / 메소드 / 헤더 값을 넣어주어야하고, 인증 정보 헤더는 true로 설정되어있어야한다. 아니면 CORS 정책에 의해 응답 거부가 된다. (인증 정보는 민감한 정보이기 때문에 출처를 정확하게 설정해주어야한다.)
📚 참고블로그
https://medium.com/wantedjobs/cors-a-to-y-28cc0b16349d