[Web] SOP(동일 출처 정책) / CORS(교차 출처 리소스 공유) / Node.js, Express로 CORS 설정하기

김뀨뀨·2022년 8월 15일
1

Web

목록 보기
1/3
post-thumbnail

SOP(Same-Origin Policy) - 동일 출처 정책

말 그대로 웹에서 같은 출처의 리소스만 공유가 가능하도록 하는 보안 방식이다.

📎 출처(Origin)

그렇다면 여기서 같은 '출처'는 무엇을 의미할까?

1. 프로토콜(protocol)

2. 호스트(host)

3. 포트(port)

이 세가지가 모두 같아야 same origin이라고 인정한다.

https://www.google.com:443 과 같은 url에서
프로토콜은 https, 호스트는 www.google.com, 포트는 443이다.

📎 SOP가 등장한 이유

동일 출처 정책은 웹 애플리케이션이 다른 출처에서 가져온 리소스와 상호작용하는 것을 방지함으로써 잠재적으로 해로울 수 있는 문서를 분리한다. 따라서 공격 받을 수 있는 경로가 줄어들고, 해킹 등의 위협에서 좀 더 안전해질 수 있다.

하지만 다른 출처의 리소스에 접근할 수 없다면 웹 애플리케이션에 많은 제약이 생길 것이다. 예를 들어 카카오 로그인 기능이 있는 웹 사이트를 만들려고 할 때, api를 사용해서 다른 출처의 리소스를 받아야한다.
이런 문제를 해결하기 위해서 '교차 출처 리소스 공유', 즉 CORS를 이용한다.

CORS(Cross-Origin Resource Sharing) - 교차 출처 리소스 공유

💡 CORS란?

CORS는 SOP와 반대되는 개념이다. CORS는 한 출처에서 실행 중인 웹 애플리케이션이 다른 출처의 리소스를 받아올 수 있는 권한을 부여해서 브라우저에게 알려주는 체제이다.
어떤 방식으로? 추가 HTTP 헤더를 사용한다.
웹 애플리케이션은 사용하는 리소스가 자신의 출처와 다를 때 CORS 요청을 실행한다.


좀 더 자세히 알아보자.
우선 domian-a.com 에서 domain-a.com의 리소스를 가져오는 것은 항상 허용된다.
하지만 domain-a.com에서 domain-b.com의 리소스를 가져오려면 domain-b.com의 서버에서 올바른 CORS 헤더를 포함한 응답을 반환해야만 리소스를 무사히 받아올 수 있다.

(✏️ 참고로 CORS 실패는 오류의 원인이 될 수 있지만, 보안상의 이유로 javascript에서는 오류의 상세 접근할 수 없다. 정확히 알아내려면 브라우저의 콘솔을 봐야한다.)

💡 CORS 동작 방식

CORS가 동작하는 방식에는 두가지가 있다. 단순 요청(simple requests)와 프리플라이트 요청(preflight requests)이다.

📌 단순 요청(Simple request)

특정 조건을 충족하면 HTTP 요청에서 preflight 요청이 필요없다.

- GET, HEAD, POST 중 하나의 메서드

- Content-Type 헤더가 다음 중 하나인 경우

  • application/x-www-form-urlencoded
  • multipart/form-data
  • text/plain

자동으로 설정되는 헤더 외의 다음 헤더의 값만 수동으로 설정한 경우

  • Accept, Accept-Language, Content-Langauge, Content-Type

CORS-safelisted request-header를 포함하는 경우

XMLHttpRequest.upload에 이벤트 핸들러, 리스너가 등록되지 않은 경우

요청에 ReadableStream 객체가 사용되지 않은 경우

위 조건을 '모두' 충족하면 preflight request를 생략하고 요청을 보낸다.

1. 클라이언트에서 요청 헤더에 자신의 Origin을 포함한 요청을 서버로 보낸다.
2. 서버가 유효한 CORS 요청인지 확인하고 응답 헤더에 Access-Control-Allow-Origin을 추가해 응답한다.
사진처럼 Access-Control-Allow-Origin: * 으로 설정한 경우에는 모든 출처에서 자신의 리소스에 접근 가능하도록 허용한다는 뜻이고, 특정 출처를 지정해줄 수도 있다.

📌 프리플라이트 요청(preflight request)

'pre'라는 접두어에서 알 수 있듯이 프리플라이트 요청은 미리 무언가 하는 방식이다.
실제 요청을 보내기 전, 브라우저가 서버에서 OPTIONS 메서드로 사전 요청을 보내 해당 출처의 리소스에 접근 권한이 있는지부터 확인한다.

1. preflight request

OPTIONS 메서드로 자신의 Origin을 포함한 요청을 보낸다.
(Content-Type이 application/xml이기 때문에 preflighted 처리가 된다.)

  • Access-Control-Request-Method: preflight request에서 사용되며 어떤 Method를 사용할지 서버에게 알리기 위해 사용된다.
  • Access-Control-Request-Headers: prefligth request에서 사용되며 어떤 Header를 사용할 것인지 서버에게 알리기 위해 사용된다.

2. preflight response

Access-Control-Allow-Origin에 request를 보낸 Origin이 포함되어 돌아온다.

  • Access-Control-Allow-Methods: preflight request에 대해 리소스에 접근할 때 허용되는 Method를 지정한다.
  • Access-Control-Allow-Headers: preflight request에 대해 해당 요청에서 사용할 수 있는 Header를 지정한다.
  • Access-Control-Max-Age: preflight request에 대해 캐시에 얼마나 오랫동안 남아있는지 지정한다.(sec)

3. main request

POST 메서드로 실제 요청을 보낸다.

4. main response

  • 접근 권한이 있는 경우
    → 응답 헤더의 Access-Control-Allow-Origin으로 요청을 보낸 출처가 Response로 돌아오고 → 실제로 요청을 보낸다.

  • 접근 권한이 없는 경우
    → 응답 헤더에 Access-Control-Allow-Origin이 없고 CORS 에러가 나서 실제 요청이 전달되지 않는다.

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

  • 실제 요청을 보내기 전 미리 권한을 확인할 수 있다. 이는 요청을 처음부태 통째로 보내는 것보다 리소스 측면에서 효율적이다.
  • CORS에 대비가 되어있지 않는 서버를 보호할 수 있다.
  • 프리플라이트 요청 없이 서버에 바로 요청을 보낸다면?
    브라우저가 CORS 에러를 띄웠을 때는 이미 서버에서 요청이 수행된 상태가 된다.

❗️ 인증 정보를 포함한 요청 - Credentialed Request

요청 헤더에 인증 정보를 담아야할 때가 있다.
이 때 같은 Origin에서 http 통신을 한다면 쿠키가 알아서 request 헤더에 들어가지만,
Origin이 다를 경우에는 별도의 설정을 하지 않으면 쿠키를 보낼 수 없다.
따라서 클라이언트와 서버 모두 CORS 설정을 해주어야 한다.

❗️ credential 설정

1. 프론트(클라이언트)

//fetch API
fetch(url, {
  credentials: 'include'
})

//XMLHttpRequest
const xhr = new XHMLHttpRequest()
xhr.withCredentials = true;

2. 서버
응답 헤더에 Access-Control-Allow-Credentials:true
로 설정해주어야 한다. 이 때 true 대신 와일드카드(*)로 설정하면 에러가 발생한다.

CORS 설정

Node.js

const http = require('http');
const server = http.createServer((request, response) => {
  //모든 도메인에 대해 설정
  response.setHeader("Access-Control-Allow-Origin", "*");
  
  //특정 도메인에 대해
  response.setHeader("Access-Control-Allow-Origin", "https://www.google.com/");
  
  //인증 정보를 포함한 요청일 경우
  response.setHeader("Access-Control-Allow-Credentials", "true");
})

Express 서버

CORS 미들웨어를 사용한다.

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

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

//특정 도메인에 대해
const options = {
  origin: "https://www.google.com/",
  credentials: true,
  optionsSuccessStatus: 200
};
app.use(cors(options));

//특정 요청에 대해
app.get("/example:id", cors(), function(req, res, next) {
  res.json({msg: "example" });
});

참고
https://developer.mozilla.org/ko/docs/Web/HTTP/CORS
https://yoo11052.tistory.com/139

profile
개발로 밥벌이 하고 싶은 사람

0개의 댓글