CORS라 하고 SOP알아보기, CORS해결하기

junamee·2021년 7월 21일
0

네트워크

목록 보기
4/4
post-thumbnail

개발하다보면 꼭 1번씩은 마주친 CORS에러👿
빨간색이라 마주칠때마다 너무너무 싫었는데
CORS가 왜 필요한건지 SOP에 대해 알게되었고
이젠 CORS에러를 보아도 브라우저가 열일하고 있구나 하고 너그러운 마음을 품을 수 있게 되었다(?)🤗

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

:한 출처에서 다른 출처의 자원에 접근할 수 있도록 브라우저에게 알려주는 체제이다.
다른 출저 자원에 접근하는 경우는 fetch로 직접 요청을 보내는 경우나, 웹 폰트 import 시, img src, a href와 같이 해당 자원을 읽어오는 경우에도 요청이 필요하다.
CORS를 제대로 알려면 SOP를 먼저 알아야 한다.

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

Origin?

여기서 말하는 Origin이 요청을 주고받는 Url을 의미하는데
이는 정확히 따지자면 document.loation.origin이다.
지금 현재 벨로그에서 콘솔창을 열고 위와 같이 입력해보면 origin이 "https://velog.io" 임을 알 수 있다.
동일한 출처는 요청을 주고 받는 두 URL의 프로토콜(scheme),호스트, 도메인, 포트가 모두 같은 것을 의미한다.

https://velog.io/write === https://velog.io/list =>경로만 다른 경우
https://velog.io/write === http://velogs.io/write=>프로토콜도 다르고, 도메인도 다르다.

만약 SOP가 없다면?

🔓보안문제가 심각하다.

예시) 시나리오를 들면, 해커가 요상한 사이트를 만들어 유저에게 해커의 사이트로 접속하도록하고, 사이트의 스크립트 파일에 <img src=`중요한 정보가 담긴 사이트`/> 를 담아 보내면 유저의 document에서 이미지를 로드하듯이 중요한 정보가 담긴 외부 사이트 document를 로드해오게 되고 이때 사용자가 해당 사이트에 접속되었던 상태였다면 쿠키까지 붙혀져 서버는 사용자의 접속으로 간주하고 응답을 보내주게 된다. 그럼 외부document의 응답을 인식하고 해킹사이트로 전송시키는 요청을 보내면서 보안이 털리게 된다.
(아래 설명의 cross-origin reads가 가능하다면의 상황, 만약 SOP가 적용된다면 외부 Document의 응답은 받아왔지만 해당값을 인식할 수 없게 된다.=> CORS에러)

SOP면 모두 해결되는걸까?

SOP가 적용되면 응답 데이터를 인지하지 못하고(Read불가능) 다른 사이트로의 전달이 막혀 해커가 데이터에 접근할수 없게 되는 것이다. 다르게 말하면 중요한 정보를 요청받아오는 일까지는 가능하다는 것이다.
즉 SOP자체도 보안이 완벽하다곤 할 수 없다. 교차출처에 대하여 write, embedding이 가능하기 때문이다.

  • cross-origin reads 불가능=> CORS ERROR!
  • cross-origin writes 가능 : 다른 서버에 자원을 요청하는 행위 자체는 가능함 (GET요청으로 데이터를 가져오게 하거나, POST요청으로 데이터를 수정하거나)
    => *CSRF, 사이트간 요청위조 공격 (서버가 사용자의 사이트를 신뢰하는 상황)

    예시) 해커가 지정한 비밀번호로 외부사이트의 비밀번호를 변경하도록 하는 요청을 보내게 한다. 비밀번호가 변경되면 사용자의 아이디로 로그인하게 된다.(서비스 관리자 계정으로 로그인해서 전체 서비스의 에러를 발생시키거나 서비스 사용자 정보를 유출할수있게 된다.)

    =>해결방법 same-site!

  • cross-origin embedding 가능 : 상대 origin 의 script에 <script>css, <img><iframe>등을 심어버릴 수 있다. 그럼 사용자 브라우저는 script를 읽으며 해당 리소스를 받아오기 위해 리소스 주소로 요청을 보내게 된다.
    => *XSS 공격(사용자가 사이트를 신뢰하는 상황)

    예시)해당 origin에서 세션ID가 담긴 쿠키를 탈취하여 해커에게 보내거나 다른 곳으로 리다이렉션 등 비정상 동작을 일으킴

    => (Script의 유효성을 검증하는 단계가 필요하다. )

위의 공격상황을 고려한 보안이 더 요구되고 있고
때문에 SOP가 최소한의 맞는 방법이지만 요즘은 필요한 정보의 범위가 넓기에 CORS가 일부 허용되어야 하는 것이다.


CORS는 어떻게 동작하는가?

Preflight Request

예비요청과 본 요청으로 나누어서 서버에 요청을 전송하는데
예비요청을 preflight라고 하고 이때 HTTP메소드는 OPTIONS, 본 요청 전에 서버에서 요청 메서드와 헤더에 대해 인식하는지를 체크하는 것이다. 이때 preFlight의 응답메시지의 헤더에서

Access-Contrl-Allow-Headers
Access-Contrl-Allow-Methods
Access-Contrl-Allow-Origin

이 값들이 본 요청에서 보낼 내용과 일치하는지를 확인한다.

우선 이 정보를 받아오면 statusCode 200번을 볼 수 있다.
특히 CORS관련해서는 Access-Contrl-Allow-Origin에서 Origin이 요청을 보낸 Origin과 동일한지를 확인한다. 동일하다면 교차출처요청이 가능하니 다음으로 본 요청을 보내게 된다.

만일, 두 Origin이 다르다면 그 다음 본 요청이 보내지지 않고 CORS에러가 나게 되는 것이다.
이 preflight는 매번 보낼수도 있지만, 캐싱 조건을 커스텀할수도 있다. (ex_해당 기간 동안은 preflight를 캐쉬하여 사용하겠음)

Simple Request

"simple requests"에 해당하는 요청

Credentialed Request

HTTP Cookie와 HTTP Authentication 정보를 인식할 수 있게 해주는 요청,

axios.post(API.LOGIN, contents, { withCredentials: true })


CORS는 어떻게 해결할까?

리액트-node.js 환경에서의 해결방법을 살펴보면...

백엔드에서의 해결방법

접속을 허용할 origin을 "Access-Control-Allow-Origin"에 추가한다.
(모든 origin을 허가하지 않도록 주의한다.)
Express에서는 아래와 같은 방법으로 CORS 허가를 설정할 수 있다.

const prod = process.env.NODE_ENV === 'production';

if (prod) {
  app.enable('trust proxy');
  app.use(morgan('combined'));
  app.use(helmet({ contentSecurityPolicy: false }));
  app.use(hpp());
} else {
  app.use(morgan('dev'));
  app.use( > //*cors허가 설정
    cors({
      origin: true,
      credentials: true,
    })
  );
}

origin: true -> 프론트 도메인 주소가 자동으로 Access-Control-Allow-Origin으로 설정 (*: 전부허용)
credentials: true 요청에 쿠키를 포함하고 싶을 때, include:모든 요청에 인증정보를 포함하겠다., omit:인증정보 담지않음

클라이언트에서의 해결방법

서버와 클라이언트가 포트넘버는 다르고 로컬주소가 동일한 경우,
(ex_ 서버:http://localhost:3095, 클라이언트: http://localhost:3090) Proxy를 통해 해결을 한다. 클라이언트에서 서버로 요청을 보낼 때 port넘버가 달라 찾을 데이터 값이 없다면 proxy로 설정해둔 주소로 대신 요청을 보내게 되는 것이다. 클라이언트와 서버의 중계역할을 담당한다. (proxy가 어원적 의미로 '대리'라는 뜻을 갖고있다.)

  • 웹팩설정: devServer에서 proxy설정
  • CRA 리액트 프로젝트에서는 package.json에서 "proxy"설정

(로컬개발단계에서의 해결방법이고, 로컬주소도 동일할 때 가능한 방법이라 결국은 백엔드에서의 origin설정이 정답이 되긴한다)

devServer proxy설정

devServer: {
    historyApiFallback: true, 
    port: ${클라이언트 포트넘버},
    publicPath: '/dist/',
    proxy: {
      '/api/': {
        target: `http://localhost:${서버포트넘버}`,
        changeOrigin: true,
      },
    },
  },

/api/로 시작하는 요청을 서버포트주소에서 보내는 것 처럼 취급하겠다는 설정이다.
클라이언트에서는 기존의 localhost:3000/api/users/를 localhost부분을 삭제하고 /api/users로 변경해준다.

axios.post(`/api/users`)

이 때 특징으로는 동일한 주소로 이미 판단하여 preflight를 보내지 않는다는 점이다.

  • webpack devServer의 기능
    1) 핫리로딩 가능
    2) proxy를 통한 CORS에러 해결
    3) historyFallback 설정: router path기능

package.json에서 proxy설정

  },
  	...
  	"proxy": "http://localhost:4000",
  	...
  {
profile
아티클리스트 - bit.ly/3wjIlZJ

0개의 댓글