2 분 소요

이 글을 참고했다.

클라이언트와 협업 도중 아래와 같은 에러를 만났다.
스크린샷 2023-03-04 오후 11 51 35

CORS란?

  • Cross-Origin Resource Sharing = 교차 출처 리소스 공유

CORS에서 Origin이란?

  • 웹 컨텐츠에서의 출처를 의미
  • 스킴 + 호스트 + 포트
  • 같은 출처를 가졌다 = 스킴, 호스트, 포트가 모두 일치한다

스크린샷 2023-03-08 오후 12 53 21

스킴(scheme)과 프로토콜(protocol)
엄밀히 말해서 protocol과 scheme은 다른 개념이다. 이 글에서는 http, https를 기준으로 설명하고 있기 때문에 스킴과 프로토콜을 혼용해서 사용한다.

  • 스킴 == 프로토콜 👉 http, https, ftp, …
  • 스킴 != 프로토콜 👉 file, magnet, mailto, …

CORS

한 웹 어플리케이션이 다른 출처에 존재하는 자원에 접근하고 싶다면? 접근 권한을 줄 수 있도록 브라우저에 알려주는 체제인 CORS를 이용해야 한다.
예를 들어 한 컴퓨터의 로컬에서 Spring Boot를 8080 포트로 띄우고, React를 3000 포트로 띄웠다고 가정하자.
이 둘은 포트가 다르므로 다른 출처임을 알 수 있다.
React가 Spring Boot의 API를 호출하려고 할 때, 출처가 다르므로 접근 권한이 없다며 CORS 에러가 발생한다.
스크린샷 2023-03-08 오후 1 00 07

제한된 교차 출처 HTTP 요청

그렇다면 그냥 접근 권한을 서로 열어두면 편하지 않을까?라는 생각이 들 수 있다.
브라우저의 교차 출처 HTTP 요청을 제한하는 건 보안을 위해서다.
서로 다른 출처를 가진 두 애플리케이션이 마음대로 서로를 접근할 수 있는건 매우 위험하다.
크롬에서 F12를 눌러 개발자 도구만 열어봐도 어떤 서버와 통신하고 어떤 정보를 주고 받는지 등등 여러 정보를 제재없이 열람할 수 있다.
다른 출처를 가진 애플리케이션에 접근 제한이 없다면 XSS나 CSRF 등을 통해 중요한 데이터를 빼가는게 매우 쉬워진다.

중요한 점은 이 CORS를 확인하는 로직이 서버가 아닌 브라우저에 구현되어있다는 점이다. 그러므로 Postman 같은 툴을 이용해 API 요청을 보낼 때는 CORS 에러가 발생하지 않는다.

CORS 에러 해결 방법

CORS가 어떻게 동작하는지 알아보며 에러를 해결해보자.

기본적으로 웹 클라이언트 어플리케이션은 다른 출처의 리소스를 요청할 때 HTTP 프로토콜을 사용한다.
이때 브라우저는 요청 헤더에 Origin이라는 필드에 출처를 함께 담아보낸다.
이후 서버가 이 요청에 대한 응답을 할 때 Access-Control-Allow-Origin 이라는 값에 이 리소스에 접근하는 것이 허용된 출처를 내려준다.
응답을 받은 브라우저는 브라우저가 보냈던 Origin과 서버에서 받아온 Access-Control-Allow-Origin 두 값을 비교한다. 그 후 이 응답이 유효한지 아닌지를 결정한다.

맨 위에 사진에 있는 클라이언트 쪽에서 받은 에러를 확인해보자.

No 'Access-Control-Allow-Origin' header is present on the requested resource.

문제는 Access-Control-Allow-Origin 값을 못 받았다는 점이다. 그렇다면 Spring은 이 부분을 어떻게 해결할 수 있을까?

@Configuration
public class WebConfig implements WebMvcConfigurer {
    public static final String ALLOWED_METHOD_NAMES = "GET,POST,HEAD,DELETE,PATCH,OPTIONS";
    @Override
    public void addCorsMappings(CorsRegistry registry) {
        registry.addMapping("/**")
                // .allowCredentials(true)
                .allowedOrigins(
                        "http://localhost:3000",
                )
                .allowedMethods(ALLOWED_METHOD_NAMES.split(","))
                .allowedHeaders("*");
    }
}
  • addMaping: 해당 설정을 적용할 API 범위 선택 (/** -> 전체 적용)
  • allowedOrigins: Origin을 허용할 범위 선택 (생략 시 *와 같은 의미로 전체 허용됨)
  • allowedMethods: 허용할 HTTP 메서드 선택
  • exposedHeader: 서버에서 반환할 헤더 지정
  • allowCredentials: 쿠키 요청을 허용한다 (다른 도메인 서버에 인증하는 경우에만 사용해야하며, true 설정시 보안상 이슈가 발생할 수 있다)

이 글을 참고해 허용 메소드를 지정하였다.
(불필요한 메소드까지 모두 허용하면 보안적으로 좋지 않다는 내용이다.)

만약 preflight 요청이 통과되지 않았다는 에러를 마주치게 된다면?

이 글의 하단 부분을 참고하자.



💛 개인 공부 기록용 블로그입니다. 👻

맨 위로 이동하기

태그:

카테고리:

업데이트: