Spring 과 Vue를 사용하면서 서로 다른 서버에서 개발하다 보니 처음 겪는 에러가 발생했다. 에러는 개발하면서 언제든 발생할 수 있었지만, 나에게는 CORS 에러는 정말이지 처음 보는 에러였다...😂
그렇기에 에러를 해결하면서 프로젝트를 하던 도중 계속해서 CORS 에러를 해결했지만, 지속해서 특정 개발단계에서 CORS 에러를 직면했다. 에러를 해결 도중에는 control + v 를 해결해 나갔지만, 이 부분은 내가 아직 개념이 덜 잡힌 거 같아서 글로 남기려고 한다.
cors는 Cross-Origin Resource Sharing 의 약자이며, 말 그대로의 의미는 교차 출처 리소스 공유를 의미한다.
브라우저가 리소스 로드를 허용해야 하는 자체가 아닌 다른 출처를 서버가 나타낼 수 있도록 하는 브라우저와 서버 간에 웹에서 전송하면 읽을 수 있도록 하는 기본 네트워크 프로토콜이다.
위와 같으며, Protocol , host, port, 까지 합친 것을 의미한다. 이 출처를 허용해야지 같은 출처라고 인정하고 서버 간에 원활한 통신이 가능하게 된다.
CORS는 기본적으로 사전에 요청을 보낸다. 이를 preflight라고 한다. 이는 서버가 실제 요청을 허용했는지 확인하기 위해 브라우저는 한 번에 예비 요청과 본 요청으로 나누어서 서버로 전송한다.이때 예비 요청을 preflight라고 한다.
❓그렇다면 해당 preflight는 무엇을 보낼까?
실제 요청에 사용되는 HTTP 메서드와 헤더를 보냅니다. 이때 요청을 보낼때 HTTP 메소드 중 options를 보냅니다. 해당 메서드는 예비 요청을 보내기 전에 스스로 이 요청을 보내는 것이 안전한지 확인을 위하기 입니다.
우리는 자바스크립트의 fetch API를 사용하여 브라우저에게 리소스를 받아오라는 명령을 내리면 브라우저는 서버에게 예비 요청을 먼저 보내고, 해당 요청이 API 동일 출처 정책을 따른다. 이때 사용되는 것이 Same-origin policy 동일 출처 정책을 검사한다. 해당 검사를 통해 프로토콜, 포트 및 호스트가 동일한지 확인한 뒤 서버는 해당 예비 요청의 응답으로 해당 서버가 어떤 것을 허용하고 금하는지 알려주고 헤더에 담아서 다시 브라우저에게 보낸다.
이 부분이 가장 오류를 해결하기에 적합한 항목이라 생각이 든다. 서버가 허용한 출처를 확인하고 출처를 허용해 줬다면 200이 잘 뜰 것이다. 하지만 아니라면 cors에러를 내뱉을 것이다.
만약 오류를 해결을 했다면 출처를 명확하게 확인을 해야할 것이다.
요청을 보고 명확하게 이해를 하자
const headers = new Headers({
'Content-Type': 'application/xml',
});
fetch('https://test.com/rss', { headers });
HTTP/1.1 200 OK
Date: Mon, 01 Dec 2008 00:23:53 GMT
Server: Apache/2
Access-Control-Allow-Origin: *
Keep-Alive: timeout=2, max=100
Connection: Keep-Alive
Transfer-Encoding: chunked
Content-Type: application/xml
[…XML Data…]
https://test.com/rss 해당 서버가 있다고 가정하자. 해당 서버로 요청을 보낸다면. 예비 요청으로 위와 같은 응답을 보낼 것이다. 위 응답으로 해당 서버가 어떤 cors정책을 허용했는지 확인이 가능하다.
Access-Control-Allow-Origin: *
Origin 및 헤더의 이 패턴은 Access-Control-Allow-Origin 액세스 제어 프로토콜의 가장 간단한 사용입니다. 만약 프런트 서버에서 https://bar.other 이와 같은 요청만을 허용해줄 때는
Access-Control-Allow-Origin: "https://bar.other"
위와 같은 행동을 하면 된다. 하지만 현실적으로 위와 같이 설정해줬다고 해서 안 되는 경우는 정말 다반사일 것이다.
이럴 때는 frontend측 서버의 headers 속성 및 backend측 서버의 headers를 잘 허용 했는지를 확인해야 한다.
✔ Access to fetch at ‘url’ from origin ‘url2’ has been blocked by CORS policy: Response to preflight request doesn’t pass access control check: The ‘Access-Control-Allow-Origin’ header has a value ‘url’ that is not equal to the supplied origin. Have the server send the header with a valid value, or, if an opaque response serves your needs, set the request’s mode to ‘no-cors’ to fetch the resource with CORS disabled.
만약 cors 에러를 내뱉게 된다면 위와 같은 것이다. 해당 내용을 보게 된다면 예비 요청에서는 정상적으로 200을 내뱉었지만 빨간 오류를 볼 수 있다. 많이들 헷갈리는데, cors 정책 위반으로 인한 에러는 예비 요청의 성공 여부와 별 상관이 없다....❓❓
❓이게 무슨 말인가 상관이 없다니... 브라우저가 CORS 정책 위반 여부를 판단하는 시점은 예비 요청에 대한 응답을 받은 이후이기 때문이다.
물론 예비 요청 자체가 실패해도 똑같이 CORS 정책 위반으로 처리될 수도 있지만, 중요한 것은 예비 요청의 성공/실패 여부가 아니라 헤더에 유용한 Access-Control-Allow-Origin 이 있냐는 것이다. 예비 요청이 실패서 200이 아닌 상태 코드가 내려오더라도 헤더에 저 값이 제대로 들어가 있다면 CORS 정책 취반이 아니라는 의미이다.
The request was redirected to 'url', which is disallowed for cross-origin requests that require preflight. Request requires preflight, which is disallowed to follow cross-origin redirects.
cors 프로토콜은 원래 그 동작이 필요했지만, 이후에 더 이상 필요하지 않도록 변경된다. 하지만
브라우저가 변경 사항을 구현한 것은 아니므로 여전히 원래 필요한 동작을 요구하기에 위와 같은 오류를 내뱉는다.
그렇다면 위 오류가 나는 원인은 무엇일까?
보면 의아할 것이다. 위와 같은 조건들은 평상시에 사용하는 원인일 것이다. 하지만 이러한 내용들을 모두 만족시키는 것은 몹시 어렵다고 알려진다.
Credentialed Request는 주로 인증된 사용자를 헤더에 담을 때 사용되는 내용이다. 일반적으로 특정 프로토콜 api를 통해 별도의 옵션 없이 보낼 때 쿠키, 인증과 관련된 헤더를 함부로 요청에 담지 않는다. 이때 요청에 인증과 같은 정보를 담게 도와주는 것이 credentials 옵션이다.
만약 이러한 옵션을 사용하게 된다면 좀 더 빡빡한 검사 조건을 가지고 있는 에러를 만나는 게 다반사일 것이다.
해당 오류 조건들은 구체적으로 무엇일까?
fetch('url', {
credentials: 'include', // Credentials 옵션 변경!
});
해당 include는 모든 인증 요청을 허용한다는 의미가 된다. 하지만 이렇게 된다면
🚨 Access to fetch at ’url’ from origin ’http://localhost:8000’ 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’.
위와 같은 오류를 직면하게 될 수도 있다. include를 사용할 시
Access-Control-Allow-Origin: *
를 사용하지 말라는 뜻이다. 이 또한 명확한 해결책이 있다.
사실 지금까지 설명한 부분을 몇가지 설정을 통해서 해결할 수는 있다. 하지만 그렇게 하기에는 cor는 우리에게 많은 고난과 시련을 알려줄 수 있다.
CORS 정책 위반으로 인한 문제를 해결하는 가장 대표적인 방법은, 그냥 정석대로 서버에서 Access-Control-Allow-Origin 헤더에 알맞은 값을 세팅해주는 것이다.
나는 처음에 해당 방법이 원활할지는 상상도 못 했다. 그저 내가 맡은 부분이 spring으로 cors를 막아주는 내용을 맡았기 spring에서 해결을 해줬다. 물론 front에서 설정해줄 수 있다는 것은 알고 있었다. 하지만 webpack-dev-server를 사용한다면 해당 라이브러리가 제공하는 프록시 기능을 사용한다면 편하게 cors 정책을 우회 하게 만든다.
module.exports = {
devServer: {
proxy: {
'/api': {
target: 'https://api.evan.com',
changeOrigin: true,
pathRewrite: { '^/api': '' },
},
}
}
}
먼저 무작정 cors를 직면하고, 그저 에러를 해결한 점이 너무나도 후회스럽다. 이 포스트를 작성하면서 정말 많은 것을 느끼게 돼서 너무 좋기도 하고 알았다면 좀 더 프로젝트 진행에 원활한 게 진행이 됐을 것이다. cors를 직면하면서 크게 3가지 종류로 나뉠 수 있다.
토이 형식으로 프로젝트를 쪼개서 진행하여 cors를 해결했지만, 프로젝트를 합치는 과정에서 위와 같은 오류를 너무나도 많이 직면했다. 하지만 이글을 써내려 가면서 cors에 대해 알아가서 아 이 때 오류가 여기서 나는 거였구나 라는 생각이 많이 났다. 좀더 원리를 알고 있었다면 원활했을 텐데.. 조금 아쉬우면서도 제대로 짚고 넘어간 거 같아 기분이 좋다. 이제는 조금 cors 문제를 한시름 놓아도 될 듯싶다..ㅎ