SOP(Same Origin Policy, 동일 출처 정책)
동일 출처 정책은 한 출처(Origin)의 문서 및 스크립트가 다른 출처의 리소스와 상호작용하는 방법을 제한하는 브라우저 보안 기능이다. 즉, 동일한 출처에서만 리소스를 공유할 수 있게 제한하는 정책이다.
- Origin(출처)
동일 출처 정책과 이후에 정리할 CORS에도 똑같은 정책이라는 용어가 들어가는데 그렇다면 출처(Origin)는 대체 어떤 것을 의미하는 것일까?
출처를 먼저 알아보자면 출처는 프로토콜(스키마), 포트, 호스트로 정의할 수 있다.
동일 출처 정책에서 동일 출처에 대한 의미는 URL에서 위의 세 가지(프로토콜, 포트, 호스트)가 모두 동일한 경우 동일한 출처로 간주된다.
예를 들어 http://www.example.com/test/test.html 이라는 URL과 아래의 표를 참고해서 출처를 비교해 보자.
URL | 결과 | 이유 |
http://www.example.com/test2/test.html | 동일 출처(성공) | 경로만 다름 |
http://www.example.com/test/one/one.html | 동일 출처(성공) | 경로만 다름 |
https://www.example.com/test.html | 실패 | 프로토콜이 달라서 실패함 |
http://www.example.com:81/test/test.html | 실패 | 포트 번호가 달라서 실패함(http는 80번 포트) |
http://blog.example.com/test/test.html | 실패 | 호스트가 달라서 실패함 |
그렇다면 왜 동일한 출처만 접근할 수 있게 제한을 두는 것인지 알아보자.
그림을 예시로 들면 출처가 다를 경우 정상적인 웹 사이트에 해커가 특정 코드를 심어서 해커의 서버로 접속하게 만들 수 있다.
이러한 보안 문제로 인해서 CSRF나 XSS와 같은 해커의 공격을 받게 되고 사용자의 개인 정보나 중요한 데이터를 탈취당할 수 있는 심각한 문제가 발생할 수 있다.
따라서 악의적인 목적에 의해서 문제가 발생할 수 있기 때문에 동일 출처 정책을 강제하여 출처가 다른 스크립트는 실행되지 않도록 브라우저에서 사전에 방지하는 것이다.
하지만 웹 환경이 지속적으로 발전하고 거대해지면서 같은 출처가 아닌 다른 출처에 있는 자원도 사용해야 되는 상황이 필요해졌고, 이때 CORS를 통해서 해결할 수 있게 되었다.
CORS(Cross Origin Resource Sharing, 교차 출처 리소스 공유)
최신 웹 앱들은 다른 출처에서 리소스를 가져오는 경우가 많아지면서 기존의 SOP 보다 더 유연한 방식이 필요해졌고 그러면서 나온 것이 CORS 기능이다.
그러면 기존의 SOP에서 CORS는 어떻게 동작하게 되는지 알아보자.
CORS 작동 방식
CORS가 어떻게 작동되는지 naver 사이트를 예시로 살펴보자.
1. 클라이언트(브라우저) 요청
브라우저가 교차 출처 요청을 하면 브라우저는 현재 출처(스키마 혹은 프로토콜, 호스트, 포트)가 있는 Origin 헤더를 추가한다.
2. 서버 응답
서버는 이 헤더를 보고 액세스를 허용하려고 할 때 요청 출처를 지정하는 Access-Control-Allow-Origin 헤더를 응답에 추가한다. (모든 출처를 허용하려면 "*")
3. 브라우저에서 응답 수신
브라우저는 적절한 Access-Control-Allow-Origin 헤더와 함께 이 응답을 확인하면 응답 데이터를 클라이언트 사이트와 공유한다.
결론적으로 클라이언트에서 출처에 대한 요청을 보내게 되고 서버가 해당 출처를 받아서 Access-Control-Allow-Origin 헤더에 허용할 출처를 기재해서 다시 클라이언트로 응답을 해주게 된다.
여기서 알 수 있는 것은 기존의 SOP 방식에선 브라우저에서 출처에 대한 차단과 비교를 하게 되었는데 CORS로 와서는 서버에서도 출처에 대한 설정이 따로 필요하게 되는 것이다.
웹 프로젝트를 진행하다 보면 정상적인 API 호출을 했는데도 CORS 에러가 나는 것을 알 수 있는데 이러한 CORS 설정을 하지 않으면 정상적인 요청에도 차단을 하게 돼버린다.
CORS 작동 방식 3가지
CORS가 동작하는 방식은 세 가지 시나리오가 있다고 한다. 하나씩 살펴보면서 어떤 것이 있는지 알아보자.
1. CORS preflight
CORS에서 사용하는 preflight는 본격적인 교차 출처 HTTP 요청 전에 서버 측에서 해당 요청의 메서드와 헤더에 대해 인식하고 있는지를 확인하는 CORS 요청이다.
preflight를 해석해 보자면 예비 요청을 의미하는데 말 그대로 서버에 예비 요청을 미리 보내서 잘 통신되는지 확인을 해본 뒤 본 요청을 보내는 방식이다.
일반적인 HTTP 요청과는 다르게 preflight를 보내게 되면 요청 메서드가 GET, POST 등이 아닌 OPTIONS라는 특별한 메서드로 요청을 보내게 된다.
실제로 사용할 HTTP 메서드는 Access-Control-Request-Method에 설정하게 되고(사진에서는 DELETE), HTTP 헤더도 Access-Control-Request-Headers에 설정하게 된다.
서버에서는 해당 예비 요청에 대해서 어떤 것을 허용하고, 금지하는지에 대한 정보를 응답해 주게 된다.
- Access-Control-Allow-Origin 헤더에 허용되는 Origin들의 목록을 설정
- Access-Control-Allow-Methods 헤더에 허용되는 메서드들의 목록을 설정
- Access-Control-Allow-Headers 헤더에 허용되는 헤더들의 목록을 설정
- Access-Control-Max-Age 헤더에 해당 예비 요청이 브라우저 캐시 될 수 있는 제한 시간을 초 단위로 설정
- preflight의 문제점
예비 요청을 보냄으로써 보안을 강화하는 것은 좋지만 결국에는 요청을 한 번 더 보내야 된다는 문제가 발생하게 된다. 이로 인해서 실제 요청에 걸리는 시간이 늘어나게 되고 애플리케이션 성능에 영향을 미칠 수밖에 없다.
이러한 문제를 해결하기 위해서 사용할 수 있는 방법은 브라우저의 캐시를 이용하는 것이다.
응답 헤더에 Access-Control-Max-Age 값을 설정하여 캐시 될 시간을 명시해 주면 해당 예비 요청을 캐싱시켜 최적화할 수 있다.
2. 단순 요청(Simple Request)
단순 요청은 앞서 살펴본 예비 요청을 생략하고 바로 서버에 본 요청을 보낸 후 응답으로 Access-Control-Allow-Origin 헤더를 보내주면 브라우저가 CORS 정책 위반 여부를 검사하는 방식이다.
이러한 단순 요청을 하기 위해서는 아래의 조건을 만족할 때만 가능하다.
조건 |
다음 HTTP 메서드중 하나의 메서드 - GET - HEAD - POST |
유저 에이전트가 자동으로 설정한 헤더 |
Accept |
Accept-Language |
Content-Language |
Content-Type - application/x-www-form-urlencoded - multipart/form-data - text/plain |
하지만 조건을 보더라도 너무 제약이 많고, 특히나 Content-Type 같은 경우에는 웹 프로젝트를 해본 개발자라면 알겠지만 대부분 application/json으로 통신하기 때문에 조건을 만족하기 어렵다.
따라서 대부분의 API 요청은 예비 요청으로 이루어지게 된다.
3. 인증된 요청(Credential Request)
인증된 요청은 클라이언트에서 서버에게 자격 인증 정보(Credential)를 실어 요청할 때 사용된다.
자격 인증 정보란 세션 ID가 저장되어 있는 쿠키 혹은 Authorization 헤더에 설정하는 토큰 값 등을 말한다.
클라이언트에서 일반적인 JSON 데이터 외에도 쿠키 같은 인증 정보를 포함해서 다른 출처의 서버로 전달할 때 CORS의 세 가지 요청 중 하나인 인증된 요청으로 동작된다는 말이고 또한 기존의 단순 요청이나 예비 요청과는 다른 인증 형태로 통신하게 된다.
- 클라이언트에서 인증 정보를 보내도록 설정하기
브라우저가 기본적으로 제공하는 요청 API 들은 별도의 옵션 없이는 브라우저의 쿠키와 같은 인증과 관련된 데이터를 함부로 요청 데이터에 담을 수 없게 되어있다.
그렇기 때문에 필요에 따라서 인증 정보를 담아서 보내야 할 경우에는 credentials라는 옵션을 추가해야 한다.
해당 옵션에는 3가지의 값을 사용할 수 있는데 하나씩 살펴보면 아래와 같이 있다.
- same-origin(default): 같은 출처 간 요청에만 인증 정보를 담을 수 있다.
- include: 모든 요청에 인증 정보를 담을 수 있다.
- omit: 모든 요청에 인증 정보를 담지 않는다.
위와 같은 별도의 설정을 하지 않으면 인증 정보는 서버에게 전송할 수 없다.
- 서버에서 인증된 요청에 대한 헤더 설정하기
서버에서도 인증된 요청에 대해 일반적인 CORS 요청과 다른 설정이 필요하게 된다.
먼저 응답 헤더의 Access-Control-Allow-Credentials는 true로 설정해줘야 한다.
그다음 응답 헤더의 Access-Control-Allow-Origin, Access-Control-Allow-Methods, Access-Control-Allow-Headers의 값에는 와일드카드 문자("*")를 사용할 수 없다.
와일드카드("*")를 사용하게 되면 모든 출처에 대한 접근을 허용하게 되는데 인증 정보는 매우 민감한 정보이기 때문에 정확한 출처를 명시하여 제한을 시켜줘야 한다.
만약 위의 헤더 설정을 지키지 않았을 경우 CORS 에러 메시지를 접하게 된다.
참고 자료
'CS > 네트워크' 카테고리의 다른 글
[네트워크] - Proxy (0) | 2024.09.13 |
---|---|
[네트워크] - SSL (0) | 2024.09.13 |
네트워크 - REST API, RESTful (0) | 2024.07.17 |
네트워크 - 로드 밸런싱(Load Balancing) (0) | 2024.07.14 |
네트워크 - DNS (0) | 2024.07.13 |