minzzl
CORS 본문
다음과 같은 오류를 만나본적이 있나요?
CORS를 알기전에 SOP라는 개념을 짚고 넘어가는 것이 좋습니다.
SOP(Same Origin Policy)
- 다른 출처의 리소스를 사용하는 것에 제한하는 보안 방식
출처란 무엇일까요?
아래의 그림에 잘 나와있습니다만, URL의 Protocol, Host, Port를 통해 같은 출처인지 다른 출처인지 판단할 수 있습니다.
즉 Protocol, Host, Port가 모두 같아야만 같은 출처라고 할 수 있는데요, 그렇다면 http://localhost 와 동일 출처인 URL은 다음 중 무엇일까요?
1. https://localhost
2.http://localhost:80
3.http://127.0.0.1
4.http://localhost/api/cors
정답은 2번과 4번입니다.
[풀이]
1. https로 다른 프로토콜이므로 다른 출처입니다.
2. http 기본 port는 80 포트이므로 http://localhost 에서는 포트 표시가 생략되었을 뿐 동일한 출처입니다.
3. localhost의 IP는 127.0.0.1이 맞지만, 브라우저 입장에서는 srting value로 비교하므로 다른 출처입니다.
4. /api/cors는 추가적으로 붙는 location 이므로 동일 출처입니다.
그렇다면 왜 다른 출처의 리소르를 사용하는 것에 대한 제한을 하는 SOP를 사용해야만 보안에 도움이 되는 것일까요?
이에 대한 답은 예제와 함께 사용하겠습니다.
1. 페이스북에 로그인 이후 사용하는 선량한 사용자가 있습니다. (페이스북에서 인증 토큰을 받아온다)
2. 해커가 선량한 사용자에게 링크를 메일을 통해 보냅니다.
3. 선량한 사용자가 해당 링크를 클릭하니 http://hacker.ck 로 이동하게됩니다.
4. http://hacker.ck 에서 선량한 사용자의 토큰을 이용해 페이스북에서 나는 바보다 포스트를 게시합니다.
이 때에 SOP가 정말로 위력을 발휘합니다.
facebook 입장에서는 이 요청이 어디로 부터 온 것인지 origin을 확인합니다.
이 요청은 hacker.ck 로부터 온 것이기 때문에 본인의 출처와 다른 것을 확인 합니다. 따라서 facebook은 SOP에 위반되기때문에 해당 요청을 받아들이지 않습니다.
그렇다면 다른 출처의 리소스가 필요하다면 어떻게 해야할까요?
CORS 를 사용하면 됩니다.
CORS (Cross-Origin Resource Sharing)
- 추가 HTTP 헤더를 사용하여, 한 출처에서 실행 중인 웹 앱플리케이션이 다른 출처의 선택한 자원에 접근할 수 있는 권한을 부여하도록 브라우저에 알려주는 체제
CORS 접근제어 시나리오
1. 단순 요청(Simple Request)
프리플라이트 없이 요청을 보내면서 그 즉시 이것이 cross origin인지 확인하는 절차입니다.
Simple request를 보내기 위해서는 다음과 같은 조건이 필요합니다.
1. GET, POST, HEAT 메서드 중에 하나여야합니다.
2. content type은 application/x-www-form-urlencoded, multipart/form-data, text/plain 이어야합니다.
3. 헤더는 Accept, Accept-Language, Content-Language, Content-Type 만 허용합니다.
2. 프리플라이트 요청 (Preflight Request)
쉽게 말하자면 사전 작업입니다.
예를 들어 친구 집에 놀러가고자 할 때 친구한테 먼저 물어봐야겠죠? 그 답변에 따라 정하게 되는데 이와 프리플라이트가 유사합니다.
본 요청을 보내기전에 일단 서버에게 이 요청을 보내도 되는지 물어보는 것을 의미합니다. 이는 options 메소드를 통해서 진행되며 요청을 하게 되면 프리플라이트 요청 후 요청이 가능한 상황이면 서버에게 요청을 보냅니다.
프리플라이트의 request format은 다음과 같습니다.
- Origin : 이 요청은 어디서 부터 날라가는 것이라는 표현
- Access-Control-Request-Method : 나는 이 메서드를 보낼건데... 보내도 되니?
- Access-Control-Request-Header : 실제 요청의 추가 헤더를 뭐뭐 보낼 수 있는지 물어보는 요청..
서버 측에서 보내는 프리플라이트의 response format은 다음과 같습니다.
- Access-Control-Allow-Origin : 이 origin은 허가가 되어있어
- Access-Control-Allow-Method : 이 Method은 허가가 되어있어
- Access-Control-Allow-Header : 이 Header들은 허가가 되어있어
- Access-Control-Max-Age : Preflight 응답 캐시 기간
Access-Control-Max-Age의 경우 프피플라이트를 보내게 되면 실질적으로 2번 요청이 보내지는것이기 때문에(사전 요청, 실제 요청) 리소스적으로 좋지 않습니다. 따라서 브라우저는 프라플라이트 응답에 대해 캐싱을 해두고 똑같은 요청을 보낼 때 캐싱을 확인 후 프리플라이트 샂너 요청을 보내지 안고 본 요청을 보내는데, 이 때의 캐시 기간을 의미합니다.
simple requst로 가능한데 왜 프리플라이트가 필요한 것일까요?
그것은 CORS를 모르는 서버를 위해서입니다.
아래의 그림을 보면서 알아보겠습니다.
서버가 CORS를 모르는 서버입니다. 즉 CORS 관련한 설정이 없는 서버입니다. client에서 아 이것은 actual request, 즉 프라이플라이트 없이 요청을 보낼 때 문제가 발생하는 것을 확인 할 수 있는 예제입니다.
서버 입장에서는 CORS 설정이 없기 때문에 당연히 allow-origin이 없습니다. 이를 확인한 브라우저가 CORS 에러를 내뱉어줍니다. 만약에 요청이 심플한 get 요청이 아니라 delete 요청이 가게 되면, 서버입장에서는 디비를 다 지우고 내 뱉었는데 CORS를 내뱉는 것입니다.
따라서 프리플라이트가 필요한 것입니다. 사전 요청에서 이미 CORS 에러가 났기 때문에 서버는 행동을 따로 하지 않습니다.
3. 인증 정보 포함 요청 (Credentialed Request)
인증 관련 헤더를 포함할 때 사용하는 요청입니다. 쿠키나 jwt와 같은 토큰을 클라이언트에서 자동으로 담아서 보내고 싶을 때 credential을 include 하게 되면은 서버 측까지 전달해집니다. 여기서 중요한 것은 서버측에서는 Access-Control-Allow-Credentials를 true로 해주어야 클라이언트 쪽에서 보내는 것을 받을 수 있습니다.
CORS 해결하기
1. 프론트 프록시 서버 설정
브라우저 입장에서는 프론트 서버한테 보내는 것입니다. 당연히 같은 포트일 것이기 때문에 same origin 입니다.
근데 프론트 서버에서는 살짝 바꾸어 만약 /api 요청이 있는 것은 target은 8080으로 보내줘라고 하면 프론트 서버에서 보내주기 때문에 브라우저 입장에서는 Same origin이므로 CORS가 터지지 않습니다.
2. 직접 헤더에 설정해주기
3. 스프링 부트 이용하기
'프로젝트 > 웹' 카테고리의 다른 글
피그마 WebGL 오류 (0) | 2023.02.14 |
---|---|
RESTful API (0) | 2022.10.10 |
NginX (0) | 2022.10.09 |
브라우저 저장소 local storage, session storage, 쿠키 (1) | 2022.10.09 |