CORS에 대해 알아보기
요청 방식에 따른 CORS 발생 여부
1) Cross-Origin 정책을 지원하는 경우
<img>
<video>
<script>
<link>
등<script>
에서type="module"
사용 시에는 제외- 다른 사이트의 리소스에 접근하는 것이 가능!
2) 기본적으로 Same-Origin 정책을 따르는 경우
- XMLHttpRequest, Fetch API 스크립트, 웹 폰트 CSS 파일 내
@font-face
에서 다른 도메인의 폰트 사용 시 - 자바스크립트에서의 요청은 기본적으로 서로 다른 도메인에 대한 요청을 보안상 제한한다.
- 브라우저는 기본으로 하나의 서버 연결만 허용되도록 설정되어 있기 때문이다.
CORS 에러 이해하기
-
CORS (Cross-Origin Resource Sharing, 교차 출처 리소스 공유 정책)
Access to XMLHttpRequest at 'http://localhost:8080/login' from origin 'http://localhost:3000' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource. If an opaque response serves your needs, set the request’s mode to ‘no-cors’ to fetch the resource with CORS disabled.
http://localhost:8080/login'에서 'http://localhost:3000' 출처로 가져올 수 있는 액세스가 CORS 정책에 의해 차단되었습니다. 요청된 리소스에 'Access-Control-Allow-Origin' 헤더가 없습니다. 불투명한 응답이 필요에 부합하는 경우 요청 모드를 'no-cors'로 설정하여 CORS를 비활성화한 상태에서 리소스를 가져오십시오.
Origin이란?
- 위의 사진처럼
Origin
은Protocol
Host
Port
의 조합이다.
Same-Origin Policy (동일 출처 정책)
- SOP는 동일한 출처에서만 리소스를 공유할 수 있다는 정책이다.
- 즉, 다른 출처(Cross-Origin) 서버에 있는 이미지나 영상과는 상호작용이 불가능하다.
- URL의 구성 요소 중
Protocol
Host
Port
이 3가지만 동일하다면 동일 출처로 판단한다.
동일 출처 정책이 필요한 이유
-
동일 출처 제약이 없다면 해커가 CSRF(Cross-Site Request Forgery)나 XSS(Cross-Site Scripting) 등의 방법을 이용해서 개인 정보를 가로챌 수 있다.
CSRF (Cross-Site Request Forgery)
공격자가 사용자를 속여 사용자가 모르는 사이에 권한이 있는 상태에서 악의적인 요청을 보내는 공격입니다. 예를 들어, 사용자가 로그인된 상태에서 공격자가 조작한 링크를 클릭하면, 해당 사용자의 권한을 이용해 은행 이체나 정보 수정 같은 요청이 실행될 수 있습니다.XSS (Cross-Site Scripting)
웹 애플리케이션에서 입력된 사용자 데이터를 적절히 검증하지 않아, 악성 스크립트가 웹 페이지에 삽입되는 공격입니다. 이 스크립트는 사용자의 브라우저에서 실행되어 쿠키, 세션 토큰 등의 민감한 정보를 훔치거나, 사용자가 방문하는 페이지에 임의의 동작을 수행하게 만들 수 있습니다.
출처 비교와 차단은 브라우저가 수행한다
- 출처를 비교하는 로직은 서버가 아닌 브라우저에 구현된 스펙이다.
- 따라서 응답 데이터는 멀쩡하지만 브라우저단에서 응답을 차단하는 것이다.
- 그래서 브라우저에서는 에러가 뜨지만, 서버 쪽에서는 정상적으로 응답했다고 뜨기 때문에 난항을 겪는 것이다!
교차 출처 리소스 공유 (Cross-Origin Resource Sharing)
- CORS는 다른 출처의 리소스 공유에 대한 허용/비허용 정책이다.
- 개발을 하다 보면 어쩔 수 없이 다른 출처 간의 상호작용을 해야 하는 케이스도 있으며, 또한 실무적으로 다른 회사의 서버 API를 이용해야 하는 상황도 존재한다.
-
따라서 이와 같은 예외 사항을 두기 위해 CORS 정책을 허용하는 리소스에 한해 다른 출처라도
받아들인다는 것이다. - 사실 CORS는 다른 출처의 리소스를 얻기 위한 해결 방안이다.
-
- SOP 정책을 위반해도 CORS 정책에 따르면 다른 출처의 리소스라도 허용한다는 뜻이다.
브라우저의 CORS 기본 동작
-
클라이언트에서 HTTP 요청의 헤더에 Origin을 담아 전달
-
서버는 응답헤더에
Access-Control-Allow-Origin
을 담아 클라이언트로 전달- 해당 필드의 값으로 '이 리소스를 접근하는 것이 허용된 출처 URL'을 내려 보낸다.
-
클라이언트에서 Origin과 서버가 보내 준
Access-Control-Allow-Origin
을 비교한다.- 이후 브라우저는 자신이 보낸 요청의 Origin과 서버가 보낸 응답의
Access-Control-Allow-Origin
을 비교한다. - 만약 유효하지 않다면 응답을 사용하지 않고 버린다. (CORS 에러)
- 이후 브라우저는 자신이 보낸 요청의 Origin과 서버가 보낸 응답의
결국 CORS를 해결하려면 서버의 허용이 필요하다
서버에서
Access-Control-Allow-Origin
헤더에 허용할 출처를 기재해서 클라이언트에 응답해야 한다!
CORS 작동 방식 3가지 시나리오
1) Preflight Request (예비 요청)
- 브라우저는 요청을 보낼 때 예비 요청을 먼저 보내고, 서버와 잘 통신되는지 확인한 후 본 요청을 보낸다.
- 즉, 예비 요청의 역할은 본 요청을 보내기 전에 브라우저 스스로 안전한 요청인지 미리 확인하는 것이다.
- 이 예비 요청은 HTTP 메서드 중
OPTION
을 사용한다. - 하지만 예비 요청을 보내게 되면 실제 요청에 걸리는 시간이 늘어나게 되어 애플리케이션 성능에 큰 영향을 미칠 수 있다.
- 브라우저 캐시를 이용해
Access-Control-Max-Age
헤더에 캐시될 시간을 명시해 주면 Preflight 요청을 캐싱시켜 최적화할 수 있다.
- 브라우저 캐시를 이용해
2) Simple Request (단순 요청)
- 예비 요청을 생략하고 서버에 직행으로 본 요청을 본내 후, 서버가 이에 대한 응답 헤더에
Access-Control-Allow-Origin
헤더를 보내면, 브라우저가 CORS 정책 위반 여부를 검사하는 방식이다. - 다만 아래 3가지 경우를 만족할 때에만 예비요청을 생략할 수 있다.
- 요청의 메서드는
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중 하나여야한다. 아닐 경우 예비 요청으로 동작된다.
- 요청의 메서드는
- 위의 조건을 만족하기 까다롭기 때문에 대부분의 API 요청은 그냥 예비요청으로 이루어진다고 이해하면 된다.
3) Credential Request (인증된 요청)
- 인증된 요청은 클라이언트에서 서버에게 자격 인증 정보(Credential)를 실어 요청할 때 사용되는 요청이다.
자격 인증 정보
란 세션ID가 저장되어 있는쿠키
또는Authorization 헤더
에 설정하는 토큰 값 등을 일컫는다.
- 클라이언트에서 인증 정보를 보내도록 설정
credential
옵션을 통해 인증과 관련된 정보를 담을 수 있게 한다.same-origin (기본값)
같은 출처 간 요청에만 인증 정보를 담을 수 있다.include
모든 요청에 인증 정보를 담을 수 있다.omit
모든 요청에 인증 정보를 담지 않는다.
- 이러한 별도의 설정을 하지 않으면 쿠키 등의 인증 정보는 자동으로 서버에게 전송되지 않는다.
// fetch 메서드
fetch('https://example.com:1234/users/login', {
method: 'POST',
credentials: 'include', // 클라이언트와 서버가 통신할때 쿠키와 같은 인증 정보 값을 공유하겠다는 설정
body: JSON.stringify({
userId: 1,
}),
});
// axios 라이브러리
axios.post(
'https://example.com:1234/users/login',
{ profile: { username: username, password: password } },
{ withCredentials: true } // 클라이언트와 서버가 통신할때 쿠키와 같은 인증 정보 값을 공유하겠다는 설정
);
- 서버에서 인증된 요청에 대한 헤더 설정
- 서버도 인증된 요청에 대해 일반적인 CORS 요청과는 다르게 대응해야 한다.
- 응답 헤더의
Access-Control-Allow-Credentials
항목을 true로 설정해야 한다. - 응답 헤더의
Access-Control-Allow-Origin
의 값에 와일드카드 문자(*)는 사용할 수 없다. - 응답 헤더의
Access-Control-Allow-Methods
의 값에 와일드카드 문자(*)는 사용할 수 없다. - 응답 헤더의
Access-Control-Allow-Headers
의 값에 와일드카드 문자(*)는 사용할 수 없다.
- 응답 헤더의
- 이를 어길 경우 또 다른 종류의 CORS 에러가 발생한다.