프로젝트 중 react와 통신해야할 상황이 생겼다. cors
에러를 예상하고 있었지만 막상 닥치니 해결하기까지 너무 오랜 시간이 걸렸고, 많은 우여곡절이 있었다.
CORS
는 영어 그대로 교차 출처를 공유할 수 있는 권한을 부여하도록 브라우저에 알려주는 정책이다. 서로 다른 출처를 가진 Application이 서로의 Resource 에 접근할 수 있도록 해준다.
기본적으로 프로토콜
, 호스트
, 포트
를 통틀어서 Origin(출처) 라고 한다.
즉 서로 같은 출처란 이 셋이 동일한 출처를 말하고, 여기서 하나라도 다르다면 Cross Origin
, 즉 교차출처가 되는 것이다.
http://localhost:8080
: Spring Boothttp://localhost:3000
: React보안상의 이유로, 브라우저는 스크립트에서 시작한 Cross Origin HTTP Request
를 제한한다. 즉, SOP(Same Origin Policy)
를 따른다.
React와 Spring Boot의 port 가 서로 다르기 때문에 cors
정책 위반 에러가 나왔던 것이다.
Request
에는Origin
헤더가 있고,Response
에는Access-Control-Allow-Origin
헤더가 있는데 이 두 가지가 서로 같을 경우 같은 출처라고 인식한다는 것을 알고 있어야 한다.
Simple Request
는 Preflight Request
없이 요청을 보내 서버는 이에 대한 응답으로 Access-Control-Allow-Origin
헤더를 응답하는 방식이다.Credential
이 없는 요청의 경우 "*"
와일드 카드를 통해 브라우저의 Origin
에 상관없이 모든 리소스에 접근하도록 허용한다.Access-Control-Allow-Origin : Origin or *
가장 일반적으로 마주치는 시나리오이다.
먼저, OPTIONS
메서드를 통해서 다른 도메인의 리소스로 HTTP Request
를 보내고 실제 요청이 전송하기에 안전한지를 확인한다. Cross-Site Request
는 유저 데이터에 영향을 줄 수 있기 때문에 이와 같이 미리 전송(preflighted
)한다.
Access-Control-Request-Method:<method>
Access-Control-Request-Method
헤더는 실제 요청에 어떤 HTTP Method
가 사용될지 서버에게 알려주기 위해서 preflight request
에 사용된다.Access-Control-Allow-Mehtods:<method> or [<method>]*
Access-Control-Request-Method
헤더는 리소스에 접근할 때 허용되는 메서드를 지정한다. 이 헤더는 preflight request
에 대한 응답으로 사용된다.Access-Control-Request-Headers: <header-name>[, <header-name>]*
Access-Control-Request-Headers
헤더는 실제 요청에서 어떤 HTTP Header
를 사용할지 서버에 알려주기 위해서 preflight request
에 사용된다Access-Control-Allow-Headers
헤더는 preflight request
에 대한 응답으로 실제 요청 시 사용할 수 있는 HTTP Header
를 알려준다.HTTP Cookie
와 HTTP Authentication
정보를 사용한다.Credentials
이 필요한 CORS
에는 Front
는 Response Header
에 withCredentials=true
, Backend
는 Response Header
에 Access-Control-Allow-Origin
을 포함해야 한다.Credential Request
는 Cross-site XMLHttpRequest
나 Fetch 호출은 기본적으로 자격 증명을 보내지 않기 때문에 credentials
옵션을 통해 인증 보안을 강화한다. credentials
옵션이다.Access-Control-Allow-Credentials: true
로 응답하지 않으면, 사용자 인증이 필요한 리소스에 대한 응답은 무시되고 웹 컨텐츠는 제공되지 않는다. Access-Control-Allow-Credentials: true | false
Request with Credential
방식을 사용할 것인지를 지정한다.Access-Control-Allow-Credentials
헤더는 credentials
플래그가 true
일 때 요청에 대한 응답을 표시할 수 있는지를 나타낸다.The HTTP Access-Control-Allow-Credentials is a Response header. The Access-Control-Allow-Credentials header is used to tell the browsers to expose the response to front-end JavaScript code when the request’s credentials mode Request.credentials is “include” | https://www.geeksforgeeks.org/http-headers-access-control-allow-credentials/
CORS 반드시 Spring Security
에 앞서 처리되어야 한다. preflight request
는 JSESSIONID
같은 쿠키를 포함하고 있지 않고, 이는 Request
가 인증되지(not authenticated)않은 사용자라고 판단하고 거절해버리기 때문이다.
CorsFilter
를 이용해서 CORS를 해결할 수 있었다. CorsFilter
는 CORS를 다룰 수 있는 가장 쉬운 방법이다. CorsConfigurationSource
를 제공해 CorsFilter
를 Spring Security
에 통합할 수 있다.
@Bean
CorsConfigurationSource corsConfigurationSource() {
CorsConfiguration configuration = new CorsConfiguration();
configuration.setAllowedOriginPatterns(Arrays.asList("*"));
configuration.setAllowedMethods(Arrays.asList("HEAD","POST","GET","DELETE","PUT"));
configuration.setAllowedHeaders(Arrays.asList("*"));
configuration.setAllowCredentials(true);
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/**", configuration);
return source;
}
만약 Spring MVC
의 CORS
지원을 사용한다면 Spring Security
의 CorsConfigurationSource
설정을 생략할 수 있다고 한다. Spring Security
는 Spring MVC
에서 제공되는 CORS
설정을 활용할 수 있기 대문이다.
Spring MVC
에서 CORS를 적용하는 방법은 크게 2가지로 나뉜다.
@CrossOrigin
어노테이션을 사용해서 설정하는 방법Spring MVC
설정에서 CORS
를 설정하는 방법@RestController
@RequestMapping("/account")
public class AccountController {
@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) {
// ...
}
}
@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) {
// ...
}
}
@CrossOrigin
애노테이션을 통해 origins, methods, allowedHeaders, exposedHeaders, allowCredentials, maxAge 모두 설정할 수 있다.필터 기반 솔루션을 사용하는 것과 유사하지만 Spring MVC 내에서 선언하고 @CrossOrigin 설정과 결합할 수 있다.
@Configuration
@EnableWebMvc
public class WebConfig implements WebMvcConfigurer {
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**");
.allowedOrigins("http://www.example.com")
.allowedMethods("*")
.allowCredentials(false)
.maxAge(3000);
}
}