https://www.youtube.com/watch?v=-2TgkKYmJt4&t=1162s
윗 링크 참조
이 문제를 겪고 Cors가 뭔지 알아갸 겠다는 생각이 듭니다.
Cors를 알기 전에 SOP(Same Origin Policy)를 알아둬야 합니다.
다른 출처의 리소스를 사용하는 것에 제한 하는 보안 방식
Protocol
,Host
,Port
중에 하나라도 다르면 다른 출처라고 판단합니다.
이 3가지가 같아야지만 같은 출처라고 이야기 합니다.
인터넷 익스플로우같은 경우는 Port가 출처 판단에 기여하지 않습니다.
Protocl
,Host
만 출처 판단에 기여합니다.
Port
가 달라도 Protocol
,Host
만 같으면 이것은 같은 출처이다. 라고 판단 할 수도 있습니다.
http://localhost 와 동일 출처인 url은 무엇일까요? 모두 골라주세요
- https://localhost
- http://localhost:80
- http://127.0.0.1
- http://localhost/api/cors
정답은 바로 ~ 2번 4번 입니다.
1번 https://localhost 이 http://localhost 와 다른 이유
1번의
Protocol
은 https 이기 때문에 다른 주소입니다.
2번의 http://localhost:80 와 http://localhost 가 같은 이유
2번 http
Protocol
의 기본 포트가 :80 이기 때문에 같습니다.
3번 http://127.0.0.1 이 http://localhost 와 다른 이유
사실 127.0.0.1 IP는 localhost가 맞기는 맞습니다.
하지만 브라우저 입장에서는 이거를 String Value를 서로 비교한다고 하네요.
그래서 String Value가 localhost와 127.0.0.1이 다르기 때문에
브라우저 입장에서는 이것은 다른 출처라고 판단을 한다고 합니다.
4번 http://localhost/api/cors 와 http://localhost 가 같은 이유
/api/cors 같은 경우는 추가적으로 붙는 location 이죠.
그렇기 때문에 /api 앞까지( =http://localhost) 까지 비교를 해서
이것은 동일 출처이다 ! 이렇게 판단을 합니다.
이렇게 SOP(Same Origin Policy)가 다른 출처의 리소스를 사용하는 것에 제한 하는 보안 방식 이라고 하는데,
이런 SOP를 사용을 해야 보안에 도움이 될까요?
이에 대한 대답은 예제와 함께 알아보도록 하겠습니다.
선량한 사용자가 페이스북에 로그인을 하고 서비스 사용을 합니다.
1.로그인을 하는데 있어서 페이스북에서 인증 토큰을 받아옵니다.
이 선량한 사용자는 페이스북 인증 토큰을 가지고 있습니다.
2. 그런데 해커가 선량한 사용자에게 매력적인 링크를 메일로 보내옵니다.
ex) http://hacker.ck
3. 선량한 사용자는 일단 클릭을 합니다.
http://hacker.ck
클릭을 하게 되니까, 해커가 만든 주소로 이동을 하게 되요.
해커는 스크립트로 "나는 바보다" 라는 포스트를 작성하게 하는 스크립트를 만든 상태인거에요.
http://hacker.ck
이 링크를 타고 들어 갔더니? 스크립트 내용이 실행이 되죠.
근데 아까 이야기 했듯이. 선량한 사용자가 페이스북 인증 토큰을 가지고 있어요.
그러면 해커는 이 인증토큰을 사용해, 포스트 게시 명령을 내립니다.
여기서 SOP가 강력한 위력을 발휘 합니다.
페이스북에서는 Origin 확인을 합니다.
어디서 온거지? Origin을 확인 하니까 이 요청은 http://hacker.ck 에서 보낸거에요.
그러면 facebook 입장에서는 자기 출처와 다르죠.
그래서 이 Origin은 다른 출처라고 판단하기 때문에,
즉 Cross Origin 이라고 판단하기 떄문에, SOP에 위반된다.
그래서 이 요청은 받을 수 없다고 이야기 합니다.
답은 CORS !!!!!
추가 HTTP 헤더를 사용한다네요.
프리플라이트 요청부터 보겠습니다.
1.OPTIONS 메서드를 통해 다른 도메인의 리소스에 요청이 가능한 지 확인 작업
현실 세계
친구 집에 놀러 갈때, 놀러가도 되는지전화기
로 물어보는 단계입니다.
친구 답변에 따라 놀러 갈지 , 안놀러 갈지 정해집니다.
이것이 바로 Preflight입니다.
데이터 세계
본 요청을 보내기 전에, 일단 서버한테 물어보는거에요.
어이 서버 이 요청 보내도 되니? 서버가 된다 or 안된다 대답을 합니다.
이렇게 Preflight는 서버에게 물어보는 것을OPTIONS 메서드
를 통해 확인을 합니다.
2.요청이 가능하다면 실제 요청(Actual Request)을 보낸다.
요청을 하게 되면
위 그림처럼 요청을 2번 보내게 되는겁니다.
먼저 preflight를 보내구요. 그 다음 실제 요청을 보냅니다.
사전 요청을 할때 물어보는게 있어요. 그에 맞춘 포맷이 있습니다.
PREFLIGHT REQUEST 포맷은
맨 처음 헤더에는
Origin : 요청 출처
가 들어 있어야 해요.
어디어디에서 요청을 날린 거야~ 라고 표현을 해줘야 합니다.
그다음은
Access-Control-Request-Method: 실제 요청의 메서드
가 들어 있어야 해요.
난 이런 메서드를 보낼거야~ 라고 표현을 해줘야 합니다. 그래서 이 메서드를 보내도 되니? 마지막으로
Access-Control-Request-Headers:
가 들어갑니다.
실제 요청에 추가 헤더를 뭐뭐 보낼 수 있는지 물어보는 요청입니다.
그러면 그에 대한 답변을 보낼 수 있어야 겠죠?
여기서 Access-Control-Max_Age
의 기능은 이럴 때 사용됩니다.
같은 Preflight가 여러번 요청오면 그에 따른 Response를 여러번 해야 겠죠?
같은 요청이면 캐싱을 해두고 Response를 생략합니다.
언제까지 캐싱을 할지 정하는 기능입니다.
PREFLIGHT RESPONSE는 다음과 같은 특징을 가집니다.
Simple Request는 Preflight 요청 없이 바로 본 요청을 보냅니다.
그 즉시 이것이 Cross-Origin인지 확인하는 절차를 거칩니다.
클라이언트에서는 나는 foo.example인데 이러한 ~요청을 보냅니다 서버님.
그러면 서버는.Access-Control-Allow-Origin
(여기서 별표는 모든 것을 허용한다는 의미입니다.)
즉 foo.example도 허가를 해주겠다 입니다.
만약.Access-Control-Allow-Origin : abc.example
이면 foo.example은 크로스 오리진 에러가 터집니다.
SimpleRequest로 날리면 딱 한번 요청을 하고 끝내는데 말이죠 ~
왜 굳이 Preflight로 2번 왔다리 갔다리 해야 할까요?
Answer : CORS를 모르는 서버를 위해서 입니다.
흐름을 보면서 설명을 해드릴게요.
지금 현재 보이는 흐름은 서버(받는사람)가 CORS를 모르는 서버에요.
즉. CORS에 대해서 아무런 설정이 없는 서버에요.
이것은 Preflight 없이 실제 요청을 보낼 떄 어떤 오류가 생기느냐를 한번 보는 겁니다.
Client
는 내 출처는 bom.com이라고 브라우저
에게 보냅니다.
브라우저
는 그대로 서버에게 보냅니다.
서버는 CORS를 모르기 떄문에..? 일단 그냥 해결을 합니다. 이것저것 막 하고
응답을 내렸죠.
그런데 서버 입장에서는 CORS 설정이 없기 떄문에,
ALLOW-ORIGIN: 없음 으로 되어 있습니다.
브라우저는
그제서야 서버는~
ALLOW-ORIGIN:없음 이야.. CORS 에러!!!!!!
라고 CLIENT
에게 이야기 해줍니다.
여러분 만약에 DELETE 요청이 왔는데, 서버에서 다 처리를 해놓고
ALLOW-ORIGIN : 없음 이렇게 보내버리면 CLIENT
는 에러를 확일 할텐데요.
CLIENT
는 데이터가 사라진걸 모르는 큰 문제가 생깁니다.
그래서 Preflight가 필요합니다.
그래서 Preflight 요청을 보내게 되면은 어떻게 될까요?
일단 CLIENT
가 BROWSER
에게 요청을 보내고 BROWSER
는 SERVER
에게 요청을 보냅니다.
서버 입장에서는 CORS
설정이 없으니까 ALLOW-ORIGIN: 이 없죠.
브라우저는 여기서 ERROR를 터뜨립니다.
그러면 CLIENT
는 이것은 CORS
에러 니까 다음 실제 요청을 보내지 않습니다.
그러기 때문에 서버는 안전하게 지켜집니다.
그래서 Preflight는 CORS를 모르는 서버를 위해서
정말로 필요한 작업이라고 생각해주세요.
만약에 쿠키나 JWT토큰을 CLIENT
에서 자동으로 담아서 보내고 싶을 때,
Credentialed Request를 Include
를 하게 되면은 서버까지 전달 하게 됩니다.
여기서 중요한 것은,
서버측에서 마찬가지로 이 설정을 해줘야 해요.
Access-Control-Allow-Credntials:true
로 집어 넣어줘야 합니다.
그래야지만 CLIENT
측에서 보내는 것을 받을 수 있습니다.
Access-Control-Allow-Credntials:true
로 설정한 순간
Access-Control-Allow-Origin : * 로 설정해주면 절대 안되요.
모든 출처를 허용하는 순간 ERROR가 터집니다.
그래서 정확한 ORIGIN을 줘야 합니다.
- 프론트 프록시 서버 설정을 살짝 바꿔 주는거에요
어떤 식으로 바꿔주게 될까요?
프런트 서버, 우리가 뭐 npm run serve 형식으로 따로 띄우자나요?
브라우저가 거기 프런트 서버한테 그 서버(npm run servce)로 보내는 거에요.
그러면 포트도 같은 포트로 보내겠죠?
브라우저 입장에서는 ORIGIN 가 TARGET 이 같기 때문에,
이것은 SAME ORIGIN 형식으로 보내집니다.
그런데 프런트 서버에서는 살짝 바꿔서
추가로 /api 요청이 있는 것은 TARGET을8080포트
로 보내줘 하면은
프런트 서버에서 보내주기 때문에 , 브라우저 입장에서는
(CORS가 브라우저에서 터진다고 이야기 했잖아요?)
이것은 same origin이기 때문에 ERROR가 터지지 않아요.
이 방법은 귀찮아 보입니다.
우리는 정말로 위대한 프레임워크 . 스프링 부트를 이용하고 있는데..
굳이 헤더를 설정 해줘야 하나 ? 의문이 들을 수 있어요.
스프링 부트를 어떻게 설정을 하냐. 라이브 코딩 형식으로 보여드리겠습니다.
크로스 오리진(CORS)가 터지면 어떻게 되냐면요.
먼저 요청을 보냈는데요. OPTIONS가 먼저 보내집니다. 그다음에 터져요.
서버에서 실행을 해보면, GET 요청을 보냈는데, 브라우저는 터지구요.
서버는 잘 받아 집니다.
이러면 안되겠죠? 본론으로 들어와서
이거를 어떻게 해결을 할까요? 프론트에서 프록시 설정을 해주면 됩니다.
이러면 브라우저 입장에서는 CORS를 모르죠.
한편 컨트롤러에서 해결하려고 하면, @CrossOrigin을 붙이면 됩니다.
만약 Origin 설정을 안해주면 default Origin값이 모든것이기 때문에
왠만해선 Origin 설정을 해줍시다.
아까 allowCredetials = true로 해주면 오류가 난다고 했죠?
오류가 터집니다.
@CrossOrigin은 컨트롤러마다 다 붙여야 하는 단점이 있습니다.
그래서 전역적으로 설정하는 방법이 있습니다. Configuration을 만들어줍니다.
WebMvcConfigurer를 가져와서 설정해줍니다.