내가 만난 CORS(Cross-Origin Resource Sharing) 에러 관련 총 정리

bagt13·2022년 12월 16일
2

CS

목록 보기
7/12
post-thumbnail

첫 협업 프로젝트 당시 가장 많이 만났던 에러가 바로 CORS 에러였으며, 당시 기록해놓은 내용을 정리합니다.


CORS (Cross-Origin Resource Sharing)

서로 다른 origin 간에 데이터를 주고 받을 때 발생하는 문제

브라우저는 보안 상의 이유로 script에서 시작한 교차 출처 HTTP 요청을 제한한다. 만약 다른 사이트에서 세션을 요청해서 세션을 획득할 수 있다면, 악의적으로 내 세션을 탈취할 수 있기 때문에 브라우저에서는 이러한 요청을 막는 것이다.

여기서 origin이란 요청 URL의 schema, host, port를 말하며, 이 중 하나라도 다르다면 cross-origin이다.


preflight 요청

CORS 요청(다른 origin에 자원을 요청)은 브라우저에 요청을 보낼 때 preflight 요청(사전 요청)을 먼저 보낸다. 이는 서버에 해당 요청에 대한 허가 여부를 확인하는 것이다.

preflight 요청은 OPTIONS 메서드로 요청하며, 서버에 다음과 같은 정보들을 보낸다.

preflight request 예시

Access-Control-Request-Method: POST	#실제 요청의 메서드
Access-Control-Request-Headers: auth, Content-Type #실제 요청의 추가 헤더
  • Access-Control-Request-Method - 실제 요청에서 어떤 메서드를 사용할 것인지 서버에게 알린다
  • Access-Control-Request-Headers - 실제 요청에서 어떤 header를 사용할 것인지 서버에게 알린다
  • Origin - 요청 서버의 origin

이후 이 요청을 받는 서버는 해당 요청에 대해 허용하는 값들을 응답에 넣어 반환한다.

preflight response 예시

Access-Control-Allow-Origin: http://example.com	#허용 출처(domain)
Access-Control-Allow-Methods: POST, GET, OPTIONS #허용 메서드
Access-Control-Allow-Headers: X-PINGOTHER, Content-Type	#허용 header
Access-Control-Max-Age: 86400 #Prefilght 응답 캐시 기간

브라우저는 요청과 응답을 비교해서 CORS 정책에 부합하는지 확인한다. 부합한다면 실제 요청을 보내고, 아니라면 본 요청을 보내지 않고 CORS 에러를 발생시킨다.



프론트 / 백엔드 CORS 설정

위와 같이 정상적으로 preflight 요청 -> 실제 요청이 이루어지려면 프론트/백엔드 각각 CORS 설정을 해줘야 한다.

프론트엔드에서 해야할 일

credentials 설정에 include 값을 준다 (Axios의 경우 withCredentials : true)

  • credentials 설정의 기본값은 same-origin인데, include 설정을 하면 cross-origin 요청에 인증 정보를 담겠다는 뜻이다.

백엔드에서 해야할 일

다음과 같은 값들을 설정해줘야 한다. (CORS 설정)

  • Access-Control-Allow-Origin
  • Access-Control-Allow-Methods
  • Access-Control-Allow-Headers

이때, 와일드 카드를 사용하기 보다는 직접 명시해주는 것이 보안상으로도 안전하고, 에러 발생을 방지할 수 있다.

Spring CORS 설정 예시

@Override
public void addCorsMappings(CorsRegistry registry) {
	registry.addMapping("/**")
    	.allowedOrigins("https://frontend.com")
        .allowedMethods(
        		HttpMethod.GET.name,
				HttpMethod.POST.name,
				HttpMethod.PUT.name,
				HttpMethod.DELETE.name)
        .allowedHeaders("auth")
        .exposedHeaders("auth")
 		.allowCredentials(true)
}

만일 특정 header를 포함한 response를 하고 싶다면, 위와 같이 exposedHeaders를 사용하면 된다.

쿠키를 전달하는 경우, 응답의 Set-cookie 헤더의 쿠키 설정을 확인한다.

  • samesite

    쿠키의 samesite 속성에는 None, Lax, Strict가 있는데, Lax가 기본값이다.

  • secure

    sameSite 옵션을 none으로 줄 경우, 보안을 위해 필수적으로 secure 옵션을 true로 설정해줘야 한다. 하지만 쿠키에 secure 옵션을 줄 경우 https 통신일 때만 서버로 쿠키를 전송할 수 있기 때문에 https가 필수적이다.

인터셉터/필터 등 검증 로직이 있다면 별도의 설정이 필요하다

예를 들어, 토큰 검증 인터셉터가 존재하는 경우, preflight 요청에 대한 별도의 처리가 필요하다. preflight에는 Authorization 헤더가 없기 때문이다.


  • 인터셉터 preflight 요청 처리 예시
if (HttpMethod.OPTIONS.matches(request.getMethod())) {
	return true;
}


쿠키 domain 설정

개발자 도구에서 쿠키를 확인할 수 없다면, 쿠키의 domain 설정이 서버 domain으로 되어있는지 확인해본다.

쿠키가 전달되지 않는 문제에 대해서는 다음 글에 정리해놓았다.
쿠키 문제 해결


HttpOnly 옵션

HttpOnly 옵션이 체크되어 있으면 Console로 볼 수 없다. 쿠키는 살아있지만 자바스크립트에서는 접근 할 수 없기 때문에, HttpOnly 옵션을 false로 설정해본다.

HTML은 cross-origin 요청이 가능하다

HTML은 기본적으로 Cross-Origin 정책을 따르기 때문에, HTTP 요청에 대해서 Cross-Origin 요청이 가능하다. 그러나 script 태그 내에 있는 HTTP요청에 대해서는 기본적으로 Same-Origin 정책을 따르고 있기 때문에 Cross-Origin 요청이 불가능하다.

profile
주니어 백엔드 개발자입니다😄

0개의 댓글