💡 해당 내용은 이전 블로그(Tistory)에서 이관한 글입니다.
Access to XMLHttpRequest at '주소A' from origin '주소B' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource.
CORS는 SOP를 피하기(허용) 위한 수단이다!
대부분의 웹 브라우저는 SOP라는 보안 정책을 준수합니다
SOP는 한 Origin(내가 현재 접속해 있는 사이트)에서 로드된 문서나 스크립트가 다른 Origin(다른 사이트)에서 가져온 리소스와 상호 작용하는 것을 제한하는 보안 방식입니다
말 그대로 같은 출처에서만 리소스를 공유
할 수 있다 라는 규칙을 가진 정책입니다
용어 | 조건 |
---|---|
Same Origin (동일 출처) | Protocol, Host, Port 모두 일치 |
Cross Origin (다른 출처, 교차 출처) | Protocol, Host, Port 중 하나라도 다를 시 |
이미지 출처: [10분 테코톡] 나붐의 CORS
💡 IE(Internet Explorer)에서는 SOP(동일 출처 정책) 기준에 두 가지 예외사항이 있습니다
하지만 IE는 거의 사용되지 않는 브라우저이므로 해당 예외사항은 알고만 있으면 될 것 같습니다
다른 출처의 선택한 자원에 접근할 수 있는 권한을 부여
하도록 브라우저에 알려주는 체제입니다Origin
이라는 필드를 추가합니다Origin
: 요청을 보낸 출처 (ex. https://github.com/giibeom)access-control-allow-origin
라는 필드를 내려줍니다access-control-allow-origin
: 해당 리소스를 접근할 수 있게 허용된 출처 (ex. access-control-allow-origin: *)*
은 모든 출처를 허용한다는 와일드카드 입니다OPTIONS
라는 메서드를 사용합니다Origin
: 요청 출처Access-Control-Request-Method
: 실제 요청의 메서드Access-Control-Request-Headers
: 실제 요청의 추가 헤더Access-Control-Allow-Origin
: 서버 측 허가 출처Access-Control-Allow-Methods
: 서버 측 허가 메서드Access-Control-Allow-Headers
: 서버 측 허가 헤더Access-Control-Max-Age
: Preflight 응답 캐시 기간Access-Control-Max-Age는
효율적으로 리소스를 관리해주기 위해 필요한 값입니다Access-Control-Allow-Origin
이 존재하지 않습니다아래는 www.google.com 에서 제 블로그로 리소스 요청을 전송하였고, 브라우저가 자동으로 진행한 예비 요청(Preflight Request)의 예시입니다
예비요청 응답 코드가 200으로 떨어지는 이유는 CORS가 발생하는 시점은 이 다음에 일어나고 실제 예비 요청 자체는 성공하기 때문입니다
예시 내 응답 헤더에 있는 Access-Control-Allow-Origin
과 요청 헤더에 있는 Origin
이 서로 다르기 때문에 아래 콘솔창과 같이 CORS 에러가 발생하는 것을 볼 수 있습니다
Access-Control-Allow-Origin
값을 반환해줍니다Access-Control-Allow-Origin
와 Request 시 보냈던 Origin
을 판별하여 응답의 여부를 결정합니다하지만 단순 요청 (Simple Request) 방식이 일어나는 상황은 아래와 같은 조건을 “모두” 충족할 때 일어납니다
GET
, HEAD
, POST
중 하나여야 합니다사실상 3가지의 조건이 모두 충족하기란 쉽지 않습니다 (2, 3 때문에)
application/json
이 많이 사용됩니다Authorization
를 많이 세팅합니다해당 방식은 클라이언트와 서버 양쪽에서의 설정이 필요합니다
credentials
: 요청에 인증과 관련된 정보를 담을 수 있게 하는 옵션옵션 값 | 동작 설명 |
---|---|
same-origin (기본 값) | 같은 출처 간 요청에만 인증 정보를 담을 수 있습니다 |
include | 모든 요청에 인증 정보를 담을 수 있습니다 |
omit | 모든 요청에 인증 정보를 담지 않습니다 |
💡 예시를 들어보자
fetch('서버주소', { credentials: 'include', });
credentials
옵션에서 omit을 제외한 나머지 옵션을 설정하게 되면 브라우저는 기존에 확인했던 Access-Control-Allow-Origin
이외에도 추가적인 검사를 진행합니다credentials
설정을 하였다면 두가지의 설정을 하여야 합니다Access-Control-Allow-Credentials: true
를 반환해야 합니다Access-Control-Allow-Origin: *
을 설정할 수 없습니다Access-Control-Allow-Origin
설정해주기 (정석)// 전역적으로 CORS 설정을 할 수 있는 방법
@Configuration
public class CorsFilter implements WebMvcConfigurer { // WebMvcConfigurer 구현
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/api") // 적용할 path 패턴을 입력
.allowedOrigins("http://localhost:8081") // 허가할 출처들을 기입
.allowedMethods( // 허가할 메서드를 기입
HttpMethod.GET.name,
HttpMethod.POST.name,
HttpMethod.PUT.name,
HttpMethod.DELETE.name
);
}
}
@RestController
@RequiredArgsConstructor
@RequestMapping("/api")
@CrossOrigin(origins = {"http://localhost:8080", "http://localhost:9090"}, allowCredentials = "true")
public class MemberController {
@GetMapping("/hello")
public String Hello() {
return "hello";
}
}
프론트 프록시 서버에서 백엔드 서버간의 출처를 조율해줍니다
브라우저와 프론트 프록시 서버는 출처가 동일하므로 CORS Error가 발생하지 않습니다
이미지 출처: [10분 테코톡] 나붐의 CORS
근본적인 해결이 될 때까지 임시적으로 사용하는 방법입니다! → 광고 아닙니다.....
방법은 아래와 같습니다
실무에서 CORS 에러를 겪고 나서 제대로 정리하고 싶어서 공부해보았습니다
처음에는 CORS 에러가 "CORS" 때문에 에러가 난다고 생각했지만, 알고 보니 SOP 정책 때문에 에러가 나는 것이고 CORS는 이 SOP 정책을 합법적(?)으로 사용하게 도와주는 방법이더군요 :)
보통 실무에서는 Spring에 이미 CORS 허용 설정이 되어있겠지만, 제 경우에는 Spring 모듈과의 통신이 아닌 contents 서버와의 자원(파일) 공유 중 CORS 에러를 맞닥뜨린 경험이 있어 Port를 맞춰줌으로써 문제를 해결했었던 기억이 있네요!