[ Server ] Cors

5tr1ker·2023년 6월 9일
0

Server

목록 보기
4/10
post-thumbnail

Cors 개요

Cors는 Cross-Origin Resource Sharing 의 약자로 교차 출처 리소스 공유를 의미합니다. 여기서 교차 출처는 다른 출처를 의미하며 다른 출처 간의 자원을 공유하는 정책을 말합니다.

URL에 보면 위의 그림과 같이 여러개의 구성 요소로 이루어져 있습니다. 여기서 출처 ( Origin ) 는 Protocol , Host , Port 세 가지를 의미합니다. ex ) http://domain:8080
여기서 위의 세가지 ( 출처 ) 가 같다면 같은 출처이며 셋 중에 하나라도 다르면 다른 출처로 인식합니다.

SOP

SOP는 Same-Origin Policy의 약자로 같은 출처만 허용하는 정책입니다. 과거에는 보안을 위해 같은 출처만 통신하도록 허용하였으나, 최근에는 다른 출처의 리소스를 활용하는 일이 흔하므로 SOP의 예외 조항인 CORS 정책을 채택하였습니다.

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

Preflight Request 방식은 일반적으로 클라이언트가 요청을 한 번만 보내지 않고 예비 요청과 본 요청으로 나누어서 서버에게 요청합니다. 이때 예비 요청은 Options 메서드를 사용하여 본 요청을 보내기 전에 해당 요청이 안전한지 확인합니다.

  • 클라이언트가 API 를 사용하여 서버의 리소스를 요청합니다.
  • 이때 클라이언트는 서버에게 예비 요청을 보냅니다.
  • 예비 요청의 Origin과 응답의 Access-Controll-Allow-Origin 을 비교합니다.
    - 허용되지 않는 출처라면 Cors 정책 위반 이슈가 발생합니다. ( 응답 코드는 200 )
    • 응답에 실패하면 Cors 정책 위반 이슈가 발생합니다.
  • 예비 요청을 성공하면 본 요청을 통해 서버의 리소스를 응답받습니다.

Simple Request

Simple Request는 예비 요청을 보내지 않고 바로 본 요청을 보내고, 응답 헤더의 Access-Controll-Allow-Origin 값을 확인하여 Cors 정책 위반 여부를 확인합니다.

이때 Simple Request를 사용하기 위해 다음과 같은 조건을 만족해야 합니다.

  1. 요청의 메소드는 GET, HEAD, POST 중 하나여야 한다.
  2. Accept, Accept-Language, Content-Language, Content-Type, DPR, Downlink,
    Save-Data, Viewport-Width, Width를 제외한 헤더를 사용하면 안 된다.
  3. 만약 Content-Type를 사용하는 경우에는 application/x-www-form-urlencoded,
    multipart/form-data, text/plain만 허용된다.

Simple Request는 웹 어플리케이션에서 사용하기에 해당 조건을 만족하기 어려워 Prefligh Request 방식을 주로 사용합니다.

Cors를 해결하는 방법

Cors를 해결하는 방법으로는 다음과 같은 방법이 있습니다.

@CrossOrigin 사용

가장 쉬운 방법으로 Controller 클래스 상단 위에 @CrossOrigin 어노테이션을 활용할 수 있습니다.

@CrossOrigin(originPatterns = "http://localhost:8080")
@RestController
public class Controller {

}

이 방법은 쉽지만 Cors를 적용할 대상이 많아지면 중복된 코드가 늘어날 수 있습니다. 따라서 전체 Controller 에게 적용할 수 있게 Config 파일을 활용할 수 있습니다.

WebMvcConfigure 활용

@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 를 제한할 수 있습니다.

Filter 활용

@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

profile
https://github.com/5tr1ker

0개의 댓글