CORS(Cross-Origin Resource Sharing)
출처가 다른 자원들을 공유한다는 뜻으로,
한 출처에 있는 자원에서 다른 출처에 있는 자원에 접근하도록 하는 개념
HTTP 헤더를 사용해 한 출처에서 실행 중인 웹 어플리케이션이 다른 출처(프로토콜, 도메인, 포트번호)의 리소스에 접근할 수 있는 권한을 부여하도록 브라우저에 알려주는 체제
- 브라우저에서는 보안적인 이유로 cross-origin HTTP 요청들을 제한한다.
- 그래서 cross-origin 요청을 하려면 서버의 동의가 필요하다.
- 만약 서버가 동의한다면 브라우저에서는 요청을 허락하고, 동의하지 않는다면 브라우저에서 거절한다.
CORS가 필요한 이유
- CORS 없이 모든 곳에서 데이터를 요청할 수 있게 되면, 다른 사이트에서 원래 사이트를 모방할 수 있음
- 어디서나 공격을 할 수 없도록 브라우저에서 보호하고, 필요한 경우에 서버와 협의해서 요청할 수 있도록 하기 위함
출처(Origin)
- 아래의 구성요소 중에서 Protocol + Host + Port 이 3가지가 같으면 동일 출처(Origin)이라고 한다.
동일 출처 요청(Same-Origin Policy) 과 다른 출처 요청(Cross-Origin Policy)
- 요청하는 클라이언트와 요청받는 서버가 같은 출처에 있으면 동일 출처, 서로 다른 서버에 있으면 다른 출처 요청
- 기본적으로 동일 출처 요청만 자유롭게 요청이 가능하고, 동일 출처 정책이라고 한다.
- 다른 출처 요청도 할 수 있도록 기준을 만든 체제는 다른 출처 정책이라고 한다.
- Postman과 같은 다른 서버에서 API를 호출할 때는 잘 동작하다가 브라우저에서 API를 호출할 때만 CORS Policy 오류가 발생하는 이유 : 브라우저가 동일 출처 정책을 지키고 있어서 다른 출처의 리소스 접근을 금지하고 있기 때문이다.
CORS의 동작 방법
1. HTTP 프로토콜을 사용해 요청을 보내고, 요청 헤더의 Origin 필드에 요청을 보내는 출처를 담아서 보낸다.
2. 이후 서버가 이 요청에 대한 응답을 할 때 응답 헤더의 Access-Control-Allow-Origin이라는 값에 "리소스 접근이 허용된 출처"를 내려준다.
3. 응답을 받은 브라우저는 자신이 보낸 Origin과 서버가 보내 준 응답인 Access-Control-Allow-Origin을 비교해 유효한 응답인지 확인한다.
4. 만약 유효하지 않으면 그 응답을 사용하지 않는다.
cross-origin
1. 단순 요청(Simple Request)
- 서버에 요청을 바로 보낸 후, 서버가 응답 헤더에 Access-Control-Allow-Origin과 같은 값을 보내면 그 때 브라우저가 CORS 정책 위반 여부를 검사하는 방식
- 만족해야 하는 조건
- GET, HEAD, POST 요청만 가능하다.
- Accept, Accept-Langauge, Content-Langauge, Content-Type, DPR, Downlink, Save-Data, Viewport-Width, Width과 같은 CORS 안전 리스트 헤더 혹은 USER-Agent 헤더
- Content-Type 헤더는 application/x-www-form-urlencoded, multipart/form-data, text/plain
- ReadableStream 객체는 사용되지 않는다.
- XMLHttpRequest 객체를 사용해 요청하면, 요청에서 사용된 XMLHttpRequest.upload에 의해 반환되는 객체에 어떠한 이벤트 리스너도 등록되지 않는다.
Simple Request의 동작방법
- 브라우저가 다른 출처에 자신의 주소 https://www.site.com를 origin에 담아서 요청을 보낸다.
- 서버는 요청을 확인하고 다른 출처 주소 https://www.site.com에 접근이 가능하다는 access-control-allow-origin에 해당 주소를 담아서 결과를 리턴한다.
- 서버가 이 헤더에 응답하지 않거나, 헤더 값이 출처와 일치하지 않는 도메인일 때 브라우저가 응답을 차단한다.
2. 프리 플라이트(Preflight Request)
OPTIONS 메서드로 HTTP 요청을 미리 보내 실제 요청이 전송하기에 안전한지 확인한다.
다른 출처 요청이 유저데이터에 영향을 줄 수 있기 때문에 미리 전송한다는 의미
즉 브라우저는 요청을 한 번에 보내지 않고 예비 요청과 본 요청으로 나누어서 서버로 전송한다.
요청 헤더에 포함되는 값들
origin | 어디서 요청을 했는지 서버에 알려주는 주소 |
access-control-request-method | 실제 요청이 보낼 HTTP 메서드 |
access-control-request-headers | 실제 요청에 포함된 header |
응답 헤더에 포함되는 값들
access-control-allow-origin | 서버가 허용하는 출처, 이 헤더 값은 하나의 출처가 될 수도 있고, "*"를 사용해 어떤 출처도 허용할 수 있도록 한다. |
access-control-allow-methods | 서버가 허용하는 HTTP 메서드 리스트 |
access-control-allow-headers | 서버가 허용하는 header 리스트 |
access-control-max-age | 프리 플라이트 요청의 응답을 캐시에 저장하는 시간 |
Preflight의 동작방법
- OPTIONS를 사용해 자신의 주소 https://www.api.com?q=test를 보내면서 origin, access-control-request-method, access-control-request-headers를 같이 보낸다.
- 정상적인 응답으로 access-control-allow-origin, access-control-allow-methods, access-control-allow-headers, access-control-max-age를 응답받는다.
- 정상 요청과 응답이 가능하다는 프리 플라이트 덕분에 요청을 하고 정상적인 응답을 받는다.
3. 신용 요청(Credentialed Request)
인증된 요청을 사용하는 방법으로 CORS의 기본적인 방식 보다는 다른 출처 간 통신에서 조금 더 보안을 강화하고 싶을 때 사용하는 방법
쿠키, 인증 헤더, TLS 클라이언트 인증서 등의 신용정보와 함께 요청한다.
기본적으로, CORS 정책은 다른 출처 요청에 인증정보 포함을 허용하지 않지만 요청에 인증을 포함하는 플래그가 있거나 access-control-allow-credentials가 true로 설정한다면 요청할 수 있다.
옵션 값 | 설명 |
same-origin (default) | 같은 출처 간 요청에만 인증 정보를 담을 수 있다. |
include | 모든 요청에 인증 정보를 담을 수 있다. |
omit | 모든 요청에 인증 정보를 담지 않는다. |
- 만약 same-origin이나 include와 같은 옵션을 사용해 리소스 요청에 인증 정보가 포함되면, 브라우저는 Cross-Origin 리소스를 요청할 때 단순히 Access-Control-Allow-Origin만 확인하는 것이 아니라 조금 더 빡빡한 검사 조건을 추가하게 된다.
Credentialed Request의 동작방법
- 서버 응답에 access-control-allow-credentials가 true로 설정되지 않았거나 access-control-allow-origin 헤더에 있는 값이 허용된 출처가 아니라면 아래와 같이 오류가 발생!
프리 플라이트 요청으로 실제 요청이 실행되지 이전에 검사를 하고 허용할 지 안 할 지 결정할 수 있기 때문에 프리프라이트 요청 방식이 가장 좋다!
CORS가 발생하는 예시
Client와 Server라는 스프링부트 프로젝트를 시작하고, Client에서 Server로 fetch 요청을 쓰는 예제
Server Code
@RestController
public class ServerSideController {
@GetMapping("/server")
public String message() {
return "server";
}
}
- 서버에는 단순히 String을 리턴하는 RestController
Client Code
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>Client - Server</title>
</head>
<body>
<script>
fetch('http://localhost:8080/server').then(response => {
alert(response);
});
</script>
</body>
</html>
- 클라이언트에는 단순히 index.html에서 서버로 요청을 보낸다.
- CORS는 클라이언트가 다른 도메인으로 요청을 보내야 하기 때문에, 서로 다른 포트를 설정(서버는 8080, 클라이언트는 7070)
- 위와 같이 설정하고, [http://localhost:7070/](http://localhost:7070/) 으로 접근하면 아래와 같은 CORS 예외가 발생한다.
- 클라이언트는 http://localhost:7070 도메인을 사용하고 서버는 http://localhost:8080을 사용하고 있기 때문에 다른 도메인에 접근하지 못하도록(프로토콜과 호스트는 같고, 포트 다름) 예외를 발생시키는 것
📌해결책
- 가장 단순한 해결책은 @CrossOrigin을 사용하는 것
- @CrossOrigin을 사용하면 다른 도메인의 클라이언트가 나의 서버에 요청 보내는 것을 허용하는 것이다.
@RestController
public class ServerSideController {
@CrossOrigin("http://localhost:7070")
@GetMapping("/server")
public String message() {
return "server";
}
}
- 세부적으로 자신의 서버에서 CrossOrigin을 설정하고 싶다면 아래의 방법과 같이 Configuration을 추가하는 방법도 있다.
@Configuration
public class WebConfig implements WebMvcConfigurer {
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**");
}
}
- 프로젝트를 하면서 단순히 클라이언트와 서버를 연결해주기 위해서 CORS를 사용한다고 하고, 사용했었다. 얼추 맞는 것 같으면서도 더 자세하게 알 수 있었다.
- cors는 HTTP 헤더를 사용해 한 출처에서 실행 중인 웹 어플리케이션이 다른 출처(프로토콜, 도메인, 포트번호)의 리소스에 접근할 수 있는 권한을 부여하도록 브라우저에 알려주는 체제이다!!!
[출처]
'cs' 카테고리의 다른 글
[혼공운영체제] 2. CPU의 작동원리(ALU와 제어장치, 레지스터) (0) | 2023.04.13 |
---|---|
방화벽(Firewall)의 동작 원리 (0) | 2023.04.10 |
[혼공운영체제] 1. 데이터와 명령어 (0) | 2023.04.06 |
[혼공운영체제] 0.컴퓨터 구조의 큰 그림(컴퓨터 핵심 부품) (0) | 2023.04.05 |
MSA란? (0) | 2023.04.04 |