- 웹에서는 보안을 위해 기본적으로 한 웹 페이지(출처 A)에서 다른 웹 페이지(출처 B)의 데이터를 직접 불러오는 것을 제한하는데 이를 '동일 출처 정책(Same-Origin Policy)'
- 만약 다른 출처의 리소스를 안전하게 사용하고자 할 경우 CORS 가 등장하며 CORS는 특별한 HTTP 헤더를 통해 한 웹 페이지가 다른 출처의 리소스에 접근할 수 있도록 '허가'를 구하는 방법이라 할 수 있음
→ 즉, 웹 애플리케이션이 다른 출처의 데이터를 사용하고자 할 때 브라우저가 그 요청을 대신해서 해당 데이터를 사용해도 되는지 다른 출처에게 물어보는 것 - 출처를 비교하는 로직은 서버에 구현된 스펙이 아닌 브라우저에 구현된 스펙 기준으로 처리되며 브라우저는 클라이언트의 요청 헤더(A출처)와 서버의 응답 헤더(B출처)를 비교해서 최종 응답을 결정
- 두 개의 출처를 비교하는 방법은 URL의 구성요소 중 Protocol, Host, Port 이 세 가지가 동일한지 확인하면 되고 나머지는 틀려도 상관 없음
- https://domain-a.com의 프론트 엔드 JavaScript 코드가 XMLHttpRequest를 사용하여 https://domain-b.com/data.json을 요청하는 경우 보안 상의 이유로 브라우저는 스크립 트에서 시작한 교차 출처 HTTP 요청을 제한
- XMLHttpRequest와 Fetch API는 동일 출처 정책을 따르기 때문에 이 API를 사용하는 웹 애플리케이션은 자신의 출처와 동일한 리소스만 불러올 수 있으며, 다른 출처의 리소스를 불러오려면 그 출처에서 올바른 CORS 헤더를 포함한 응답을 반환해야 함
CORS 종류
- Simple Request
- Simple Request 는 예비 요청(Prefilght) 과정 없이 자동으로 CORS 가 작동하여 서버에 본 요청을 한 후 서버가 응답의 헤더에 Access-Control-Allow-Origin 과 같은 값 을 전송하면 브라우저가 서로 비교 후 CORS 정책 위반 여부를 검사하는 방식
- 제약 사항
- GET, POST, HEAD 중의 한가지 Method를 사용
- 헤더는 Accept, Accept-Language, Content-Language, Content-Type, DPR, Downlink, Save-Data, Viewport-Width Width만 가능 → Custom Header 는 허용되지않음
- Content-type 은 application/x-www-form-urlencoded, multipart/form-data, text/plain 만 가능
- Preflight Request (예비요청)
- 브라우저는 요청을 한번에 보내지 않고, 예비 요청과 본 요청으로 나누어 서버에 전달하는데 브라우저가 예비 요청을 보내는 것을 Preflight라고 하며 이 예비 요청의 메소드에는 OPTIONS 사용
- 예비 요청의 역할은 본 요청을 보내기 전에 브라우저 스스로 안전한 요청인지 확인하는 것으로 요청 사양이 Simple Request에 해당하지 않을 경우 브라우저가 Preflight Request를 실행
Preflight Request(예비 요청)
- 브라우저가 보낸 요청을 보면 Origin에 대한 정보 뿐만 아니라 예비 요청 이후에 전송할 본 요청에 대한 다른 정보들도 함께 포함되어 있는 것을 확인 가능
- 이 예비 요청에서 브라우저는 Access-Control-Request-Headers를 사용하여 자신이 본 요청에서Content-Type 헤더를 사용할 것을 알려주거나, Access-Control-Request-Method를 사용하여 GET 메소드를 사용할 것을 서버에게 미리 알려줌
- 서버가 보내준 응답 헤더에 포함된 Access-Control-Allow-Origin: https://security.io의 의미는 해당 URL 외의 다른 출처로 요청할 경우에는 CORS 정책을 위반했다고 판단하고 오류 메시지를 내고 응답을 버림
동일 출처 기준
URL | 동일 출처 | 근거 |
https://www.security.io/auth?username=user1 | O | 스킴, 호스트, 포트 동일 |
https://user:password@security.io | O | 스킴, 호스트, 포트 동일 |
http://www.security.io/ | X | 스킴 다름 |
https://api.security.io | X | 호스트 다름 |
https://security.io:8000 | ? | 브라우저의 구현에 따라 다름, explorer 는 포트자체를 무시 |
CORS 해결 - 서버에서 Access-Control-Allow-* 세팅
- Access-Control-Allow-Origin
- 헤더에 작성된 출처만 브라우저가 리소스를 접근할 수 있도록 허용
- *, https://security.io
- Access-Control-Allow-Methods
- preflight request 에 대한 응답으로 실제 요청 중에 사용할 수 있는 메서드를 나타냄
- 기본값은 GET,POST,HEAD,OPTIONS, *
- Access-Control-Allow-Headers
- preflight request에 대한 응답으로 실제 요청 중에 사용할 수 있는 헤더 필드 이름을 나타냄
- 기본값은 Origin,Accept,X-Requested-With,Content-Type, Access-Control-Request-Method,Access-Control-Request-Headers, Custom Header, *
- Access-Control-Allow-Credentials
- 실제 요청에 쿠키나 인증 등의 사용자 자격 증명이 포함될 수 있음을 나타냄
- Client의 credentials:include 옵션일 경우 true는 필수
- Access-Control-Max-Age
- preflight 요청 결과를 캐시 할 수 있는 시간을 나타내는 것으로 해당 시간동안은 preflight 요청을 다시 하지 않음
cors() & CorsFilter
- CORS의 사전 요청(pre-flight request)에는 쿠키 (JSESSIONID)가 포함되어 있지 않기 때문에 Spring Security 이전에 처리되어야 함
- 사전 요청에 쿠키가 없고 Spring Security가 가장 먼저 처리되면 요청은 사용자가 인증되지 않았다고 판단하고 거부 가능
- CORS 가 먼저 처리되도록 하기 위해서 CorsFilter를 사용할 수 있으며 CorsFilter에 CorsConfigurationSource를 제공함으로써 Spring Security와 통합 가능
@Bean
SecurityFilterChain defaultSecurityFilterChain(HttpSecurity http) throws Exception {
http.cors(cors -> cors.configurationSource(corsConfigurationSource()));
;
return http.build();
}
@Bean
public CorsConfigurationSource corsConfigurationSource() {
CorsConfiguration configuration = new CorsConfiguration();
configuration.addAllowedOrigin("http://example.com");
configuration.addAllowedMethod("GET", "POST");
configuration.setAllowCredentials(true);
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/**", configuration); // 모든 경로에 대해 CORS 설정
return source;
}
- http.cors()
- 커스텀하게 사용할 CorsConfigurationSource를 설정
- CorsConfigurationSource를 설정하지 않으면 Spring MVC의 CORS 구성을 사용
'Language > Spring Security' 카테고리의 다른 글
CSRF 토큰 유지 및 검증 (0) | 2024.07.29 |
---|---|
CSRF (Cross Site Request Forgery, 사이트 간 요청 위조) (0) | 2024.07.29 |
예외 필터 - ExceptionTranslationFilter() (0) | 2024.07.29 |
예외 처리 - ExceptionHandling() (0) | 2024.07.29 |
SessionManagementFilter / ConcurrentSessionFilter (0) | 2024.07.26 |