Cors는 Cross-Origin Resource Sharing 의 약자로 교차 출처 리소스 공유
를 의미합니다. 여기서 교차 출처는 다른 출처를 의미하며 다른 출처 간의 자원을 공유하는 정책을 말합니다.
URL에 보면 위의 그림과 같이 여러개의 구성 요소로 이루어져 있습니다. 여기서 출처 ( Origin ) 는 Protocol , Host , Port 세 가지를 의미합니다. ex ) http://domain:8080
여기서 위의 세가지 ( 출처 ) 가 같다면 같은 출처이며 셋 중에 하나라도 다르면 다른 출처로 인식합니다.
SOP는 Same-Origin Policy의 약자로 같은 출처만 허용하는 정책입니다. 과거에는 보안을 위해 같은 출처만 통신하도록 허용하였으나, 최근에는 다른 출처의 리소스를 활용하는 일이 흔하므로 SOP의 예외 조항인 CORS 정책을 채택하였습니다.
기본적으로 웹 클라이언트가 다른 출처로 리소스를 요청할 때 헤더의 origin에 요청을 보내는 출처를 담아 요청을 보냅니다. 예 ) Origin: https://google.com
요청을 보내면 서버는 응답 헤더인 Access-Controll-Allow-Origin 이라는 값에 해당 리소스에 접근할 수 있는 출처 목록을 담아 보내줍니다. 이후 응답을 받은 클라이언트는 자신이 보냈던 요청 헤더의 origin과 서버의 응답 헤더 Access-Controll-Allow-Origin을 비교하여 정상적인 요청인지 확인합니다.
만약 허용되지 않는다면 Cors 정책 위반 이슈가 발생하며 , 허용된 출처라면 리소스를 가져옵니다.
핵심흐름은 위와 같으며 Cors가 동작하는 방식에는 크게 Preflight Request , Simple Request , Credentialed Request 로 나뉘는데 여기서는 Preflight Request 와 Simple Request 만 소개해 드립니다.
Preflight Request 방식은 일반적으로 클라이언트가 요청을 한 번만 보내지 않고 예비 요청과 본 요청으로 나누어서 서버에게 요청합니다. 이때 예비 요청은 Options 메서드를 사용하여 본 요청을 보내기 전에 해당 요청이 안전한지 확인합니다.
Simple Request는 예비 요청을 보내지 않고 바로 본 요청을 보내고, 응답 헤더의 Access-Controll-Allow-Origin 값을 확인하여 Cors 정책 위반 여부를 확인합니다.
이때 Simple Request를 사용하기 위해 다음과 같은 조건을 만족해야 합니다.
- 요청의 메소드는 GET, HEAD, POST 중 하나여야 한다.
- Accept, Accept-Language, Content-Language, Content-Type, DPR, Downlink,
Save-Data, Viewport-Width, Width를 제외한 헤더를 사용하면 안 된다.- 만약 Content-Type를 사용하는 경우에는 application/x-www-form-urlencoded,
multipart/form-data, text/plain만 허용된다.
Simple Request는 웹 어플리케이션에서 사용하기에 해당 조건을 만족하기 어려워 Prefligh Request 방식을 주로 사용합니다.
Cors를 해결하는 방법으로는 다음과 같은 방법이 있습니다.
가장 쉬운 방법으로 Controller 클래스 상단 위에 @CrossOrigin 어노테이션을 활용할 수 있습니다.
@CrossOrigin(originPatterns = "http://localhost:8080")
@RestController
public class Controller {
}
이 방법은 쉽지만 Cors를 적용할 대상이 많아지면 중복된 코드가 늘어날 수 있습니다. 따라서 전체 Controller 에게 적용할 수 있게 Config 파일을 활용할 수 있습니다.
@Configuration
public class WebConfig implements WebMvcConfigurer {
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**")
.allowedOrigins("http://localhost:8080")
.allowedMethods(HttpMethod.GET.name());
}
}
WebMvcConfigurer를 상속받아 addCorsMappings을 재정의하면 됩니다. addMapping을 통해 Cors 정책을 적용할 URL 패턴을 설정하고, 허용할 Origin 을 작성해줄 수 있습니다. 그 외에 Http Method 를 제한할 수 있습니다.
@Component
@Order(Ordered.HIGHEST_PRECEDENCE)
public class CorsFilter implements Filter {
@Value("${client.url}")
private String clientUrl;
@Override
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) req;
HttpServletResponse response = (HttpServletResponse) res;
response.setHeader("Access-Control-Allow-Origin", clientUrl);
response.setHeader("Access-Control-Allow-Credentials", "true");
response.setHeader("Access-Control-Allow-Methods","*");
response.setHeader("Access-Control-Max-Age", "3600");
response.setHeader("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept, Authorization");
if("OPTIONS".equalsIgnoreCase(request.getMethod())) {
response.setStatus(HttpServletResponse.SC_OK);
} else {
chain.doFilter(req, res);
}
}
}
응답 헤더 Access-Controll-Allow-Origin에 클라이언트 Origin을 포함하여 보내는 방법도 있습니다.
그 외에 클라이언트에서 프록시 서버
를 통해 간접적으로 요청을 전달하여 응답을 받을 수 있습니다. 프록시 서버는 클라이언트의 요청을 받아 서버의 origin으로 요청을 보내면 cors 이슈 없이 해결할 수 있습니다.
참고 블로그 1 : https://steady-coding.tistory.com/616
참고 블로그 2 : https://wonit.tistory.com/m/572
참고 블로그 3 : https://wonit.tistory.com/307