CORS(Cross-Origin-Resource Sharing)

박재언·2023년 12월 26일
0

사진 출처: https://thehackerstuff.com/what-is-cors-a-detailed-comprehensive-analysis-of-cors/

CORS란?

CORS는 교차 출처 리소스 공유(Cross-Origin-Resource Sharing)의 약어로 한 도메인의 리소스를 다른 도메인과 상호 작용하는 것을 말한다.

그렇다면 CROS 오류가 발생하는 이유는 무엇일까?

그 이유는 기존의 정책이 동일 출처 정책(Same-Origin policy) 때문이다.

동일 출처 정책이란?

어떤 출처에서 불러온 문서나 스크립트가 다른 출처에서 가져온 리소스와 상호작용하는 것을 제한하는 보안방식이다. 이는 잠재적으로 해로울 수 있는 문서를 분리해서 공격받을 수 있는 경로를 줄이기 위해 사용된다.
따라서 웹페이지는 자신의 출처와 동일한 출처를 가지는 리소스에 대해서만 API 요청을 보낼 수 있고 이러한 정책을 우회하기 위해서는 JSONP(JSON with Padding) 이나 CORS같은 메커니즘을 사용하여 우회해야 한다.

출처(Orign)


URL은 위와 같은 구조로 이뤄져 있는데 이 때 포트번호가 명시된것이 아니라면 http는 80번 https는 443번 포트로 지정되어 생략되있다.
출처는 Protocol, Host, 포트번호를 의미하며 3개가 다 동일해야 같은 출처이다.

CORS 동작 원리

Cors의 동작 방식은 3개가 있다.

  • Preflight Request
  • Credential Request
  • Simple Request

Preflight Request

서버와 브라우저가 통신하기 위해 예비 요청을 보내고 확인 응답을 받은후 실제 요청을 받는 방식이다.
Preflight는 실제

이때 예비 요청은 OPTIONS 메서드로 예비요청을 보낸다.
이후 서버는 예비 요청에 대한 응답에 Access-Control-Allow-Origin을 헤더에 포함하여 보내고 브라우저는 Access-Control-Allow-Origin을 헤더에서 확인하여 CORS 동작 수행 여부를 판단한다.

예비 요청을 수행하는 조건은 다음과 같다.

  • Content-TypeGet, Head, Post 요청
    • application/x-www-form-urlencoded
    • multipart/form-data
    • text/plain

Credential Request

인증된 요청을 사용하는 방법으로 통신의 보안을 강화하고 싶을 때 사용 하는 방법이다.
클라이언트가 서버에 요청할 때 자격 인증 정보(Credential)을 담아서 요청하며 여기서 말하는 자격 인증 정보는 세션 ID가 저장되어 있는 쿠키나 Authorization 헤더에 설정하는 토큰 값 등을 의미한다.
프론트엔드와 백엔드 모드 해당 설정을 해야 하며 Credentail Request는 prefilght request가 선행되어야 한다.

백엔드에서 할 일

  • 응답 헤더의 Access-Control-Allow-Credentials 항목을 true로 설정
  • 응답 헤더의 Access-Control-Allow-Origin*로 설정 금지
  • 응답 헤더의 Access-Control-Allow-Methods*로 설정 금지
  • 응답 헤더의 Access-Control-Allow-Headers*로 설정 금지

Simple Request

특정 조건이 만족되면 Preflight Request이 생략하고 요청을 보내는 것이다.

특정 조건은 다음과 같다.

  • Get, Head 요청
  • Content-Type 헤더가 다음과 같은 POST 요청
    • application/x-www-form-urlencoded
    • multipart/form-data
    • text/plain
  • Accept, Accept-Language, Content-Language, Content-Type, DPR, Downlink, Save-Data, Viewport-Width, Width 를 제외한 헤더를 사용하면 안된다.

서버에서 CORS에러 해결법

@CrossOrigin 어노테이션 사용

  • @CrossOrigin 을 원하는 메서드나 컨트롤러에 붙여주면 모든 도메인에 대해 접근이 허용된다.
    또한 @CrossOrigin(origins = "http://domain.com)과 같이 orgin 프로퍼티를 사용하면 특정 도메인만 허용하는 것도 가능하다.
    이의 단점으로는 필요한 곳마다 사용자가 일일이 작성해 줘야된다는 단점이 있다.

WebMvcConfigurer에서 설정

  • Spring MVC 내에서WebMvcConfigurer 를 추가한다.
    아래의 코드 같은경우 @SpringBootApplication 어노테이션이 붙어있는 파일에 WebMvcConfigurer을 Bean으로 등록해줬다
@SpringBootApplication
public class TestApplication {

	public static void main(String[] args) {
		SpringApplication.run(TestApplication.class, args);
	}
    
	@Bean
	public WebMvcConfigurer corsConfigurer() {
		return new WebMvcConfigurer() {
			@Override
			public void addCorsMappings(CorsRegistry registry) {
				registry.addMapping("/**")
						.allowedOrigins("*")
						.allowedMethods("*")
                        .allowedHeaders("*")
						.allowCredentials(true);
                        .maxAge(1500)

			}
		};
	}
}
  • 메서드 정리

    • addMapping : 프로그램에서 제공하는 URL
    • allowedOrigins : 요청을 허용할 출처
    • allowedHeaders : 허용할 헤더
    • allowedMethods : 허용할 메서드
    • allowCredentials : 쿠키 허용에 대한 응답(true, false로 작성)
    • maxAge : prefilght 요청에 대한 응답을 브라우저에서 캐싱하는 시간

CorsFilter 생성

public class CORSFilter implements Filter {
	public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException { 
		HttpServletResponse response = (HttpServletResponse) res; 
		response.setHeader("Access-Control-Allow-Origin", "*"); 
		response.setHeader("Access-Control-Allow-Methods", "POST, GET, DELETE, PUT"); 
		response.setHeader("Access-Control-Max-Age", "3600"); 
		response.setHeader("Access-Control-Allow-Headers", "x-requested-with, origin, content-type, accept"); 
		chain.doFilter(req, res); 
		} 
	public void init(FilterConfig filterConfig) {} 
	public void destroy() {}
}

해당 Filter를 작성하고 등록해주면 된다.
이 때 주의할 점은 Authentication Filter 보다 앞단계에서 Filter가 진행되어야 한다.

  • 메서드 정리

    • response.header : 응답하는 헤더 설정
    • Access-Control-Allow-Origin: 요청을 허용할 출처
    • Access-Control-Allow-Methods: 허용할 메서드
    • Access-Control-Allow-Headers: 허용할 헤더 이름
    • Access-Control-Max-Age: 쿠키 허용에 대한 응답

SecurityConfig에서 설정

    @Bean
    public CorsConfigurationSource corsConfigurationSource() {
        CorsConfiguration configuration = new CorsConfiguration();
        configuration.setAllowedOrigins(Arrays.asList("https://example.com"));
        configuration.addAllowedMethod("*");
        configuration.addAllowedHeader("*");
        configuration.setAllowCredentials(true);

        configuration.addExposedHeader("Authorization");
        configuration.addExposedHeader("Authorization-Refresh");

        UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
        source.registerCorsConfiguration("/**", configuration);
        return source;
    }

Spring Security를 사용하는 경우 SecurityConfig에서 Bean을 등록해주는 것으로 해결할수 있다.

CorsConfigurationSource의 내부 로직

	public void setAllowedOrigins(@Nullable List<String> origins) {
		this.allowedOrigins = (origins == null ? null :
				origins.stream().filter(Objects::nonNull).map(this::trimTrailingSlash).collect(Collectors.toList()));
	}
    
    public void addAllowedOrigin(@Nullable String origin) {
		if (origin == null) {
			return;
		}
		if (this.allowedOrigins == null) {
			this.allowedOrigins = new ArrayList<>(4);
		}
		else if (this.allowedOrigins == DEFAULT_PERMIT_ALL && CollectionUtils.isEmpty(this.allowedOriginPatterns)) {
			setAllowedOrigins(DEFAULT_PERMIT_ALL);
		}
		origin = trimTrailingSlash(origin);
		this.allowedOrigins.add(origin);
	}

위와 같이 CorsConfigurationSourceset... 메서드는 List형식으로 값을 읽어오고 *가 적용되지 않고
add...식의 메서드는 *가 적용되지만 한 개의 데이터만 입력받는다.

0개의 댓글