SOP와 CORS

Seungmin Lee·2022년 8월 15일
0
post-thumbnail

SOP(Same-Origin Policy)

SOP같은 출처의 리소스만 공유할 수 있음을 의미하는 동일 출처 정책이다.

여기서 동일한 출처란, 프로토콜, 호스트, 포트 까지 모두 일치하는 것을 의미한다.

SOP는 해킹 등의 위협으로 부터 안전한 보안상의 이점이 있기 때문에 모든 브라우저에서 기본적으로 사용하고 있는 정책이다. 또한 HTML이나 JavaScript의 fetch API 등은 기본적으로 SOP를 따른다. 하지만 개발을 할 때 다른 출처의 리소스를 사용하게 될 일은 너무나도 많고, 이런 문제 상황에서 등장하는 것이 바로 CORS 정책이다.

CORS(Cross-Origin Resource Sharing)

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

👉 교차 출처는 다른 출처로 이해하면 쉽다.

흔히 CORS 에러 라고 하는 이 에러는 사실 CORS 때문이 아니라 SOP 때문이다. 오히려 CORS는 이 에러를 해결해줄 수 있는 방안이다.

또한 CORS는 브라우저 스펙에 포함되는 정책이기 때문에, 브라우저를 통하지 않는 통신을 할 때는 이 정책이 적용되지 않는다.

우리가 CORS 정책을 위반하는 리소스 요청을 하더라도 서버가 같은 출처에서 보낸 요청만 받겠다는 로직을 가진 경우가 아닌 이상 서버는 정상적으로 응답을 하게 된다. 즉, 서버는 일단 요청을 받으면 어떤 요청이든 응답을 하고, 이후 브라우저가 CORS 정책 위반 여부를 판단한다. 서버가 보내준 응답 헤더에는 Access-Control-Allow-Origin 값이 포함되어 있는데, 이 값은 서버가 허가하는 출처를 포함한다. 브라우저는 서버가 허가하는 출처와 요청의 출처가 동일하면 리소스를 받아오고 다르면 응답을 버리고 CORS 에러를 출력한다.

CORS 동작 방식

CORS의 동작 방식에는 크게 3가지가 있다.

1) Preflight Request(사전 요청)

주로 사용하는 방식으로, 본 요청을 보내기 전에 브라우저 스스로 이 요청이 안전한지 확인하기 위해 보내는 사전 요청이다. 이 때, OPTIONS 메서드를 사용한다.

JavaScript의 fetch API를 사용해 브라우저에게 리소스를 받아오라는 명령을 내림 ➡ 브라우저가 서버에 사전 요청을 보냄 ➡ 서버는 어떤 것들을 허용하는지에 대한 정보를 응답 헤더에 담아서 브라우저에게 보냄 ➡ 브라우저는 미리 보낸 사전 요청과 서버의 응답을 비교한 후 안전하다고 판단되면 같은 엔드포인트로 다시 본 요청을 보냄 ➡ 서버가 응답하면 브라우저가 최종적으로 리소스를 JavaScript에 넘겨줌

프리플라이트 요청이 필요한 이유

  • 리소스 측면에서 효율적이다.
  • 브라우저는 응답을 받은 후에 CORS 권한 여부를 판단하기 때문에 DELETE, PUT 처럼 서버의 정보를 삭제하거나 수정하는 요청일 경우 사전요청이 없다면 허가하지 않는 출처여도 서버는 일단 요청을 처리할 것이고, 이미 다 삭제하고 난 뒤에서야 허가되지 않은 출처임을 알 것이다.

2) Simple Request(단순 요청)

프리플라이트와 동작 방식은 같고 사전 요청의 유무만 다르다. 단순 요청은 사전 요청 없이 그냥 바로 서버에 요청을 보낸다. 하지만 아무때나 단순 요청을 할 수 있는 것은 아니다. 특정 조건을 만족하는 경우에만 사전 요청을 생략할 수 있고, 사실상 조건이 까다로워서 충족하기 어렵다.

  • GET, HEAD, POST 요청 중 하나여야 한다.
  • 자동으로 설정되는 헤더 외에, Accept, Accept-Language, Content-Language, Content-Type 헤더의 값만 수동으로 설정할 수 있다.
    • Content-Type 헤더에는 application/x-www-form-urlencoded, multipart/form-data, text/plain 값만 허용됩니다.

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

좀 더 보안을 강화하고 싶을 때 사용하는 방식으로, 요청 헤더에 인증 정보를 담아 보내는 요청이다. 출처가 다를 경우에는 별도의 설정을 하지 않으면 민감한 정보이기 때문에 쿠키를 보낼 수 없다. 이 경우에는 프론트와 서버 양측 모두 CORS 설정이 필요하다.

  • 프론트 측에서는 요청 헤더에 withCredentials : true 를 넣어줘야 한다.
  • 서버 측에서는 응답 헤더에 Access-Control-Allow-Credentials : true 를 넣어줘야 한다.
  • 서버 측에서 Access-Control-Allow-Origin 을 설정할 때, 모든 출처를 허용한다는 뜻의 와일드카드(*)로 설정하면 에러가 발생한다. 인증 정보를 다루는 만큼 출처를 정확하게 설정해주어야 한다.

CORS 정책 위반을 해결하기 위해서는?

서버에서 Access-Control-Allow-Origin 헤더에 알맞은 값을 잘 세팅해주는 것이 좋다. 와일드카드인 *를 사용해서 셋팅하게 되면 모든 출처를 허가한다는 의미여서 편할 수는 있지만, 심각한 보안 이슈를 초래할 수 있기 때문에 가급적이면 출처를 명시하는 것이 좋다.

Node.jsExpress에서 CORS 설정 방법

Node.js 서버

Node.js로 간단한 HTTP 서버를 만들 경우, 다음과 같이 응답 헤더를 설정해줄 수 있다.

const http = require('http');

const server = http.createServer((request, response) => {
// 모든 도메인
  response.setHeader("Access-Control-Allow-Origin", "*");

// 특정 도메인
  response.setHeader("Access-Control-Allow-Origin", "https://codestates.com");

// 인증 정보를 포함한 요청을 받을 경우
  response.setHeader("Access-Control-Allow-Credentials", "true");
})

Express 서버

Express 프레임워크를 사용해서 서버를 만드는 경우에는, cors 미들웨어를 사용해서 보다 더 간단하게 CORS 설정을 해줄 수 있다.

const cors = require("cors");
const app = express();

//모든 도메인
app.use(cors());

//특정 도메인
const options = {
  origin: "https://codestates.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" });
});

Reference)
CORS는 왜 이렇게 우리를 힘들게 하는걸까?

profile
<Profile name="seungmin" role="frontendDeveloper" />

0개의 댓글