이 포스팅은 개인 공부 목적으로 작성된 포스팅입니다. 왜곡된 내용이 포함되어 있을 수 있습니다.
개발을 하던중 cors에 관해서 이해가지 않는 부분이 생겨 기록한다.
CORS
https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS
cors에 대한 내용은 일부 생략하도록 하고, 글쓴이 이해한 cors 정책은 다음과 같다.
API 요청은 일반적으로 Same-Origin 정책을 가져간다
이때 Same-Origin Policy가 SOP으로 동일한 출처에 대해서만 리소스가 접근 가능하다. 여기서 동일한 출처란, 프로토콜, 포트, 호스트를 말한다.(웹 서버의 경우만 생각해보면 서버 사이드 렌더링만 가능한 것이다.)
그런데 실제로 모든 요청이 Same-Origin을 따른다면 문제가 생긴다.(외부의 어떠한 리소스에 접근 할 수 없기 때문에)
Cross-Origin Resource Sharing 정책에 이에 대한 해결책으로 허용되는 범위 내에서 동일하지 않은 출처도 리소스 접근이 가능하도록 하는 것이다.
이렇게 cors 정책을 이해하면 의문점이 몇개 있었는데 하나씩 살펴보자
정책을 누가 판단하나요?
정책에 따라 SOP이면 동일 출처 CORS이면 혀용된 출처 범위 내의 통신이 허용된다는 것이 이해된다. 그러면 정책을 누가 판단할까?
브라우저가 판단한다.
코드로 실행했었을때 잘되던 API가 브라우저 상에서 또는 클라이언트에서 갑자기 안된다는 이야기가 나오는 이유가 코드로 실행하는 경우 브라우저와 실행환경이 다르기 때문이다.(fetch와 같이 SOP가 적용되는 경우도 있는데, 이건 request 모듈에 따라 다르다)
그러면 다음 의문점에 대해 살펴보자
브라우저가 정책을 판단하는게 맞을까?
브라우저가 정책을 판단한다는 것은 클라이언트에서 서버에게 요청을 보내고 서버에게 요청을 받고 response header의 Access-Control-Allow-Origin 값을 request header의 Origin과 비교하게 된다. 이렇게되면 정책은 오직 클라이언트에게 API 결과를 보여주는 것만 결정하게 되고, 서버에서 해당 로직이 정상적으로 실행되었는지는 고려하지 않게 된다.
예를 들어 POST으로 데이터 생성 로직에 대해서 SOP 정책이 적용되는 경우 서버에서는 실제로 데이터가 생성되었지만 클라이언트는 알 수 없기 때문이다.(생성되지 않았다고 생각할 가능성이 높다)
글쓴이의 지식으로 이해해보면 SOP, CORS가 HTTP 메소드의 멱등성을 고려해야하는 이유중 하나인 것 같고, 이러한 정책은 멱등성 보다 클라이언트에서 발생하는 보안 취약점을 예방하기 위해 등장한 것으로 생각한다(XSS)
Options
앞서 말했듯이 요청과 응답을 모두 받고, 클라이언트에서 정책에 따라 결정하는 것은 위험한 방법이다. 먼저 해당 요청이 응답 받을 수 있는 상태인지 확인할 수 있다면 앞서 이야기한 문제점을 예방할 수 있는데 http OPTIONS이 이에 해당한다.
https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods/OPTIONS
http OPTIONS메소드는 서버에게 permitted communication에게 허락받는 요청을 보낸다(허락 받는다)
OPTIONS 요청과 설정한 HTTP METHOD request를 두번하게 되는데, 이를 Preflighted requests라고 한다.(기존 방식은 Simple)
Preflighted request인지는 어떻게 알까?
공식문서에 제시하고 있는 format를 제외하는경우 Preflighted request로 보내지는 것 같다.
아래 코드는 Preflighted request의 예이다.
const fetchPromise = fetch("https://bar.other/doc", {
method: "POST",
mode: "cors",
headers: {
"Content-Type": "text/xml",
"X-PINGOTHER": "pingpong",
},
body: "<person><name>Arun</name></person>",
});
fetchPromise.then((response) => {
console.log(response.status);
});
이 경우 Content-Type이 test/xml으로 Simple에서 호환되지 않기 때문에 Preflighted request가 적용된다.
NGINX으로 CORS 설정하기
cors 설정하는 방법으로 사용중인 spring boot에서 설정하는 방법이 있고, nginx에서 설정하는 방법이 있는데 글쓴이는 NGINX에 설정하는 방법을 사용하였다.
#중략
location / {
if ($request_method = 'OPTIONS') {
return 204;
}
add_header 'Access-Control-Allow-Origin' '*';
add_header 'Access-Control-Allow-Methods' 'GET, POST, DELETE, PATCH, OPTIONS';
add_header 'Access-Control-Allow-Headers' 'Content-Type, Authorization';
add_header 'Access-Control-Allow-Credentials' 'true';
proxy_pass http://localhost:8080; #<=====포워딩될 주소
}
#중략
Access-Control-Allow-Origin 으로 모든 url으로 부터의 요청을 허락하고, Access-Control-Allow-Methods으로 모든 http method에 대해 허락한다.
Access-Control-Allow-Headers는 클라이언트가 보낼 수 있는 header를 명세한다.
Access-Control-Allow-Credentials는 토큰,쿠키를 포함하기 위한 설정이다.
마지막으로 OPTIONS일때 204으로 무조건 성공을 반환하여 브라우저에서 확인할 필요없이 요청을 보내도록 설정하였다,
(처음에서 OPTIONS일떄만 header를 추가했더니 Simple request의 경우 cors가 적용되지 않았다)
Swagger block[Mixed Content]
cors 설정만 하면 문제가 해결될 줄 알았는데 문제가 한가지 더 있었다.
지금 서버는 80와 443이 아닌 별도의 포트에서 실행중이고, nginx에 설정한 리버스 프록시로 80와 443 포트에 요청을 보내면 스프링 부트 포트로 가게 설정했다.
요청을 보내고 받는 것은 앞서 설정한 cors에 의해 통과되어야하는데, swagger에서는 swagger가 요청을 보낼 때, swagger는 https에 있고, 요청을 보내는 서버는 http에 있어 block[Mixed Content]가 발생하였다.
cors와 같은 것이라고 생각했었는데, 문서에서는 https에서 제시하는 클라와 서버간의 암호화를 깨는 접근 방법으로 cors보다는 https 보안에 해당하는거 같다.
https://developer.mozilla.org/en-US/docs/Web/Security/Mixed_content
다행히 mixed content는 문제는 swagger에서 해결방법을 제시하고 있었는데 Config에 server url을 명세하는 경우 mixed content를 무시할 수 있었다.
@Configuration
public class SwaggerConfig {
@Value("${SPRING_SWAGGER_SERVER_URL:http://localhost:8080}")
private String serverUrl;
@Bean
public OpenAPI customOpenAPI() {
return new OpenAPI()
.servers(Collections.singletonList(new Server().url(serverUrl).description("API Server")))
.info(new Info().title("ourmenu API 명세서").description("ourmenu 사용되는 API 명세서").version("v1"));
}
}
test api가 정상적으로 작동 하였다.