CORS(Cross-Origin-Resource Sharing)
출처가 다른 자원들을 공유한다는 뜻으로, 한 출처에 있는 자원에서 다른 출처에 있는 자원에 접근하도록 하는 개념입니다. 직역하면, 교차되는 출처 자원들의 공유입니다. 다른 출처에 있는 자원을 요청한다고 하면, 이를 교차 출처 요청이라고 부릅니다.
🏷️ 출처(Origin)
위의 구성요소 중에서 Protocol + Host + Port 3가지가 같으면 동일 출처(Origin)라고 합니다. 브라우저 개발자 도구의 콘솔 창에 location.origin를 실행하면 출처를 확인할 수 있다.
✅ 동일 출처 예시
URL | 설명 |
http://example.com:80 http://example.com |
HTTP 기본 Port인 80번이 생략되어있으므로 동일 출처입니다. |
http://example.com/app1/index.html http://example.com/app2/index.html |
Protocol, Host, Port(생략)이 같으며, Path부터 다르므로 동일 출처입니다. |
✅ 다른 출처 예시
URL | 설명 |
http://example.com/app1 https://example.com/app2 |
Protocol이 다릅니다 |
http://example.com http://www.example.com http://myapp.example.com |
Host가 다릅니다 |
http://example.com http://example.com:8080 |
80, 8080으로 포트가 다릅니다 |
✅ 동일 출처 요청 vs 다른 출처 요청
왼쪽의 핸드폰의 URL은 domain-a.com 입니다. 오른쪽 서버의 URL은 domain-a.com과 domain-b.com 2가지 입니다.
domain-a.com 유저가 domain-a.com 서버에 요청하면 동일 정책이기 때문에 아무런 문제가 없지만, domain-a.com 유저가 domain-b.com 서버에 요청하면 호스트가 다르기 때문에 다른 출처 요청을 합니다
✅ 동일 출처 정책(Same-origin policy)
동일 출처 정책(Same-origin policy)는 다른 출처로부터 조회된 자원들의 읽기 접근을 막아 다른 출처 공격을 예방합니다. 그러나, 다른 출처에서 얻은 이미지를 담는 <img>, 외부 주소를 담는 <link> 같은 여러 태그들을 허용합니다. 동일 출처 정책의 정확한 구현 명세는 없지만 최신의 브라우저들은 일정 규칙을 따르고 있습니다.
동일출처 정책은 다른 출처 자원을 가져오는 것을 굉장히 제한적으로 허용했으며 SPA와 미디어 중심 웹 사이트들이 더욱 늘어나고 있으므로 관련 규칙들도 계속 늘어납니다. 따라서, 다른 출처 리소스에 접근성을 높이기 위해서 CORS가 등장했습니다.
✔️ 다른 출처 요청의 위험성
<img>, <frame> 등이 웹에 등장하면서, 페이지 로딩 이후에 브라우저에서 이러한 하위 자원들을 가져올 수 있게 되어서 동일 출처, 다른 출처 모두 호출이 가능하게 되었습니다.
CORS 정책이 없고 모든 다른 출처 요청이 가능한 브라우저를 생각해봅시다.
위와같이 홈페이지를 서핑하고 있는데, <script>가 심어진 evil.com 페이지를 열게되면 <script>가 실행되어 은행에 'Delete /account'를 요청하도록 되어 있습니다. 이로 인해서 AJAX 호출로 은행 API를 호출하여 나의 은행 계좌를 삭제해버리는 사고가 발생합니다.
따라서, 다른 출처의 접근을 막기 위해서 동일 출처 정책이 등장했습니다.
🏷️ 다른 출처 요청 정책
1. 단순요청(Simple Request)
단순 요청은 서버에 API를 요청하고, 서버는 Access-Control-Allow-Origin 헤더를 포함한 응답을 브라우저에 보냅니다. 이때 브라우저는 Access-Control-Allow-Origin 헤더를 확인해서 CORS 동작을 수행할지 판단합니다.
✔️ 단순요청(Simple Request) 조건
▪️ GET, HEAD, POST 요청만 가능합니다
▪️ Accept, Accept-Language, Contet-Language, Content-Type과 같은 CORS 안전 리스트 헤더 혹은 User-Agent 헤더가능합니다.
▪️ Contet-Type 헤더는 application/x-www-form-urlencoded, multipart/form-data and text/plain만 가능합니다.
▪️ XMLHttpRequest 객체를 사용하여 요청하면, 요청에서 사용된 XMLHttpRequest.upload에 의해 반환되는 객체에 어떠한 이벤트 리스너도 등록되지 않습니다.
2. 프리 플라이트 요청(Preflight Request)
프리플라이트(Preflight) 방식은 서버에 예비 요청을 보내서 안전한지 판단한 후 본 요청을 보내는 방법입니다. 브라우저는 요청을 한번에 보내지 않고 예비 요청과 본 요청으로 나누어서 서버로 전송합다.
Preflight
브라우저가 본 요청을 보내기 전에 보내는 예비 요청을 라고 부르는 것이라고 합니다. 이 예비 요청에는 HTTP 메소드 중 OPTIONS 메소드가 사용됩니다. 예비 요청의 역할은 본 요청을 보내기 전에 브라우저 스스로 이 요청을 보내는 것이 안전한지 확인하는 것입니다.
요청 헤더
- 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 : 프리 플라이트 요청의 응답을 캐시에 저장하는 시간
플리플라이트 요청은 OPTIONS 를 사용해 자신의 주소, origin, access-control-request-method, access-control-request-headers를 같이 보냅니다.
정상적인 응답으로 access-control-allow-origin, access-control-allow-method, access-control-allow-headers, access-control-max-age를 응답받습니다.
정상 요청과 응답이 가능하다는 프리 플라이트 덕분에 실제 요청을 합니다. 그리고 정상적인 응답을 받습니다.
3. 신용 요청(Credentialed Request)
신용 요청은 출처 간 통신에서 좀 더 보안을 강화하고 싶을 때 사용하는 방법으로 쿠키, 인증 헤더, TLS 클라이언트 인증서 등의 신용정보와 함께 요청합니다. 기본적으로, CORS 정책은 다른 출처 요청에 인증정보 포함을 허용하지 않습니다. 하지만 요청에 인증을 포함하는 플래그가 있거나 access-control-allow-credentials가 true로 설정 한다면 요청할 수 있습니다.
🏷️ Spring Boot에서 cross-origin 설정
1. 메서드 설정
@RestController
@RequestMapping("/account")
public class AccountController {
// 1. 모든 출처가 허용됩니다.
// 2. 허용된 HTTP 메서드는 @RequestMapping에 선언된 메서드들입니다.
// 3. 프리플라이트 응답은 30분 동안 캐시됩니다.
@CrossOrigin
@RequestMapping(method = RequestMethod.GET, path = "/{id}")
public Account retrieve(@PathVariable Long id) {
// ...
}
@RequestMapping(method = RequestMethod.DELETE, path = "/{id}")
public void remove(@PathVariable Long id) {
// ...
}
}
2. 컨트롤러 설정
@CrossOrigin(origins = "http://example.com", maxAge = 3600)
@RestController
@RequestMapping("/account")
public class AccountController {
@RequestMapping(method = RequestMethod.GET, path = "/{id}")
public Account retrieve(@PathVariable Long id) {
// ...
}
@RequestMapping(method = RequestMethod.DELETE, path = "/{id}")
public void remove(@PathVariable Long id) {
// ...
}
}
컨트롤러에 설정했으므로 AccountController에 있는 retrieve() 와 remove() 함수 모두에 적용됩니다.
3. 개별 설정
@CrossOrigin(maxAge = 3600)
@RestController
@RequestMapping("/account")
public class AccountController {
@CrossOrigin("http://example.com")
@RequestMapping(method = RequestMethod.GET, "/{id}")
public Account retrieve(@PathVariable Long id) {
// ...
}
@RequestMapping(method = RequestMethod.DELETE, path = "/{id}")
public void remove(@PathVariable Long id) {
// ...
}
}
스프링에서 여러가지 CORS 정책을 복합해서 설정 할 수 있습니다. 모든 메서서드들은 3600초가 캐시 시간입니다. retrieve() 메서드는 허용 출처가 "http://example.com" 밖에 안됩니다. 하지만, remove() 메서드는 별도의 설정이 없으므로 모든 출처가 가능합니다.
4. 전역 설정
@Bean
public WebMvcConfigurer corsConfigurer() {
return new WebMvcConfigurer() {
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**") // 전체
.allowedMethods("*") // 모든 메서드
.allowedOrigins("http://localhost:3000"); // 허용할 주소
}
};
}
📖 Reference
'코딩 공부 > web & Java' 카테고리의 다른 글
[Web] JWT (1) | 2023.12.08 |
---|---|
[Spring/Web] CSRF (1) | 2023.12.04 |
[WEB] MPA와 SPA (0) | 2023.11.25 |
[JPA] @Entity는 기본 생성자를 왜 가져야 하는가? (0) | 2023.11.22 |
[Spring] Response Customizing (1) | 2023.11.20 |