ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • 개발자들을 괴롭히는 SOP(동일 출처 정책)와 CORS(교차 출처 리소스 공유)
    Web 2021. 7. 21. 02:26

    CORS 에러는 프론트엔드-백엔드 간 협업을 곤란하게 하는 악의 축이다

    나도 관통프로젝트 할 때 CORS 에러에 의해 앓아누울 뻔 한 적이 있었다가 구글링을 통해 해결했는데, 생각해보니 CORS 에러가 무엇인지 어떤 의미인지에 대해서는 몰랐었기에

    https://www.youtube.com/watch?v=bW31xiNB8Nc 

    https://www.youtube.com/watch?v=-2TgkKYmJt4 

    위의 두 영상을 보고 CORS, 그리고 CORS 에러의 원인인 SOP 정책에 대해 공부하게 되었다.

     

    SOP(Same-Origin Policy)란 어떤 출처(프로토콜, 호스트, 포트의 조합을 한 출처라고 한다)에서 불러온 문서, 스크립트, 리소스가 다른 출처에서 가져온 리소스와 상호작용하는 것을 제한하는 보안 방식이다

    이러한 정책은 잠재적으로 해로울 수 있는 문서를 분리하여 공격받을 수 있는 경로는 줄이는 것을 의미한다(MDN)

     

    https://ko.wikipedia.org/wiki/%EB%8F%99%EC%9D%BC-%EC%B6%9C%EC%B2%98_%EC%A0%95%EC%B1%85

    이러한 식으로 프로토콜, 호스트, 포트 중 하나라도 다르면 브라우저는 같은 출처라고 보지 않는다.

    다른 출처에 대한 접근 제한을 막지 않을 시 아래와 같은 상황이 발생할 수 있다

     

    1️⃣ 사용자가 웹 사이트에 로그인을 시도한다

    2️⃣ 로그인 성공과 함께 인증 토큰을 발급받아 쿠키에 저장한다

    3️⃣ 외부에서 사용자에게서 인증 토큰의 탈취를 시도한다(ex. 이메일 클릭 시 script 실행)

    4️⃣ 사용자의 인증 토큰을 뺏어온다

    5️⃣ 웹 사이트를 통해 인증 토큰을 가진 다른 출처로 접근하여 서비스를 이용할 수 있다(해킹 위험)

     

    이 때, 동일한 인증 토큰으로 접근하더라도 사용자의 출처(ex. 192.173.0.30:8080 vs http://unknown.com:8092)는 다른 른 출처를 가지고 있기에, 다른 출처로의 접근을 제한하는 것이 SOP 보안 방식이다.

     

    하지만 개발을 하다보면 다른 출처로서의 리소스를 허용해야 하는 순간이 반드시 있기 때문에 CORS를 사용한다.

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

    CORS를 허용하면 웹 페이지는 교차 출처 이미지, 스타일시트, 스크립트, 동영상 등의 자원들을 임베드하여 사용할 수 있다. 

    https://mdn.mozillademos.org/files/14295/CORS_principle.png

    SOP에 의해 Ajax 요청이 기본적으로 금지되기 때문에 Ajax를 사용할 때 CORS 에러를 자주 접할 수 있다.

     

    CORS가 동작하는 방식에는 크게 세 가지가 있지만 여기에서는 두 가지 방식만 간단하게 살펴보기로 한다.

    1. Simple Requests

    다음의 요청들을 모두 성립해야 한다

    1️⃣ 특정 HTTP Method(GET/HEAD/POST)를 사용

    2️⃣ 자동으로 설정된 헤더 이외에는 다음의 헤더만 사용 가능

    • Accept
    • Accept-Language
    • Content-Launguage
    • Content-Type
    • DPR
    • Downlink
    • Save-Data
    • Viewport-Width
    • Width

    3️⃣ Content-Type 헤더는 다음의 값들만 허용

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

    (이외에도 두 가지 조건이 더 있지만 생략한다)

    GET /resources/public-data/ HTTP/1.1
    Host: bar.other
    User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.14; rv:71.0) Gecko/20100101 Firefox/71.0
    Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
    Accept-Language: en-us,en;q=0.5
    Accept-Encoding: gzip,deflate
    Connection: keep-alive
    Origin: https://foo.example

    브라우저가 위와 같이(헤더의 Origin을 명시) 서버로 요청을 보내면

    HTTP/1.1 200 OK
    Date: Mon, 01 Dec 2008 00:23:53 GMT
    Server: Apache/2
    Access-Control-Allow-Origin: *
    Keep-Alive: timeout=2, max=100
    Connection: Keep-Alive
    Transfer-Encoding: chunked
    Content-Type: application/xml
    
    […XML Data…]

    서버는 Access-Control-Allow-Origin 헤더를 사용하여 응답한다.

    이 때에는, 모든 도메인에서 접근할 수 있음을 의미하며 특정 출처만 허용하려는 경우에는 Access-Control-Allow-Origin에 해당 도메인을 명시하여 보낸다.

     

    2. Preflighted Request

    simple request와는 달리, OPTIONS 메소드를 통해 다른 도메인의 리소스로 HTTP 요청을 보냈을 때 전송이 안전한지 확인하는 방식이다.

    위처럼 Preflight에서의 전송이 안전하게 확인되면 메인 요청을 전달하는 방식으로 사용한다.

    OPTIONS /resources/post-here/ HTTP/1.1
    Host: bar.other
    User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.14; rv:71.0) Gecko/20100101 Firefox/71.0
    Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
    Accept-Language: en-us,en;q=0.5
    Accept-Encoding: gzip,deflate
    Connection: keep-alive
    Origin: http://foo.example
    Access-Control-Request-Method: POST
    Access-Control-Request-Headers: X-PINGOTHER, Content-Type
    
    
    HTTP/1.1 204 No Content
    Date: Mon, 01 Dec 2008 01:15:39 GMT
    Server: Apache/2
    Access-Control-Allow-Origin: https://foo.example
    Access-Control-Allow-Methods: POST, GET, OPTIONS
    Access-Control-Allow-Headers: X-PINGOTHER, Content-Type
    Access-Control-Max-Age: 86400
    Vary: Accept-Encoding, Origin
    Keep-Alive: timeout=2, max=100
    Connection: Keep-Alive

    위와 같이 preflight request가 완료되면, 실제 요청을 전송한다.

    POST /resources/post-here/ HTTP/1.1
    Host: bar.other
    User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.14; rv:71.0) Gecko/20100101 Firefox/71.0
    Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
    Accept-Language: en-us,en;q=0.5
    Accept-Encoding: gzip,deflate
    Connection: keep-alive
    X-PINGOTHER: pingpong
    Content-Type: text/xml; charset=UTF-8
    Referer: https://foo.example/examples/preflightInvocation.html
    Content-Length: 55
    Origin: https://foo.example
    Pragma: no-cache
    Cache-Control: no-cache
    
    <person><name>Arun</name></person>
    
    
    HTTP/1.1 200 OK
    Date: Mon, 01 Dec 2008 01:15:40 GMT
    Server: Apache/2
    Access-Control-Allow-Origin: https://foo.example
    Vary: Accept-Encoding, Origin
    Content-Encoding: gzip
    Content-Length: 235
    Keep-Alive: timeout=2, max=99
    Connection: Keep-Alive
    Content-Type: text/plain
    
    [Some GZIP'd payload]

     

    3. Request with Credentials

    이후 추가 예정

     

    앞으로 시작할 공통PJT에서도 CORS 에러때문에 고통받는 일이 없었으면 좋겠다 ㅠ

    참고 자료
    https://developer.mozilla.org/ko/docs/Web/Security/Same-origin_policy
    https://developer.mozilla.org/ko/docs/Web/HTTP/CORS
    https://en.wikipedia.org/wiki/Cross-origin_resource_sharing
    https://ko.wikipedia.org/wiki/%EB%8F%99%EC%9D%BC-%EC%B6%9C%EC%B2%98_%EC%A0%95%EC%B1%85
Designed by Tistory.