[dreamhack] Lecture. Same Origin Policy, SOP
SOP
동일 출처 정책
현재 페이지의 출처가 아닌 다른 출처로부터 온 데이터를 읽지 못하게 하는 브라우저의 보안 메커니즘
Origin 구분 방법
Origin: 프로토콜, 포트, 호스트로 구성
구성 요소가 모두 일치해야 동일한 오리진
https://same-origin.com/ 이라는 오리진과 아래 URL 비교
URL 결과 이유
https://same-origin.com/frame.html | Same Origin | Path만 다름 |
**http://**same-origin.com/frame.html | Cross Origin | Scheme이 다름 |
https://cross.same-origin.com/frame.html | Cross Origin | Host가 다름 |
https://same-origin.com:1234/ | Cross Origin | Port가 다름 |
SOP 실습
- Cross Origin이 아닌 Same Origin일 때만 정보를 읽을 수 있음
- window.open → 새로운 창을 띄우는 함수
- object.location.href → 객체가 가리키고 있는 URL 주소를 읽어오는 코드
Same Origin
sameNewWindow = window.open('<https://dreamhack.io/lecture>');
console.log(sameNewWindow.location.href);
결과: <https://dreamhack.io/lecture>
Cross Origin
crossNewWindow = window.open('<https://theori.io>');
console.log(crossNewWindow.location.href);
결과: Origin 오류 발생
Cross Origin 데이터 읽기/쓰기
외부 출처에서 불러온 데이터를 읽으려고 할 때에는 오류가 발생해서 읽지 못함
데이터를 쓰는 것은 문제 없이 동작
crossNewWindow = window.open('<https://theori.io>');
crossNewWindow.location.href = "<https://dreamhack.io>";
SOP 데모
Figure 1. 모듈 구성 코드
1 <!-- iframe 객체 생성 -->
2 <iframe src="" id="my-frame"></iframe>
3
4 <!-- Javascript 시작 -->
5 <script>
6 /* 2번째 줄의 iframe 객체를 myFrame 변수에 가져옵니다. */
7 let myFrame = document.getElementById('my-frame')
8
9 /* iframe 객체에 주소가 로드되는 경우 아래와 같은 코드를 실행합니다. */
10 myFrame.onload = () => {
11 /* try ... catch 는 에러를 처리하는 로직 입니다. */
12 try {
13 /* 로드가 완료되면, secret-element 객체의 내용을 콘솔에 출력합니다. */
14 let secretValue = myFrame.contentWindow.document.getElementById('secret-element').innerText;
15 console.log({ secretValue });
16 } catch(error) {
17 /* 오류 발생시 콘솔에 오류 로그를 출력합니다. */
18 console.log({ error });
19 }
20 }
21 /* iframe객체에 Same Origin, Cross Origin 주소를 로드하는 함수 입니다. */
22 const loadSameOrigin = () => { myFrame.src = 'https://same-origin.com/frame.html'; }
23 const loadCrossOrigin = () => { myFrame.src = 'https://cross-origin.com/frame.html'; }
24 </script>
25
26 <!--
27 버튼 2개 생성 (Same Origin 버튼, Cross Origin 버튼)
28 -->
29 <button onclick=loadSameOrigin()>Same Origin</button><br>
30 <button onclick=loadCrossOrigin()>Cross Origin</button>
31
32 <!--
33 frame.html의 코드가 아래와 같습니다.
34 secret-element라는 id를 가진 div 객체 안에 treasure라고 하는 비밀 값을 넣어두었습니다.
35 -->
36 <div id="secret-element">treasure</div>
- 두 번째 줄의 iframe은 현재 웹 페이지 안에 또 다른 하나의 웹 페이지를 삽입하는 HTML태그, src 요소를 설정함으로써 삽입할 웹 페이지의 주소가 결정됨
- 10번째 줄의 onload는 이벤트 핸들러로써, 해당 객체가 성공적으로 로드되었을 때 동작. 본 예시에서는 10~23번 줄이 iframe 객체에 페이지가 로드되면 동작하는 코드
- 14~15번째 줄은 로드가 완료되면 iframe 내에 삽입된 주소에서 secret-element 객체의 값인 treasure을 읽어와 콘솔에 출력하는 동작을 수행
SOP 제한 완화
웹 서비스에서 동일 출처 정책인 SOP를 완화하여 다른 출처의 데이터 처리를 해야 하는 경우도 있음
→ 특정 포털 사이트가 카페, 블로그, 메일 서비스를 host가 다른 채로 운영하고 있다면 브라우저는 각 사이트의 오리진이 다르다고 인식함
자원을 공유하기 위해 사용할 수 있는 방법이 필요함
교차 출처 리소스 공유 Cross Origin Resource Sharing, CORS
HTTP 헤더에 기반하여 Cross Origin간에 리소스를 공유하는 방법
발신 측에서 CORS 헤더를 설정해 요청하면, 수신 측에서 헤더를 구분해 정해진 규칙에 맞게 데이터를 가져갈 수 있도록 설정
Figure 2. 웹 리소스 요청 코드
/*
XMLHttpRequest 객체를 생성합니다.
XMLHttpRequest는 웹 브라우저와 웹 서버 간에 데이터 전송을
도와주는 객체 입니다. 이를 통해 HTTP 요청을 보낼 수 있습니다.
*/
xhr = new XMLHttpRequest();
/* <https://theori.io/whoami> 페이지에 POST 요청을 보내도록 합니다. */
xhr.open(**'POST'**, '<https://theori.io/whoami>');
/* HTTP 요청을 보낼 때, 쿠키 정보도 함께 사용하도록 해줍니다. */
xhr.withCredentials = true;
/* HTTP Body를 JSON 형태로 보낼 것이라고 수신측에 알려줍니다. */
xhr.setRequestHeader('Content-Type', 'application/json');
/* xhr 객체를 통해 HTTP 요청을 실행합니다. */
xhr.send("{'data':'WhoAmI'}");
발신 측에서 POST 방식으로 HTTP 요청을 보냈으나, OPTION 메소드를 가진 HTTP 요청이 전달됨
이를 CORS preflight라고 함
수신 측에 웹 리소스를 요청해도 되는지 질의하는 과정
Figure 3. 발신측의 HTTP 요청
OPTIONS /whoami HTTP/1.1
Host: theori.io
Connection: keep-alive
Access-Control-Request-Method: POST
Access-Control-Request-Headers: content-type
Origin: <https://dreamhack.io>
Accept: */*
Referer: <https://dreamhack.io/>
Access-Control-Request로 시작하는 헤더가 존재함
해당 헤더 뒤에 따라오는 Method와 Headers는 각각 메소드와 헤더를 추가적으로 사용할 수 있는지 질의함
Figure3처럼 질문하면 아래(Figure 4)와 같이 응함
Figure 4. 서버의 응답
HTTP/1.1 200 OK
Access-Control-Allow-Origin: <https://dreamhack.io>
Access-Control-Allow-Methods: POST, GET, OPTIONS
Access-Control-Allow-Credentials: true
Access-Control-Allow-Headers: Content-Type
Access-Control-Allow-Origin | 헤더 값에 해당하는 Origin에서 들어오는 요청만 처리합니다. |
Access-Control-Allow-Methods | 헤더 값에 해당하는 메소드의 요청만 처리합니다. |
Access-Control-Allow-Credentials | 쿠키 사용 여부를 판단합니다. 예시의 경우 쿠키의 사용을 허용합니다. |
Access-Control-Allow-Headers | 헤더 값에 해당하는 헤더의 사용 가능 여부를 나타냅니다. |
위의 과정을 마치면 브라우저는 수신 측의 응답이 발신 측의 응답과 상응하는지 확인함
확인 후 POST 요청을 보내 수신 측의 웹 리소스를 요청하는 HTTP 요청을 보냄