CORS가 도입된 배경
CORS가 도입되기 전 클라이언트들은 서버에서 제공하는 리소스를 가지고만 서버와 통신을 진행했다. 이 경우 서버와 클라이언트의 origin은 동일했기 때문에 굳이 CORS를 도입하지 않아도 웹 서비스를 운영하는데 문제가 없었다.
하지만 SPA가 등장하고 한개의 웹 애플리케이션에서 다른 서버로 요청을 보내는 경우가 많아지면서 클라이언트의 origin과 서버의 origin이 달라지는 문제가 생겼다. 즉, 다른 origin에서 리소스를 받아오는 과정에 대한 정책이 필요하게 되면서 CORS가 도입되게 된다.
CORS란?
CORS(Cross-Origin Resource Sharing)이란 현재 실행 중인 웹 애플리케이션이 다른 origin의 자원에 접근할 수 있도록 권한을 부여하는 체제를 의미한다. 웹 애플리케이션은 요청하고자 하는 리소스가 자신의 출처(도메인, 프로토콜, 포트)와 다른 경우 CORS를 헤더에 태워 요청을 진행해야 한다.
일반적으로 서버는 자신과 다른 origin에서부터의 요청을 거절한다. 클라이언트가 어떤 요청을 보내서 서버의 자원이 망가질 수 있을지 알 수 없기 때문이다. XMLHttpRequest와 Fetch API의 경우 동일 출처 정책을 따르고 있다. 하지만 웹 애플리케이션들이 고도화 되면서 origin이 다르더라도 헤더에 CORS를 태워서 요청을 진행할 경우 서버가 허용하는 범위 내에서 자원 요청이 가능해졌다.
CORS 예시
서버에서 요청을 받은 후 response를 해주게 되는데, 이 때 CORS 정보를 Header에 태워서 반환해주게 된다. 크게 Origin, Methods, Headers, Max-Age로 구분된다. 서버에 Side Effect를 일으킬 수 있는 HTTP 메소드에 대해서는 CORS 명세는 브라우저가 요청을 OPTIONS 메소드를 사용해서 preflight한다. 지원하는 메소드만 메인 요청을 진행하고 서버의 허가를 받고 나서야 실체 요청을 보내도록 하고 있다. 또한 서버는 클라이언트에게 쿠키나 HTTP 인증과 같은 인증정보를 함께 보내야 한다고 알려줄 수 있다.
HTTP OTIONS Method?
목표 리소스와의 통신 옵션을 설명하기 위해 사용된다. 클라이언트 측에서 OPTIONS 메소드의 URL을 특정지을 수 있다. 특정한 URL로 OPTIONS 요청을 보내면 해당 서버에서 지원하는 메소드들의 목록을 받을 수 있다. OPTIONS으로 서버에 요청을 보내기 전 사전 문의를 하는 작업을 preflight라고 한다.
OPTIONS 요청
OPTIONS /index.html HTTP/1.1 OPTIONS * HTTP/1.1
응답HTTP/1.1 200 OK Allow: OPTIONS, GET, HEAD, POST Cache-Control: max-age=604800 Date: Thu, 13 Oct 2016 11:45:00 GMT Expires: Thu, 20 Oct 2016 11:45:00 GMT Server: EOS (lax004/2813) x-ec-custom-error: 1 Content-Length: 0
실제 CORS 명세는 아래와 같다. 서버의 response의 헤더에 추가하게 되면 클라이언트 preflight 요청시 해당 헤더를 태워서 보내주게 된다. 클라이언트는 서버에서 받은 response의 조건을 맞춰 실제 요청을 보내게 된다.
const tempCORS_Headers = {
'Access-Control-Allow-Origin': '*',
'Access-Control-Allow-Methods': 'GET, POST, PUT, DELETE, OPTIONS',
'Access-Control-Allow-Headers': 'Content-Type, Accept',
'Access-Control-Max-Age': 10 // preflight 10초 까지 허용
};
response.writeHead(200, defaultCorsHeader);
- Access-Control-Allow-Origin : 접근 가능한 origin을 특정짓는다.
- Access-Control-Allow-Methods : 허용 가능한 HTTP Methods 들을 명시한다.
- Access-Control-Allow-Headers : Header로는 Conten-Type과 Accept를 허용한다. 클라이언트가 접근할 수 있는 헤더를 서버의 화이트 리스트에 추가한다.
- Access-Control-Max-Age : preflight 의 시간을 명시한다. 다른 preflight request를 보내지 않고, preflight request에 대한 응답을 캐시 할 수 있는 시간을 의미한다. Access-Control-Max-Age가 클 수록 우선순위가 높아진다.
- Acess-Control-Allow-Credentials : 서로 다른 도메인 간 쿠키 공유를 허락하는 옵션. CORS 이슈가 있는 SPA의 경우 cors options에 credentials를 true로 변경해줘야 로그인이 가능하다.(세션 쿠키를 사용해서 로그인을 하는 경우). 프론트 서버(React의 경우 axios.defaults.withCredentials = true 설정을 해줘야 됨)
credentials : true 설정시 주의사항
credentials 옵션은 서로 다른 도메인 간 쿠키를 공유하겠다는 옵션이다. SPA에서 OAuth 2.0의 경우 refresh token을 쿠키에 태워 관리하므로 이 옵션을 true로 지정해줘야 한다. 문제는 origin이 '*'(와일드카드)로 지정될 경우 모든 origin에 대해 cors 옵션을 허용한다는 설정이지만, 이 경우 credentials ture로 지정할 경우 아래 에러가 발생한다. 즉, 서로 다른 도메인간 쿠키를 공유하는 경우 origin을 와일드카드가 아닌 특정 도메인으로 지정해줘야 한다.
Error :
Access to XMLHttpRequest at 'http://lahuman.github.io' from origin 'http://localhost:8080' has been blocked by CORS policy: The value of the 'Access-Control-Allow-Origin' header in the response must not be the wildcard '*' when the request's credentials mode is 'include'. The credentials mode of requests initiated by the XMLHttpRequest is controlled by the withCredentials attribute.
preflight 요청
preflight request는 먼저 OPTIONS 메소드를 통해 실제 요청을 보내기 전에 다른 도메인의 리소스로 HTTP 요청을 보낸다. 실제 요청을 보내기에 안전한지 사전 확인하는 작업이다. CORS의 경우 서버에 Side Effect를 줄 수 있기 때문에 preflight를 사용해서 Request를 던지게 된다.
CORS 사용시 요청/응답 과정
정리
CORS가 도입되게 된 배경은 SPA이 등장하고, 웹 애플리케이션들이 고도화 되기 시작하면서 origin이 다른 서버에 리소스를 요청하는 경우 빈번해졌기 때문이다.
origin이 다른 요청은 서버에 Side Effect를 발생시키기 때문에 서버는 CORS 명세를 통해 허용 가능한 조건을 명시할 수 있다.
클라이언트는 OPTIONS 메소드를 통해 preflight를 요청하여 요청하고자 하는 서버의 조건을 미리 확인할 수 있고 서버가 제시한 조건을 헤더에 태워서 실제 요청을 서버에 보낼 수 있게 된다. CORS 명세를 통해 origin이 다름에도 불구하고 다른 origin 서버에서 리소스를 받아올 수 있다.
preflight를 보내면 Chrome 개발도구 -> Network 탭에서 확인할 수 있다. 먼저 preflight를 보내고 난 후 서버로 부터 응답을 받고 난 뒤 실제 요청을 보내게 된다.
preflight의 header를 보면 OPTIONI 메소드가 사용되었고, 200 status를 반환했다.
preflight 요청 후 서버로 부터 받은 response header에는 CORS 에 관한 사항을 header에 셋팅한 내역이 그대로 출력된다.
preflight request 이 후 서버에 POST 메소드를 사용해서 origin이 다른 서버와 통신이 가능하다.
Reference
'Programming' 카테고리의 다른 글
[Web Server] Express Routing query VS params (0) | 2022.05.26 |
---|---|
[Programming] 웹서버(Web)와 웹 어플리케이션 서버(WAS) 차이점 The difference between Web Server and Web Application Server (0) | 2022.05.25 |
[React] Effect HOOK API useEffect 사용하는 방법 (0) | 2022.05.25 |
[Javascript] setTimeout vs setInterval 차이 (debounce, throttle ?) (0) | 2022.05.22 |
[Javascript] apply() vs call() vs bind() 차이 (0) | 2022.05.21 |
댓글