교차 출처 리소스 공유(Cross-Origin Resource Sharing, CORS)
HTTP의 헤더를 추가해줌으로써 서로 다른 출처의 자원에 접근할 수 있는 권한을 부여하도록 브라우저에게 알려주는 메커니즘(체계)입니다.
웹 브라우저는 기본적으로 출처가 같은 상태인 Same Origin 일 경우만 자원의 접근을 허용합니다.
자원의 출처가 다른 상태인 Cross Origin은 기본적으로 서로 자원을 공유할 수 없게 브라우저에서 막고 있는 것입니다.
그래서 우리가 마주하는 CORS Error는 브라우저에서 자연스럽게 자기 할 일을 하고 있는 것이라고 할 수 있습니다.
CORS Error를 발생시키는것(자원의 공유를 막는 것)은 브라우저가 수행합니다.
따라서, Postman과 같이 브러우저가 아닌 방법으로 통신하면 교차 출처 상태에서도 자원의 공유가 가능합니다.
(CORS는 브라우저를 사용할때만 나타나는 오류!)
출처가 다르면 자원의 공유가 안된다고 했는데, 그렇다면 출처는 무엇일까요
출처는 Protocol, Host, Port를 합친 주소를 말합니다.
Protocol: 통신 규칙
Host: 도메인
Port: 포트 번호
위의 예시에서 출처는 http://localhost:8080
이 될 것입니다.
Protocol, Host, Port가 같은 경우 동일 출처(Origin)
다른경우 교차 출처(Cross Origin)
가 됩니다.
브라우저는 원칙적으로
동일 출처
에 대한 자원의 공유만 허용합니다.
하지만 Client Application과 Server Application이 나누어져 있는 경우 서로 출처가 다른교차 출처
상태가 될 것입니다.
이와 같은 교차출처(Cross Origin)인 상황에도 자원의 공유를 허용해 주기 위한 정책이
CORS(Cross-Origin Resource Sharing) 정책입니다.
CORS 명세에 따라 서버에서 적절한 Header를 추가해주는 방법으로 CORS Error를 해결할 수 있습니다.
즉, 응답을 주는 서버에서 적절한 Header를 추가해주면, 브라우저
에서 해당 자원을 받을 수 있도록 허락해줍니다.
그러면 도대체 어떤 Header를 추가해줘야 하는건데? 라는 생각이 들것 같습니다.
요청 방식에 따라 3가지 조건이 있습니다.
어떤 형태로 요청이 이루어지는지에 따라 해당되는 요청 방식이 달라지고,
요청 방식에 따라 추가해줘야 하는 Header가 달라집니다.
따라서 지금부터 소개하는 3가지 요청 방식에 대해서 이해를 할 필요가 있겠습니다.
3가지 요청에 대해서 알아보기 전에 본 요청과 예비요청에 대해서 한번만 정리하고 넘어가겠습니다.
우리가 Server에 요청(Request)을 보낼 경우 지정한 Servlet의 Method에 요청이 도달합니다.
예비 요청의 경우에는 doOption이라는 Method가 받아서 처리합니다.
CORS를 해결하기 위해서는 서버가 응답으로 적젏란 Header를 추가해줘야 한다고 설명하였습니다.
본요청일 경우에는 Servlet의 Method 내부에서 Header를 추가해줘야 할것이고,
예비 요청일 경우에는 doOption Method 내부에서 Header를 추가해줘야 해결할 수 있게 됩니다.
가장 간단한 형태의 요청입니다.
예비요청없이 바로 본 요청이 진행되기 때문에 Servlet Method에 바로 요청이 도달합니다.
다음의 조건을 모두 충족하는 경우 Simple Request에 해당됩니다.
Simple Request가 되기 위한 조건(3가지 조건을 모두 충족해야 합니다)
1. Method가 다음에 해당할것
- GET
- HEAD
- POST
2. 자동으로 설정된 헤더 외에 유저가 설정한 헤더가 다음에 해당 할 것
- Accept
- Accept-Language
- Content-Language
- Content-Type
3. Content Type은 다음의 값들중 하나일 것
- application/x-www-form-urlencoded
- multipart/form-data
- text/plain
// Simple request 조건에 부합합니다.
// 1. POST요청에 해당합니다.
// 2. 수동으로 설정한 헤더가 Context-Type뿐입니다.
// 3. Context Type이 application/x-www-form-urlencoded에 해당합니다.
this.$axios({
url: "http://localhost:8080/board/login",
method: "POST",
headers: { "Content-Type": "application/x-www-form-urlencoded" },
data: { memberId: "id", memberPw: "pw" }
}).then(data => { console.log(data) })
.catch(error => { console.log(error)});
예시와 같은 요청을 보낼때, Request방식은 (Simple Request)가 될것이고,
Servlet의 doPost로 요청이 전달됩니다.
이때, 헤더에 허용하는 origin의 출처를 적어주면 CORS 오류를 해결할 수 있습니다.
// 예시
response.setHeader("Access-Control-Allow-Origin", "http://localhost:8081");
대부분의 HTTP API 요청은 text/xml 혹은 application/json이 사용되기 때문에 Simple Request방식이 사용될 경우는 적습니다.
preflight라는 사전 요청을 통해 안전한 요청인지 확인하고, 본 요청을 보내는 방식입니다.
Simple request는 제한 조건이 많은 요청이기 때문에
Preflight request가 우리가 주로 사용하게 될 요청 방식일 것입니다.
preflight는 Option method에 도달하여 response에 Header를 담아옵니다.
이후 Header 응답이 잘 도착하면, 본 요청을 전송합니다.
개발자 도구 -> 네트워크를 보면 요청을 2번 보내는 것을 확인할 수 있습니다.
// "Content-Type": "Content-Type": "application/json"의 조건이
// Preflight request를 하도록 만듭니다.
this.$axios({
url: "http://localhost:8080/board/login",
method: "POST",
headers: { "Content-Type": "application/json" },
data: { memberId: "id", memberPw: "pw" }
}).then(data => { console.log(data) })
.catch(error => {console.log(error) });
preflight request(프리플라이트 요청)의 해결 방법으로는
사용자가 어떤 방식으로 요청을 했는지를 확인하고
이에 대응하는 Access-Controll-Allow 헤더를 response해주면 됩니다.
위의 Axios예시의 경우
doOption코드(preflight 요청을 받습니다)
response.setHeader("Access-Control-Allow-Origin", "http://localhost:8081");
response.setHeader("Access-Control-Allow-Methods", "POST");
response.setHeader("Access-Control-Allow-Headers", "Content-Type");
doPost(본 요청을 받습니다.)
response.setHeader("Access-Control-Allow-Origin", "http://localhost:8081");
다음과 같은 헤더 세팅을 해줌으로써 해결할 수 있습니다.
서버에게 자격 인증 정보(Credential)를 실어 요청할때 사용되는 요청입니다.
여기서 말하는 자격 인증 정보란 세션 ID가 저장되어있는 쿠키(Cookie) 혹은 Authorization 헤더에 설정하는 토큰 값 등을 말합니다.
다음과 같은 헤더를 명시하여 주지 않으면 서버가 응답하더라도 응답이 브라우저에서 폐기됩니다.
Access-Control-Allow-Credentials: true
또한, 인증 정보를 포함한 요청인 경우에는 Access-Control-Allow-Origin에 *와 같은 와일드 카드 문자를 사용할 수 없습니다.
명확한 주소를 명시하여야 합니다.
// axios 라이브러리
this.$axios({
url: "http://localhost:8080/board/login",
method: "POST",
headers: { "withCredentials": true },
data: { memberId: "id", memberPw: "pw" }
}).then(data => { console.log(data) })
.catch(error => {console.log(error) });
// 정확한 Origin을 명시하였습니다.
response.setHeader("Access-Control-Allow-Origin", "http://localhost:8081");
// Crudentials를 허용해 주었습니다.
response.setHeader("Access-Control-Allow-Credentials", "true");
// 정확한 Method를 명시해 주었습니다.
response.setHeader("Access-Control-Allow-Methods", "POST");
개발자의 입장에서 CORS에러를 해결하는 방법은 웹 어플리케이션에서 보내는 요청이
3가지중 어떤 요청에 해당되는지를 파악하고,
Request 종류에 따른 적절한 Header를 달아서 응답해줌으로써 해결해줄 수 있습니다.
CORS에 대한 내용을 조사하던중 글을 쓰는데 참고하며, 도움이 많이 된 3개의 사이트가 있습니다.
참고자료에 명시된 3개의 글을 보신다면 CORS에 대해 더 잘 이해하실 수 있으실 것이라고 생각합니다.