민팽로그

[web] SOP와 CORS 본문

📚소소한 스터디

[web] SOP와 CORS

민팽 2021. 12. 20. 02:35

동아리 지원서를 작성하다가 CORS라는 개념을 처음 접했다. 개발을 할 때 다른 출처의 리소스를 가져올 때 CORS로 인한 문제가 발생하는 일이 흔하다고 한다. 나중에 이 문제를 겪기 전에 미리 무엇인지 알아두면 좋을 것 같아 정리하기로 했다.

 

SOP(Same Origin Policy)

CORS를 언급하기 전에 SOP라는 개념을 먼저 알아보자.

SOP는 다른 출처(Origin)의 리소스를 사용하는 것을 제한하는 보안 정책이다. 대부분의 웹 브라우저들은 SOP 보안 정책을 준수한다.

여기서 출처는 URL의 Protocol, Host, Port 를 통해 확인할 수 있다. Protocol, Host, Port 세 가지가 모두 같으면 같은 출처이고, 하나라도 다르면 다른 출처인 것이다. 

SOP가 없다면 웹 보안이 심하게 취약해질 수 있다. 어떤 웹 사이트에 대한 사용자의 로그인 쿠키 정보가 웹 브라우저에 저장이 되어 있을 때, 해커가 쿠키 정보를 해커의 웹 사이트로 보내는 악성 스크립트를 작성하면 이를 실행한 사용자의 쿠키 정보가 그대로 유출된다. SOP는 서로 다른 출처들 간에 리소스 요청을 제한하여 이런 보안상의 문제를 막아주는 것이다.

 

하지만 개발을 할 때, 다른 출처의 리소스가 필요한 상황은 흔하게 나타난다. 이럴 때마다 SOP에 의해 제한이 된다면 항상 같은 출처의 리소스만을 사용해야 한다. 

 

따라서, 필요할 때 서로 다른 출처 간의 리소스를 공유할 수 있도록 하기 위한 것이 바로 CORS이다. CORS 표준을 지키지 않고 다른 출처의 자원에 접근하려 하면 CORS와 관련된 오류가 발생하게 된다.

 

 

CORS(Cross-Origin Resource Sharing)

CORS는 추가 HTTP 헤더를 사용하여, 한 출처에서 다른 출처의 자원에 접근할 수 있는 권한을 부여하도록 브라우저에 알려주는 체제이다.  

 

CORS 접근제어 시나리오는 다음과 같이 3가지가 있다.

  1. 프리플라이트 요청(Preflight Request)
  2. 단순 요청(Simple Request)
  3. 인증정보 포함 요청(Credentialed Request)

 

 

1. Preflight Request

Preflight Request는 실제 요청을 보내기 전에 OPTIONS 메소드를 통해 서버에 리소스 요청이 가능한지 먼저 확인한다. 요청이 가능하다는 응답을 받으면 실제 요청(Actual Request)을 보낸다. Preflight Request가 거부됐다면, Actual Request는 보내지 않는다.

Preflight Request 진행 과정

 

Preflight Request은 Origin(출처), Access-Control-Request-Method(실제 요청의 메소드), Access-Control-Request-Header(실제 요청의 추가 헤더)를 포함하여 요청을 보낸다.

Preflight Response는 Access-Control-Allow-Origin(서버 측 허가 출처), Access-Control-Allow-Methods(서버 측 허가 메소드), Access-Control-Allow-Header(서버 측 허가 헤더), Access-Control-Max-Age(응답 캐시 기간: 프리플라이트 응답을 받으면 브라우저가 캐싱을 해두고 응답 캐시 기간동안 사전 요청 없이 실제 요청을 보낼 수 있음)을 포함하여 응답할 수 있다. 응답 코드가 200대여야 하며 응답 바디는 비어있는 것이 좋다.

 

 

2. Simple Request

Preflight Request를 보내지 않고 바로 실제 요청을 보낸다. Simple Request는 다음과 같은 조건을 충족해야 한다.

 

Method

- GET

- POST

- HEAD

 

Header

- Accept

- Accept-Language

- Content-Language

- Content-Type

 

Content-Type

- application/x-www-form-urlencoded

- multipart/form-data

- text/plain

 

위 조건을 모두 만족하면 Simple Request로 요청을 할 수 있다.

Simple Request 진행 과정

 

 

그렇다면, Simple Request를 사용하면 요청 한번으로 끝나는데 왜 Preflight Request가 필요할까?

그 이유는 CORS를 모르는 서버로 인해 발생할 수 있는 오류를 막기 위해서이다. 클라이언트가 브라우저에 출처를 보내면 브라우저가 그대로 출처를 서버에 보낸다. CORS를 모르는 서버는 Access-Control-Allow-Origin 설정이 없이 응답하게 되고, 이 응답으로 인해 브라우저는 클라이언트에게 CORS 관련 에러를 보여준다. 만약 클라이언트가 DELETE 요청을 했다면, 서버는 DB의 내용을 지웠음에도 불구하고 클라이언트는 CORS 관련 오류를 마주하게 되는 것이다. 

Preflight Request를 사용하면 서버에서 Access-Control-Allow-Origin를 보내지 않았을 때 클라이언트는 CORS 에러를 받고 Actual Request를 보내지 않게 되어 실제 요청으로 인한 서버 측 문제를 방지할 수 있다.

 

 

3. Credentialed Request

인증 관련 헤더를 포함할 때 사용하는 요청이다. 클라이언트측은 credentials:include, 서버측은 Access-Control-Allow-Credentials:true로 설정해야 한다. 이 때 서버는 Access-Control-Allow-Origin을 *로 두어서는 안된다.

 

 

CORS 해결 방법
  1. 프론트 프록시 서버 설정(개발 환경)
  2. 직접 헤더에 설정해주기
  3. 스프링 부트 이용하기

CORS 관련 오류를 해결하기 위해 프록시 서버를 이용할 수 있다. 프록시 서버를 이용하면, 클라이언트에서 서버로 직접 요청을 보내지 않고 중간에 프록시 서버가 대신 요청을 받아 서버에 보낸다. 서버로부터 받은 응답을 프록시 서버가 클라이언트에 보내줄 땐 Access-Control-Allow-Origin을 설정하여 응답하면 문제를 해결할 수 있다. 만약 스프링 부트를 사용한다면 @CrossOrigin 어노테이션을 사용하여 CORS 오류를 해결할 수도 있다. 이 어노테이션을 사용하여 origins, methods, maxAge, allowedHeaders를 필요에 따라 설정할 수 있다. origins의 값을 따로 지정하지 않으면 기본값으로 *를 갖기 때문에, 보안성을 좀 더 강화하고 싶다면 origins에 값을 직접 지정해주는 것이 좋다.

 

참고

https://velog.io/@jesop/SOP%EC%99%80-CORS

https://coding-groot.tistory.com/91

https://velog.io/@sj950902/CORS%EC%99%80-SOP%EC%97%90-%EB%8C%80%ED%95%B4-%EC%95%8C%EC%95%84%EB%B3%B4%EC%9E%90-1%ED%83%84

https://www.youtube.com/watch?v=-2TgkKYmJt4 

https://blog.naver.com/PostView.naver?blogId=chldntjr8036&logNo=221727138693&redirect=Dlog&widgetTypeCall=true&directAccess=false 

https://xiubindev.tistory.com/115

https://dev-pengun.tistory.com/entry/Spring-Boot-CORS-%EC%84%A4%EC%A0%95%ED%95%98%EA%B8%B0

 

 

Comments