첫 협업 프로젝트 당시 가장 많이 만났던 에러가 바로 CORS 에러였으며, 당시 기록해놓은 내용을 정리합니다.
서로 다른 origin 간에 데이터를 주고 받을 때 발생하는 문제
브라우저는 보안 상의 이유로 script에서 시작한 교차 출처 HTTP 요청을 제한한다. 만약 다른 사이트에서 세션을 요청해서 세션을 획득할 수 있다면, 악의적으로 내 세션을 탈취할 수 있기 때문에 브라우저에서는 이러한 요청을 막는 것이다.
여기서 origin이란
요청 URL의 schema, host, port를 말하며, 이 중 하나라도 다르다면 cross-origin이다.
CORS 요청(다른 origin에 자원을 요청)은 브라우저에 요청을 보낼 때 preflight 요청(사전 요청)을 먼저 보낸다. 이는 서버에 해당 요청에 대한 허가 여부를 확인하는 것이다.
preflight 요청은 OPTIONS 메서드
로 요청하며, 서버에 다음과 같은 정보들을 보낸다.
Access-Control-Request-Method: POST #실제 요청의 메서드
Access-Control-Request-Headers: auth, Content-Type #실제 요청의 추가 헤더
Access-Control-Request-Method
- 실제 요청에서 어떤 메서드를 사용할 것인지 서버에게 알린다Access-Control-Request-Headers
- 실제 요청에서 어떤 header를 사용할 것인지 서버에게 알린다Origin
- 요청 서버의 origin이후 이 요청을 받는 서버는 해당 요청에 대해 허용하는 값들을 응답에 넣어 반환한다.
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 에러를 발생시킨다.
위와 같이 정상적으로 preflight 요청 -> 실제 요청이 이루어지려면 프론트/백엔드 각각 CORS 설정을 해줘야 한다.
credentials 설정에 include 값을 준다 (Axios의 경우 withCredentials : true)
다음과 같은 값들을 설정해줘야 한다. (CORS 설정)
Access-Control-Allow-Origin
Access-Control-Allow-Methods
Access-Control-Allow-Headers
이때, 와일드 카드를 사용하기 보다는 직접 명시해주는 것이 보안상으로도 안전하고, 에러 발생을 방지할 수 있다.
@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 헤더가 없기 때문이다.
if (HttpMethod.OPTIONS.matches(request.getMethod())) {
return true;
}
개발자 도구에서 쿠키를 확인할 수 없다면, 쿠키의 domain 설정이 서버 domain으로 되어있는지 확인해본다.
쿠키가 전달되지 않는 문제에 대해서는 다음 글에 정리해놓았다.
쿠키 문제 해결
HttpOnly 옵션이 체크되어 있으면 Console로 볼 수 없다. 쿠키는 살아있지만 자바스크립트에서는 접근 할 수 없기 때문에, HttpOnly 옵션을 false로 설정해본다.
HTML은 기본적으로 Cross-Origin 정책을 따르기 때문에, HTTP 요청에 대해서 Cross-Origin 요청이 가능하다. 그러나 script 태그 내에 있는 HTTP요청에 대해서는 기본적으로 Same-Origin 정책을 따르고 있기 때문에 Cross-Origin 요청이 불가능하다.