Same-Origin PolicySOP가 없다면?CORSCORS 흐름1. 예비 요청(pre flight request)과 본 요청2. 단순 요청3. Credentialed 요청CORS 필요성CORS 우회하기1. 프론트엔드에서 Proxy Server(서버의 출처로)를 활용webpack-dev-serverhttp-proxy-middleware (node server 사용시)리액트 CRASpring에서 CORS 허용가능하도록 설정하는법WebMvcConfigurer이용WebSecurityConfigCorsFilter@CrossOrigin 어노테이션 이용테스트 방법Chrome 에서 CORS Disable/Enable
Same-Origin Policy
어떤 출처에서 불러온 리소스(eg.클라이언트 페이지)가 다른 출처(eg. Server)에서 가져온 리소스와 상호작용하는 것을 제한하는 보안 정책. 스크립트에만 적용됨. 웹 브라우저에서 실행되는 자바스크립트 클라이언트에서 원래 코드가 제공된 사이트 외에 다른 사이트에는 접근을 제외하는 규칙임
- 호스트, 프로토콜, 포트가 같으면 동일한 출처임
- 어떤 페이지의 악의적인 스크립트가 다른 페이지의 DOM을 통해 데이터에 접근하는 것을 방지함
- 이미지, CSS같은 리소스는 HTML 태그를 통해 다른 출처여도 접근할 수 있음
- 사용자를 보호하기 위한 클라이언트 측 정책으로 요청이 들어왔을 때, Same Origin이 아니라면 허용을 안해줌 → 서버 간 통신에는 적용되지 않음
SOP가 없다면?
- SOP가 없을 때 발생가능한 문제 예
- 가짜 은행 피싱사이트의 페이지에 iframe으로 진짜 은행 사이트가 로드됐다. iframe 내 진짜 은행 사이트에 로그인 했을 때 가짜 사이트가 간단한 스크립트로 진짜의 DOM 요소에 접근 가능하다. 요청을 조작해 누군가에게 송금하게 하는것도 가능할 수 있다.
- 출처가 다른 사이트 간 통신은 보안에 취약할 수 밖에 없다
- 출처가 같으면(eg. 클라이언트의 출처와 서버의 출처가 같으면. 근데 이런경우가 많지 않지) 외부의 공격을 차단할 수 있기에 SOP를 사용함
- 그러나 웹은 다른 출처의 resource를 가져다쓰는 경우가 많기에 예외 조항으로 CORS를 두고 있음

CORS
HTTP-header 의 정보를 통해 서버가 브라우저에게 리소스 로딩을 할 수 있는 다른 출처를 허용할 수 있게함

- 다양한 호스트에 접근하고 다양한 리소스가 필요하다 보니 보안이 중요해짐
- 클라이언트는 동일한 출처의 리소스에만 접근이 가능함(SOP)
- 다양한 출처의 리소스에 접근 가능하게 해주는 것이 CORS임
- 서버 측에서 CORS설정을 해 주었다면 응답 헤더에 Access-Control-Allow-Origin이 설정되어 돌아옴
- 작동방식 :
- 1) 클라이언트가 다른 출처의 리소스를 요청할 때 헤더에
Origin
필드에 요청을 보내는 곳(출처)를 함께 보낸다 - 2) 이후 서버가 응답할 때 응답 헤더의
Access-Control-Allow-Origin
필드에 해당 (서버에 있는)리소스에 접근이 허용된 출처를 보낸다 - 3) 응답을 받은 클라이언트는(브라우저) 보냈던 요청의
Origin
과 응답의Access-Control-Allow-Origin
을 비교해Origin
이 허용된 곳인지를 확인함 - 허용되지 않으면 CORS 에러. 허용되면 resource 받을 수 있음
CORS 흐름
1. 예비 요청(pre flight request)과 본 요청
- 우리가 가장 많이 접하는 요청
- preflight 요청은 CORS 호환 웹 브라우저와 서버 간에 자바스크립트 클라이언트의 실제 요청이 교차 사이트의 리소스에 접근하기 전 ‘무대 뒤에서’ 발생함. REST API는 CORS 제안인
Access-Control-Allow-Origin
HTTP 헤더를 사용해서 리소스에 대한 교차-사이트 접근을 허용하는 사이트를 나열 할 수있음
현재 웹 브라우저의 대부분은 Origin
이나 Access-Control-Request-Method
같은 특별한 HTTP 요청 헤더를 보냄으로써 CORS를 지원함
Origin
헤더 : 요청 자바스크립트 클라이언트의 스킴/호스트/포트의 소스 위치를 나타냄
Access-Control-Request-Method
헤더 : CORS preflight 요청 떄 보내지는데, 클라이언트의 실제 요청에서 어떤 HTTP 요청이 사용되는지를 나타냄
- 단순 요청이 아닌 경우 브라우저가 본 요청을 보내기 전 OPTIONS 메서드를 사용해 preflight를 보냄
- 서버는 해당 요청에 대해 response를 보내고 브라우저가 해당 Header정보를 확인한 후 허가된 경우에만 본 요청을 넣음
이 때 서버가 Header에 담은 허가 정보를 확인한 후 어떤 것이 해당이 안되는지 확인을 해야 함
Access-Control-Allow-Origin : 서버측 허가 출처 Access-Control-Allow-Method : 서버측 허가 메소드 Access-Control-Allow-Headers : 서버측 허가 헤더
2. 단순 요청


- 서버에서 Allow-Origin 안주면 본요청 fail하게 됨
- 브라우저를 거치지 않고 바로 서버로 들어감
- 서버에서 온 Response Header를 확인하여 브라우저가 CORS 위반 여부를 가림
3. Credentialed 요청

- 서버의 쿠키나 인증과 관련된 정보를 가져오도록 credentials 설정을 바꿀 수 있는데, 이 경우 더 엄격한 CORS 심사를 함

Client
가credentials
옵션을include
로 설정하고 다른 출처의 리소스에 접근할 때, 서버는 모든 출처, 메소드 헤더를 허용하고 있는 상태이면 ⇒ CORS 에러 발생함!
credentials
가include
인데 서버가 모든 출처, 메소드, 헤더에 대해 리소스를 허용한다면 아무 출처에나 인증 정보를 줘버려서 보안에 문제가 생김

- 따라서 서버가 다음과 같이 Header의 정보를 바꿔 주어야 함. 그래야 CORS 에러 발생안함
CORS 필요성
- 웹은 모두에게나 열려 있기 때문에 F12를 눌러 개발자 도구로 들어가면 코드도 열람 가능하고 스크립트 또한도 조작이 가능함 ⇒ CSRF, XSS 같은 사용자 공격에 노출
- 브라우저에 조작된 요청을 넣거나
- 브라우저에 저장된 사용자 정보를 빼가는 공격이 있을 수 있음
CORS 우회하기
1. 프론트엔드에서 Proxy Server(서버의 출처로)를 활용
- 브라우저는 axios 요청 시 프론트 엔드 출처끼리 리소스를 공유한다고 착각해 CORS를 따지지 않음
- 하지만 background에서 webpack이 프론트엔드 서버와 백엔드 서버를 연결해 줌
webpack-dev-server

http-proxy-middleware (node server 사용시)

리액트 CRA
package.json에 아래 입력

Spring에서 CORS 허용가능하도록 설정하는법
WebMvcConfigurer이용
class ServletConfig implements WebMvcConfigurer{ @Override public void addCorsMappings(CorsRegistry registry) { registry.addMapping("/api/**") .allowedOrigins("*") // 모든 출처에 접근 허가를 내리면 보안에 문제 발생. // 필요한 부분에만 접근허가 해주기 .allowedMethods("GET"); // "*" 하면 모든 메서드 .allowedHeaders() .allowCredentials() } }

WebSecurityConfig
@EnableWebSecurity public class WebSecurityConfig { @Bean public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { // ... http.cors(); return http.build(); } }
CorsFilter
- 위의 http.cors() 를 활성화하면 CorsFilter가 생기게 되는데 해당 필터에서 WebConfig의 config를 불러와서 허용을 해줌. 만약에 CorsFilter를 통과 못하면 CORS 허용이 안됨. 에러 발생
- 예를 들어 CorsFilter 앞의 Filter에서 response 만들어서 바로 내려줄 때, preflight 조차 CorsFilter를 통과하지 못하기에 CORS Error 가 발생하게 됨
@CrossOrigin 어노테이션 이용
- Controller 클래스에 붙일 수도, 메서드에 붙일수도 있음
테스트 방법
curl -I -X OPTIONS \ -H "Origin: http://EXAMPLE.COM" \ -H 'Access-Control-Request-Method: GET' \ http://EXAMPLE.COM/SOMETHING 2>&1 | grep 'Access-Control-Allow-Origin'
Chrome 에서 CORS Disable/Enable
open -n -a /Applications/Google\ Chrome.app/Contents/MacOS/Google\ Chrome --args --disable-site-isolation-trials --disable-web-security --user-data-dir="/tmp/chrome_dev_test"
- 위와 같이 실행하면 Chrome App 아이콘 하나 더 생기면서 걔만 CORS Disable된 버전으로 계속 키고 끌수 있음