CORS 등장 배경

  • 과거 웹사이트를 만들 때, 대부분 하나의 서버에서 브라우저의 모든 요청을 처리
  • 점점 웹사이트가 발전하여서 웹사이트에서 할수있는 일이 많아졌음(ex 날씨 api 를 이용하여 기능을 넣을 필요가 있는 경우)
  • 웹브라우저와 날씨 api 도메인이 서로 달라서 요청을 주고받을 수 없음 → 서로 도메인이 달라 과거에는 해당 문제를 해결하기 위한 JSONP 방식 사용
  • JSONP 방식은 <script> 요소를 사용하여 외부 출처로부터 조회된 내용을 실행하는 것이 허용 → 조회된 내용을 서버에 데이터로 반환하는 용도로 사용
  • JSONP 방식 예제 → CORS가 나오기 전까지 사용 되던 방식으로 보안상 이슈로 사용 X

    # 함수이름을 넣어 요청
    const script = document.createElement("script")
    script.src = "ingg.com/test.json?callback=parseResponse"
    
    # 서버에서는 함수이름을 넣고 매개변수로 데이터를 넣어서 반환
    parseResponse({
    "id" : "123",
    "name" : "ingg",
    })
    
    function parseResponse(data){
      // ...
    }

CORS란

  • CORS는 다른 출처의 리소스가 필요한 경우, SOP를 우회하기 위한 여러가지 방법 중 가장 권장되는 방법
  • 교차 출처 리소스 공유(Cross-Origin Resource Sharing, CORS)는 추가 HTTP 헤더를 사용하여, 한 출처에서 실행 중인 웹 애플리케이션이 다른 출처의 선택한 자원에 접근할 수 있는 권한을 부여하도록 브라우저에 알려주는 체제이다. - MDN


SOP 정책

SOP 정책이란

  • Same Origin(동일 출처 정책)은 scheme, host, port가 모두 같을 때를 의미
  • SOP(Same-Origin Policy)란 같은 Origin에서만 리소스를 공유할 수 있다는 규칙
  • Same Origin Policy(동일 출처 정책)은 어떤 출처에서 불러온 문서나 스크립트가 다른 출처에서 가져온 리소스와 상호작용하는 것을 제한하는 중요한 보안 방식
  • Same Origin(동일 출처 정책)은 잠재적으로 해로울 수 있는 문서를 분리함으로써 공격받을 수 있는 경로를 줄여줌
  • 브라우저에서 다른 서버에 요청할 경우 SOP 정책 영향을 받음
  • 브라우저를 거치지 않고 서버 간 통신을 할 때는 SOP 정책이 적용 X

SOP 정책이 존재이유

  • 다른 출처의 어플리케이션이 서로 통신하는 것에 대해 제약
  • 악의를 가진 사용자가 소스 코드를 보고 CSRF(Cross-Site Request Forgery) 나 XSS(Cross-Site Scripting)와 같은 방법을 사용하여 정보를 탈취를 하는 것을 막음


CORS 동작 방식

  • CORS 접근 제어 시나리오 (교차 출처 리소스 공유가 동작하는 방식)에는 크게 세가지
    1. 프리플라이트 요청 (Preflight Request)
    2. 단순 요청(Simple Request)
    3. 인증정보 포함 요청(Credentialed Request)

1. 프리플라이트 요청 (Preflight Request)

  • Preflight Request는 요청을 예비 요청과 본 요청으로 나눔
  • OPTIONS 메서드를 통해 다른 도메인의 리소스에 요청이 가능한지 (실제 요청이 전송하기에 안전한지) 확인 작업을 하고, 요청이 가능하다면 실제 요청을 보냄
  • Cross-origin 요청은 유저 데이터에 영향을 줄 수 있기 때문에 Preflight 요청
  • Preflight Request
    • OPTIONS 요청과 함께 두 개의 다른 요청 헤더가 전송
    • Preflight Request가 완료되면 실제 요청을 전송
    • 첫 번째 행은 실제 요청을 전송할 때 POST 메서드로 전송된다는 것을 서버에 알려줌
    • 두 번째 행은 실제 요청을 전송 할 때 X-PINGOTHER 와 Content-Type 사용자 정의 헤더와 함께 전송된다는 것을 서버에 알려줌
      Access-Control-Request-Method: POST                                     # 실제요청의 메서드
      Access-Control-Request-Headers: X-PINGOTHER, Content-Type  # 실제요청의 추가헤더
  • Preflight Response
    • 서버가 메서드와 헤더를 받을 수 있음을 알려줌
    • 첫 번째 행은 응답할 때 서버측 허가되는 서버 도메인(http://foo.example)를 클라이언트에게 알려줌
    • 두 번째 행은 응답할 때 서버측 허가되는 POST, GET, OPTIONS 메서드를 클라이언트에게 알려줌
    • 세 번째 행은 응답할 때 서버측 허가되는 X-PINGOTHER 와 Content-Type 사용자 정의 헤더를 클라이언트에게 알려줌
    • 마지막 행 은 preflight request에 대한 응답을 캐시할 수 있는 시간(초)를 클라이언트에게 알려줌 → 86400초 (=24시간)
    • Preflight를 보내면 사전, 실제 요청 두번이 매번 왔다갔다 하므로 브라우저가 캐싱을 해두고 똑같은 요청을 보낼 때에는, 사전 요청을 보내지 않고 바로 본 요청을 보냄
      Access-Control-Allow-Origin: http://foo.example                    # 서버측 허가출처
      Access-Control-Allow-Methods: POST, GET, OPTIONS              # 허가 메서드
      Access-Control-Allow-Headers: X-PINGOTHER, Content-Type   # 서버측 허가헤더
      Access-Control-Max-Age: 86400                                           # Prefilght 응답 캐시기간

2. 단순 요청(Simple Request)

  • Simple Request는 Preflight Request와 다르게 요청을 보내면서 즉시 cross origin인지 확인
  • Simple Request는 아래 조건을 모두 충족해야함
    1. 메서드는 GET POST HEAD 중 하나
    2. 헤더는 Accept, Accept-Language, Content-Language, Content-Type만 허용
    3. Content-Type 헤더는 다음의 값들만 허용
      • application/x-www-form-urlencoded
      • multipart/form-data
      • text/plain


3. 인증정보 포함 요청(Credentialed Request)

  • 인증 관련 헤더를 포함할 때 사용하는 요청
  • 브라우저가 제공하는 비동기 리소스 요청 API인 XMLHttpRequest 객체나 fetch API는 별도의 옵션 없이 브라우저의 쿠키 정보나 인증과 관련된 헤더를 기본적으로 요청에 담지 않으므로, credentials 옵션을 변경하지 않고서는 cookie를 주고 받을 수 없음
  • 인증정보 포함 요청의 세가지 옵션
    1. omit → 절대로 cookie들을 전송하거나 받지 않음
    2. same-origin → 동일 출처(same origin)이라면, user credentials (cookies, basic http auth 등..)을 전송 (default 값)
    3. include → cross-origin 호출이라 할지라도 언제나 user credentials (cookies, basic http auth 등..)을 전송
  • credentials 설정을 include/true로 설정하면 CORS정책에 의해 Access-Control-Allow-Origin을 모든 출처를 허용하는 * 로 지정할 수 없다는 에러가 발생
  • CORS 설정에서 *을 입력하여 모든 출처를 허용한 경우에는 특정 출처를 정확히 명시
  • 인증 정보 포함하여 요청하는 예시
    fetch('주소', {
    credentials: 'include', // 모든 요청에 인증 정보 포함
    });
  • axios 로 통신할 시, withCredentials 설정을 true 로 넣어주면 됨

    axios.post(주소, 데이터, { withCredentials: true });
    
    // 또는 공통으로 추가
    axios.defaults.withCredentials = true;



CORS 해결 방법

  • CORS 정책 위반으로 에러가 발생했을때 해결하는 방법

1. Access-Control-Allow-Origin 응답 헤더 세팅

  • 서버측 응답에서 접근 권한을 주는 헤더를 추가하여 해결
    app.use((req, res, next) => {
    res.header("Access-Control-Allow-Origin", "*");                               // 모든 도메인
    res.header("Access-Control-Allow-Origin", "https://example.com");  // 특정 도메인
    });

2. CORS 모듈 사용

  • 아무 옵션없이 설정하면 모든 cross-origin 요청에 대해 응답 → 특정 도메인이나 특정 요청에만 응답하게 옵션을 설정하는 것이 좋음

    const cors = require("cors");
    const app = express();
    
    app.use(cors());
  • 특정 도메인 접근 혀용

    const options = {
      origin: "http://example.com",        // 접근 권한을 부여하는 도메인
      credentials: true,                          // 응답 헤더에 Access-Control-Allow-Credentials 추가
      optionsSuccessStatus: 200,           // 응답 상태 200으로 설정
    };
    
    app.use(cors(options));
  • 특정 요청 접근 허용
    app.get("/example/:id", cors(), function (req, res, next) {
      res.json({ msg: "example" });
    });

3. webpack-dev-server proxy 기능

  • 리액트 개발환경에서, 서버쪽 코드를 수정하지 않고 해결 가능

  • 아래와 같이 프록시 속성을 설정하면, 서버에서 해당 요청을 받아줌

  • 중간의 프록시 서버 덕분에, domain.com 서버에서는 같은 도메인(domain.com)에서 온 요청으로 인식하여 CORS 에러가 발생 X

    // 프록시 쓰지 않았을때
    // localhost:8080(클라이언트 측) --X (CORS)--> domain.com (서버 측)
    
    // 프록시를 설정 후
    // localhost:8080(클라이언트 측) --O 프록시가 설정된 Webpack Dev Server--> domain.com (서버 측)
    
    module.exports = {
      devServer: {
        proxy: {
          "/api": {
            target: "domain.com",
            changeOrigin: true,
          },
        },
      },
    };

4. package.json에 proxy 값을 설정

  • create-react-app으로 생성한 프로젝트에서는, package.json에 proxy 값을 설정하여 proxy 기능을 활성화하는 방법
    {
      //...
      "proxy": "http://localhost:4000"
    }

+ Recent posts