일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
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 |
- 데이터베이스
- mutable
- proxy
- binarySearch
- immutable
- 얕은복사
- Database
- ERD
- 프록시서버
- java
- 알고리즘
- 깊은복사
- 불변객체
- reverse프록시
- index
- NoSQL
- acid
- RDBMS
- forward프록시
- 이진탐색
- 자료구조
- 방어적복사
- 인덱스
- 정규화
- transaction
- 조인
- Today
- Total
jacketList
SOP 와 CORS 본문
SOP가 무엇인가요?
same object policy의 약자로 동일 출처 정책을 의미한다.
동일 출처 정책? -> 같은 출처(Origin)의 리소스만 공유가 가능하다는 정책
출처(Origin)이 뭔가요?
출처는 프로토콜(Protocol or Scheme), 호스트(Host), 포트(Port)로 구성되어 있고 모두 같아야 동일한 출처라고 말한다.
1번은 프로토콜이 다르고 3번은 호스트가 다르다
2번은 http의 기본 포트가 80이라 생략이 가능하므로 같은 출처이고
4번은 api/cors 부분은 path이므로 같은 출처이다.
동일 출처 정책(SOP)는 어떤 출처(Origin)에서 불러온 문서(Document)나 스크립트(Script)가 다른 출처에서 가져온 리소스와 상호작용 하는 것을 제한하는 보안 방식이다.
이러한 제약을 통해 CSRF(Cross-site Request Forgery)나 XSS(Cross-site Scripting)등의 공격을 막을 수 있다.
예전의 웹은 프론트엔드 레이어와 백엔드 레이어를 별도로 구성하지 않는 경우가 많았다.
대신 서버가 직접 요청 처리의 결과를 HTML문서로 만들어 클라이언트에게 보내주었다.
이는 모든 처리가 같은 도메인 내에서 일어난다는 뜻이고 다른 출처로 요청을 보낼 필요가 없었다는 것을 의미한다.
하지만! 시간이 지나면서 웹 기술로 할 수 있는 것들이 많아졌고 자연스럽게 다른 출처로 요청하고 응답을 받아오는 일이 빈번해 졌다.
프론트엔드 레이어와 API 서버 레이어를 따로 구성하여 웹 프론트엔드에서 다른 도메인에 위치한 API서버로 요청을 넣어야 하는 상황이 생겼고 이를 해결하기 위해 등장한 것이 CORS다
CORS(Cross-Origin-Resources Sharing, 교차 출처 리소스 공유)란?
쉽게 말하면 서로 다른 도메인간 자원 공유를 가능하게 해주는 것이다. 그럼 어떻게 CORS정책을 따르게 하여 SOP정책을 회피할 수 있을까? 먼저 CORS가 동작되는 과정을 살펴보면
브라우저의 CORS 기본 동작
- 클라이언트에서 HTTP요청의 헤더에 Origin을 담아 전달
- 웹은 기본적으로 HTTP프로토콜을 이용하여 서버에 요청을 보내는데
- 이때 브라우저는 요청 헤더에 Origin이라는 필드에 출처를 함께 담아 보내게 된다.
- 서버는 응답헤더에 Access-Control-Allow-Origin을 담아 클라이언트로 전달한다.
- 이후 서버가 요청에 대한 응답을 할 때 응답 헤더에 Access-Control-Allow-Origin이라는 필드를 추가하고 값으로 '이 리소스를 접근하는 것이 허용된 출처 url'을 보낸다.
- 클라이언트에서 Origin과 서버가 보내준 Access-Control-Allow-Origin을 비교한다.
- 이후 응답을 받은 브라우저는 자신이 보냈던 요청의 Origin과 서버가 보내준 응답의 Access-Control-Allow-Origin을 비교해본 후 차단할지 말지를 결정한다.
- 만약 유효하지 않다면 그 응답을 사용하지 않고 버린다. -> CORS에러
- 위의 경우에는 출처가 일치하므로 리소스를 문제없이 가져오게 된다.
CORS의 동작에는 세가지 시나리오가 있다.
예비요청(Preflight Request)
단순요청(Simple Request)
인증정보 포함 요청(Credentialed Request)
각 요청에 대해서 알아보자
예비요청(Preflight Request)
브라우저는 요청을 보낼때 한번에 보내지 않고, 먼저 예비 요청을 보내 서버와 잘 통신 되는지 확인한 후 본 요청을 보낸다.
즉, 예비 요청의 역할은 본 요청을 보내기 전에 브라우저 스스로 안전한 요청인지 미리 확인하는 것 이다.
이때 브라우저가 예비요청을 보내는 것을 preflight라 부르며, 이 예비요청의 HTTP메소드를 GET이나 POST가 아닌 OPTIONS라는 요청이 사용된다는 것이 특징이다.
자바스크립트로 api 요처을 보낸다고 가정해보면
- fetch() 메소드를 통해 리소스를 받아오려 한다.
- 브라우저는 서버로 HTTP OPTIONS 메소드로 예비 요청(Preflight)을 먼저 보낸다.
- Origin 헤더에 자신의 출처를 넣는다.
- Access-Control-Request-Method헤더에 실제 요청에 사용할 메소드를 설정한다.
- Access-Control-Request-Header헤더에 실제 요청에 사용할 헤더들을 설정한다.
- 서버는 예비 요청에 대한 응답으로 어떤 것을 허용하고 어떤 것을 금지하고 있는지에 대한 헤더 정보를 담아서 브라우저로 보내준다.
- Access-Control- Allow-Origin: 허용되는 Origin들의 목록을 설정한다.
- Access-Control- Allow-Methods: 허용되는 메소드들의 목록을 설정한다.
- Access-Control- Allow-Headers: 허용되는 헤더들의 목록을 설정한다.
- Access-Control-Max-Age: 해당 예비 요청이 브라우저에 캐시 될 수 있는 시간을 초 단위로 설정한다.
- 이후 브라우저는 보낸 요청과 서버가 응답해준 정책을 비교하여, 해당 요청이 안전한지 확인하고 본 요청을 보내게 된다.
- 서버가 본 요청에 대한 응답을 하면 최종적으로 이 응답 데이터를 자바스크립트로 넘겨준다.
※예비 요청의 문제점과 캐싱
본 요청을 보내기 전 OPTIONS메소드로 예비 요청을 보내 보안을 강화하는 취지는 좋지만 결국 실제 요청에 걸리는 시간이 늘어나게 되어 어플리케이션 성능에 영향을 미치는 단점이 있다.
특히 수행하는 API 호출 수가 많아질수록 예비 요청으로 인해 서버 요청을 배로 보내게 되니 비용적인 측면에서 폐가 될 수 있다. 따라서 브라우저 캐시(Cache)를 이용해 Access-Control-Max-Age 헤더에 캐시될 시간을 명시해 주면, Preflight 요청을 캐싱 시켜 최적화를 시켜줄 수 있다.
위와 같이 요청에 대한 응답이 캐싱되어 있다면, 에비 요청을 서버로 보내지 않고 캐시된 응답을 사용한다.
단순요청(Simple Request)
단순요청은 예비요청(Preflight)을 생략하고 바로 서버에 직행으로 본 요청을 보낸 후, 서버가 이에 대한 응답의 헤더에 Access-Control-Allow-Origin헤더를 보내주면 브라우저가 CORS정책 위반 여부를 검사하는 방식이다.
다만 단순한 만큼 특정 조건을 만족하는 경우에만 예비 요청을 생략할 수 있다.
대표적으로 3가지 경우를 만족할 때 만 가능하다.
- 요청의 메소드는 GET,HEAD,POST 중 하나여야 한다.
- Accept, Accept-Language, Content-Language, Content-type, DPR, DownLink, Save-Data, Viewport-Width, Width 헤더일 경우에만 적용된다.
- Content-Type헤더가 applicaition/x-www-form-urlencoded, multipart/form-data, text/plain 중 하나여야 한다. 아닐경우 예비 요청으로 동작한다.
이처럼 다소 까다로운 조건들이 많기 때문에, 위 조건을 모두 만족하여 단순 요청이 일어나는 상황은 드물다고 보면 된다.
왜냐하면 대부분 HTTP API요청은 text/xml 이나 applicaiton/json으로 통신하기 때문에 3번 Content-Type이 위반되기 때문이다.
따라서 대부분의 API요청은 그냥 예비 요청(Preflight)으로 이루어진다 라고 이해하면 된다.
인증된 요청(Credentialed Request)
클라이언트에서 서버에게 자격 인증 정보(Credential)를 실어 요청할 때 사용되는 요청이다.
일반적인 JSON데이터 외에도 쿠키 같은 인증 정보를 포함해서 다른 출처의 서버로 전달할 때 인증된 요청으로 동작되며, 기존의 단순 요청이나 예비 요청과는 살짝 다른 인증 형태로 통신하게 된다.
자격 인증 정보?
세션 ID가 저장되어 있는 쿠키(Cookie) 혹은 Authorization헤더에 설정하는 토큰 값 등을 일컫는다.
클라이언트에서 인증 정보를 보내도록 설정할 때 요청에 인증과 관련된 정보를 담을 수 있게 해주는 옵션으로 credentials를 사용한다.
아래와 같이 3가지 값을 사용할 수 있다.
same-origin(기본값): 같은 출처 간 요청에만 인증 정보를 담을 수 있다.
include: 모든 요청에 인증 정보를 담을 수 있다.
omit: 모든 요청에 인증 정보를 담지 않는다.
이처럼 별도의 설정을 해주지 않으면 인증 정보는 절대로 자동으로 서버에게 전달되지 않는다.
서버에 인증된 요청을 보내는 방법으로는 아래와 같은 방법이 있다.
// fetch 메서드
fetch("https://example.com:1234/users/login", {
method: "POST",
credentials: "include", // 클라이언트와 서버가 통신할때 쿠키와 같은 인증 정보 값을 공유하겠다는 설정
body: JSON.stringify({
userId: 1,
}),
})
// axios 라이브러리
axios.post('https://example.com:1234/users/login', {
profile: { username: username, password: password }
}, {
withCredentials: true // 클라이언트와 서버가 통신할때 쿠키와 같은 인증 정보 값을 공유하겠다는 설정
})
// jQuery 라이브러리
$.ajax({
url: "https://example.com:1234/users/login",
type: "POST",
contentType: "application/json; charset=utf-8",
dataType: "json",
xhrFields: {
withCredentials: true // 클라이언트와 서버가 통신할때 쿠키와 같은 인증 정보 값을 공유하겠다는 설정
},
success: function (retval, textStatus) {
console.log( JSON.stringify(retval));
}
});
출처: https://inpa.tistory.com/entry/WEB-📚-CORS-💯-정리-해결-방법-👏 [Inpa Dev 👨💻:티스토리]
서버에서는 인증된 요청에 대한 헤더를 설정해야 한다.
일반적인 CORS요청과 다르게
- Access-Control-Allow-Credentials 항목을 true로 설정해야 한다.
- Access-Control-Allow-Origin의 값에 와일드카드 문자 ' * ' 는 사용할 수 없다.
- Access-Control-Allow-Methods 의 값에 와일드카드 문자 ' * ' 는 사용할 수 없다.
- Access-Control-Allow-Headers 의 값에 와일드카드 문자 ' * ' 는 사용할 수 없다.
즉 인증정보는 민감한 정보이기 때문에 출처를 정확하게 설정해 주어야 한다.
References
https://xionwcfm.tistory.com/235
https://github.com/devSquad-study/2023-CS-Study/blob/main/Network/network_sop_and_cors.md
'Cs > 네트워크' 카테고리의 다른 글
TCP/UDP (1) | 2023.11.21 |
---|---|
프록시 서버[Proxy Server] (5) | 2023.11.19 |
쿠키(Cookie)와 세션(Session) (0) | 2023.11.18 |