Notice
Recent Posts
Recent Comments
Link
«   2024/07   »
1 2 3 4 5 6
7 8 9 10 11 12 13
14 15 16 17 18 19 20
21 22 23 24 25 26 27
28 29 30 31
Tags
more
Archives
Today
Total
관리 메뉴

옥수수, 기록

WEB server 기초 본문

카테고리 없음

WEB server 기초

ok-soosoo 2022. 12. 8. 23:57

SOP(Same-Origin Policy)

‘같은 출처의 리소스만 공유가 가능하다’라는 동일 출처 정책을 뜻한다.

 

출처란(Origin)?

프로토콜, 호스트, 포트의 조합으로 되어있다. 이 중 하나라도 다르면 동일한 출처로 보지 않는다.

ex)

https://mail.naver.com/v2/folders/0/all ≠ http://mail.naver.com/v2/folders/0/all

⇒ 프로토콜이 다르기 때문에 동일 출처 ❌

 

https://mail.naver.com/ ≠ https://naver.com/

⇒ 호스트가 다르기 때문에 동일 출처 ❌

 

http://mail.naver.com:30 ≠ http://mail.naver.com

⇒ http의 기본포트는 80이다. 포트가 다르기 때문에 동일 출처 ❌

 

https://mail.naver.com:443 === https://mail.naver.com

⇒ https의 기본포트는 443이다. 동일 출처 ⭕️

 

SOP는 왜 생겨나게 되었을까?

동일 출처 정책은 잠재적으로 해로울 수 있는 문서를 분리, 공격받을 수 있는 경로를 줄여준다.

⇒ 해킹 등의 위협에서 보다 안전해질 수 있다.

ex)

SOP가 없다면

  1. 사이트 이용이 끝나고 로그아웃을 하지않거나 자동로그인 기능을 켜놓았을 경우, 브라우저에 로그인 정보가 남아있다.
  2. 로그인 정보를 노리는 코드가 있는 타 사이트를 방문하게되면 개인정보를 뺏김

SOP가 있었다면

  • 다른 사이트와의 리소스 공유를 제한하기 때문에 로그인 정보가 타 사이트의 코드에 의해 새어나가는 것을 방지할 수 있음
  • 브라우저들은 기본적으로 SOP를 사용한다.

하지만 다른 출처의 리소스를 사용할 일은 너무 많다. 개발중인 웹사이트에서 카카오맵 API를 사용하거나 github의 정보를 받아와 쓰는 것 모두 다른 출처의 리소스를 사용하는 일이다.

하지만 브라우저는 기본적으로 SOP정책을 사용하기에 다른 출처에서 리소스를 받아오고 싶다면 CORS가 필요하다.


CORS(Cross-Origin Resource Sharing)

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

브라우저는 SOP에 의해 기본적으로 다른 출처의 리소스 공유를 막지만 CORS를 사용하면 접근 권한을 얻을 수 있다.

웹 개발을 하다 보면 만날 수 있는 CORS error

위의 에러를 풀어쓰면

다른 출처의 리소스를 가져오려고 했지만 SOP때문에 접근이 불가능합니다. CORS 설정을 통해 서버의 응답 헤더에 ‘Access-Control-Allow-Origin’을 작성하면 접근 권한을 얻을 수 있습니다.
 

CORS 동작 방식

  1. 프리플라이트 요청 (Preflight Request)브라우저는 서버에 실제 요청을 보내기 전 Preflight 요청을 보내고, 응답 헤더의 Access-Control-Allow-Origin으로 요청을 보낸 출처가 돌아오면 실제 요청을 하게 된다.
  2. 요청을 보낸 출처가 접근 권한이 없다면 브라우저에서 CORS에러를 띄우고, 실제 요청은 전달되지 않는다.
  3. 실제 요청을 보내기 전, OPTIONS 메서드로 사전 요청을 보내 해당 출처 리소스에 접근 권한이 있는지부터 확인하는 것을 프리플라이트 요청이라고 한다.
  • 프리플라이트 요청이 필요한 이유
    • 실제 요청을 보내기 전 미리 권한 확인을 할 수 있기 때문에, 실제 요청을 처음부터 통째로 보내는 것보다 리소스 측면에서 효율적
    • CORS에 대비되어 있지 않은 서버를 보호할 수 있다. CORS 이전에 만들어진 서버들은 SOP 요청만 들어오는 상황을 고려하고 만들어져 다른 출처에서 들어오는 요청에 대한 대비가 되어있지 않다.
  1. 단순 요청 (Simple Request)
    • 조건
      • GET, HEAD, POST 요청 중 하나여야함
      • 자동으로 설정되는 헤더 외 Accept, Accept-Language, Content-Language, Content-Type헤더의 값만 수동으로 설정할 수 있다.
        • Content-Type 헤더에는 application/x-www-form-urlencoded, multipart/form-data, text/plain 값만 허용된다.
  2. 특정 조건이 만족되면 프리플라이트 요청을 생략하고 요청을 보내는 것을 말한다.
  3. 인증정보를 포함한 요청 (Credentialed Request)쿠키는 민감한 정보이기 때문에 출처가 다를 경우 별도의 설정을 하지 않으면 보낼 수 없다.
    • 프론트 : 요청 헤더에 요청 헤더에 withCredentials : true 를 넣어줘야 한다.
    • 서버 : 응답 헤더에 Access-Control-Allow-Credentials : true 를 넣어줘야 한다.
      • 서버 측에서 Access-Control-Allow-Origin 을 설정할 때, 모든 출처를 허용한다는 뜻의 와일드카드(*)로 설정하면 에러가 발생한다. 인증 정보를 다루는 만큼 출처를 정확하게 설정해주어야 한다.
  4. 이 경우 프론트, 서버 양측 모두 CORS 설정이 필요하다.
  5. 요청 헤더에 인증 정보를 담아 보내는 요청이다.

CORS 설정 방법

  1. Node.js 서버
    1. 간단한 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");
})

response.setHeader()는 단일 헤더만 설정해 줄 수 있다.
response.writeHead()를 사용하면 상태 코드, 콘텐츠 및 여러 헤더를 포함하여 응답 헤드에 대한 거의 모든 것을 설정할 수 있다.
response

 

2. Express 서버

  1. 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" });
});
서버가 데이터를 받아오는 형태

const http = require('http');

const PORT = 4999;

const ip = 'localhost';

const server = http.createServer((request, response) => {
 
    if (request.method === 'OPTIONS') {
      response.writeHead(200, defaultCorsHeader);
      response.end(); // response.end()는 응답마다 있어야 한다.
   }
    if (request.method === 'POST' && request.url === '/upper') {

// request로 들어온 데이터를 받아오는 법 
      let body = []; 
      request.on('data', (chunk) => { //request된것이 'data'면 콜백함수의 인자 chunk로 들어가서 
        body.push(chunk); //빈 배열인 body에 그 데이터를 보내게 된다.
// 여기서 chunk는 덩어리라는 뜻으로 여러 덩어리로 나뉘어진 데이터들이다
      }).on('end', () => { // 'end' >>> 모든 데이터를 다 받아왔을 때 콜백함수 실행/ 일종의 조건문이라고 봐도 좋을듯
        body = Buffer.concat(body).toString().toUpperCase(); 
// 앞서 body에 담아줬던 조각난 chunk들을 
// Buffer.concat을 사용해 모아주고 .toString()메서드로 문자열화 시켜준다
        response.writeHead(201, defaultCorsHeader);
        response.end(body);
// response.end()는 이제 response작업이 끝났음을 알려주는 메서드인데 인자에 값이 들어간다면 
// 아래 형태의 코드가 축약되어 있는 것이다. 위 코드와 아래 코드는 동일하게 작동한다.
// response.write(body)
// response.end()
      })
  
    } else if (request.method === 'POST' && request.url === '/lower' ) {
      let body = [];
      request.on('data', (data) => {
        body.push(data);
      }).on('end', () => {
        body = Buffer.concat(body).toString().toLowerCase();
        response.writeHead(201, defaultCorsHeader);
        response.end(body);
      })
    } else {
        response.writeHead(404, defaultCorsHeader);
        // response.write('error');
        response.end(); 
    }  
});

server.listen(PORT, ip, () => {
  console.log(`http server listen on ${ip}:${PORT}`);
});

const defaultCorsHeader = {
  'Access-Control-Allow-Origin': '*',
  'Access-Control-Allow-Methods': 'GET, POST, PUT, DELETE, OPTIONS',
  'Access-Control-Allow-Headers': 'Content-Type, Accept',
  'Access-Control-Max-Age': 10
};
Comments