티스토리 뷰
토이프로젝트를 진행하는 도중에 처음으로 내가 API를 만들어 ajax, Fetch를 통해서 네트워크 통신을 하려고 하였다.
그런데 웬걸 크롬에 개발자 도구에서 콘솔(console) 창을 보았더니 무슨 무서운 빨간색 글씨가 있었다...
이 에러는 CORS에 대한 에러이고 앞으로도 CORS를 제대로 모르면 이러한 문제를 매일 매번 직면할 것 같아서
이번에 왜 이러한 보안 정책이 생겼고 어떻게 해결해 나아갈지 작성해 보려고 한다.
CORS 모르는 사람도 재밌게 볼 수 있도록 작성해 볼게요~~
CORS 란 무엇인가?
Cross-Origin Resource Sharing 으로써 한국어로 직역하면 다른 출처 간 출처 리소스 공유라고 해석할 수 있다.
엥? 다른 출처 간 리소스 공유? 출처는 뭐지? 뭘 공유한다는 건데?? 왜 빨간색만 뜨는 건데!!!!?라고 생각하였다.
지금으로선 CORS의 정의만 가지고 무슨 느낌인지 모르겠다.. 이 포스팅을 읽음으로써 CORS가 무엇인지 알려주겠습니다.
HTTP 헤더 기반으로, 브라우저가 리소스 로드를 허용해야 하는 자체 출처 이외의 모든 출처(도메인, 포트)를 서버가 표시할 수 있도록 합니다.
- 출처 : mdn
정확히 정리하자면 CORS는 웹 애플리케이션의 보안을 강화하기 위해 브라우저에서 적용되는 정책이라고 합니다.
웹 브라우저는 보안상의 이유로 스크립트가 동일 출처(Same-Origin)에서만 리소스를 요청하도록 제한하는 겁니다.
그래서 CORS 설정을 해주면 자체 출처 이외의 설정한 모든 출처와 리소스를 요청을 할 수 있다는 것입니다.
동시에 알아야 하는 동일 출처 정책 (SOP)
CORS를 알기 위해서 또 알아야 할 한 가지인 동일 출처 정책인 SOP (Same-Origin Policy)라는 것을 알아야 합니다.
그림을 먼저 보면 같은 A domain에서는 같은 출처(Origin)를 가지기 때문에 서버에서 리소스를 받아 올 수 있는 것입니다.
반대로 다른 출처인 B domain에서 보면 서버에서 리소스를 받아 올 순 있겠지만 브라우저가 동일 출처 정책인 SOP를 체크하여 해당 리소스와 B 웹페이지와의 상호작용을 차단하는 것입니다.
아하 SOP 정책은 이렇게 동일 출처일 때는 리소스를 받아오고 다른 출처일 때는 브라우저가 차단하는 정책이구나라고 이해하시면 됩니다.
그래서 다른 출처, 동일 출처가 뭔데?
우선 출처를 알기 위하여 그림으로 먼저 보겠습니다.
- Protocol : http, https 등
- Host : 사이트 도메인
- port : 사이트 포트번호
- Path : 사이트 내부 경로
- Query String : GET형태 요청의 매개변수
이때 출처는 Protocol과 Host, Port를 합친 것을 말합니다. 즉, Origin = Protocol + Host + port
우리는 브라우저의 개발자 도구를 열어 보고 콘솔(console) 창에서 코드를 직접 입력하여 눈으로 확인해 봅시다.
console.log(location.origin);
// https://aoxx.co.kr
Tip) 윈도우 : F12 , 맥 : command + option + i
다들 현재 있는 곳에 위에 있는 사진처럼 프로토콜 + 도메인으로만 나오지 않으신가요!?
그렇다면 해당 위치 origin을 가진 곳(https://aoxx.co.kr)에서 같은 origin을 가진 곳(https://aoxx.co.kr)에게 요청하면 동일 출처라고 말하게 되는 것이고,
origin을 가진 곳(https://aoxx.co.kr)에서 다른 origin을 가진 곳(https://api.aoxx.co.kr)에게 요청하면 다른 출처로 인식한다는 것이다. 이 경우에는 CORS 정책으로 인해 다른 출처에 있는 것은 브라우저가 요청을 차단할 수 있습니다.
그래서 서버 측에서 CORS 정책을 허용해 주어야 해당 요청이 성공할 수 있는 것입니다.
동일 출처와 다른 출처 구분
요청한 Origin : https://aoxx.co.kr 일 경우
요청받은 URL | 출처 | 형태 |
https://aoxx.co.kr/manage | 동일 | 프로토콜, 호스트, 포트 번호 동일 |
https://aoxx.co.kr/manage?page=1 | 동일 | 프로토콜, 호스트, 포트 번호 동일 |
http://aoxx.co.kr/manage | 다른 | 프로토콜이 다름(http ≠ https) |
https://api.aoxx.co.kr | 다른 | 호스트가 다름 |
https://www.naver.com | 다른 | 호스트가 다름 |
https://aoxx.co.kr:8080/ | 다른 | 포트 번호가 다름 (443 ≠ 8080) |
어떻게 다른지 알겠나요?
뭔가 현재 독자분들 표정일 것 같은 느낌입니다 ㅎㅎㅎㅎㅎ
URL을 보고 무엇이 다른지 천천히 보면 아 이게 없거나 다르면 다른 출처구나라고 이해하실 겁니다.
왜 CORS가 필요한 것인가?
위에 보신 SOP 정책처럼 말고 그냥 아무 때나 호출하고~ 사용하고~ 하면 될 것인데 귀찮게 시리 브라우저는 왜 같은 출처만 사용하게 하고 왜 차단하는 것일까요?
차단하지 않으면 외부에 있는 개발자, 해커들도 서버에 있는 리소스를 막 사용할 수 있는 것 아닌가?? 그럼 보안은? 내 데이터는? 즉, 내 서버에 있는 리소스는 외부에 사람에게 다 공개가 되거나 수정이 될 위험이 있다는 것이죠....
그럼 CORS는 다른 출처 간 리소스 공유하고 했으니 이것을 설정하게 되면 다른 출처에 있는 웹과 서버는 리소스 교환이 이루어질 수 있다는 것이다!!! 그래서 SOP에 막 차단하는 것에 비해 CORS는 우리에게 해결책과 같은 구원 기술이었던 것이다!
SOP 정책으로 인한 차단 (CORS 설정 X)
CORS 설정 후
CORS는 어떻게 작동하는 것일까?
1. 예비 요청(Preflight Request)
브라우저는 요청을 바로 보내지 않고 예비 요청과 본 요청으로 나누어서 서버에 전송합니다.
이때 브라우저가 본 요청을 보내기 전에 먼저 보내는 예비 요청을 Preflight라고 불리며, HTTP 메서드 중 OPTIONS를 사용합니다.
먼저 보내는 예비 요청의 역할은 본 요청을 보내기 전에 브라우저 스스로 해당 요청을 보내는 것이 안전한지 확인하는 용도로 사용합니다.
아래와 같이 흐름도를 보면 이해하실 겁니다.
이와 같이 브라우저는 헤더에 실제 요청할 메서드, 헤더들을 작성하고 미리 서버에 예비로 보내봅니다.
그래서 Access-Control-Request를 사용하여 헤더에 태우는 것이고
서버는 이 예비 요청에 대한 응답으로 어떤 것을 허용하고 차단하고 있는지에 대한 것을 응답헤더로 내려줍니다.
브라우저는 서버가 내려준 Access-Control-Allow를 확인한 후 차단할지 본 요청을 보낼지 기능을 수행하게 됩니다.
2. 단순 요청 (Simple Request)
1번에서 본 예비 요청과 전반적인 형태는 같습니다. 하지만 예비 요청의 존재하는지 안 하는지의 차이입니다.
그런데 단점으로는 아무 때나 이 단순 요청 (Simple Request) 사용할 수 있는 것도 아니고 까다로운 특정 조건을 만족해야 하는 경우에만 예비요청을 생략할 수 있게 되었습니다.
- 요청의 메서드는 GET, POST, HEAD 중 하나여야 한다.
- Accept, Accept_Language, Content-Language, Content-Type, DPR, Downlink, Save-Data, Viewport-width, Width만 헤더에 사용해야 한다.
- 만약 Content-Type를 사용하는 경우에는 application/x-www-form-urlencoded, multipart/form-data, text/plain만 허용된다.
2번에 헤더에 Authorization과 같은 인증에 관련된 것도 헤더에 사용 못한다니 되게 까다롭습니다.
또한, 3번에서는 대부분의 API는 application/json을 Content-Type을 가지도록 설계되기 때문에 사용하기 까다롭습니다.
3. 인증된 요청 (Credentialed Request)
해당 요청은 다른 출처 간 네트워크 통신에서 조금 더 보안을 강화하고 싶을 때 사용하는 방법입니다.
기본적으로 브라우저의 비동기 리소스 요청인 Fetch API는 기본적으로 브라우저의 쿠키 정보나 인증과 관련된 헤더를 요청에 자동으로 담지 않습니다. 하지만 Fetch API의 credentials 옵션을 사용하면 요청에 인증과 관련된 정보를 담을 수 있게 됩니다.
이를 통해 서버가 인증된 사용자를 식별하거나 보안 상의 작업을 수행할 수 있게 됩니다.
옵션 | 값 설명 |
same-origin (기본값) | 같은 출처 간 요청에만 인증 정보를 담을 수 있다. |
include | 모든 요청에 인증 정보를 담을 수 있다. |
omit | 모든 요청에 인증 정보를 담지 않는다. |
이제 독자 분들이 same-origin이나 include와 같은 옵션을 사용하여 리소스 요청에 인증 정보가 포함된다면,
이제 브라우저는 다른 출처의 리소스를 요청할 때 Access-Control-Allow-Origin만 확인하는 것이 아니라 좀 더 빡빡하게 검사를 하게 됩니다.
여기까지 달려오는데 고생 많으셨어요 지금까지 CORS가 무엇인지 이해가 되셨을 거라고 생각합니다.
아래에는 CORS 설정을 어떻게 해줄 것인지 설명해 줄 것이고 개념만 알고 싶었다면 여기까지 보시면 됩니다.
CORS를 머릿속에 저장해요~~~~~~ 아니면 여러 번 저의 글을 읽어주세요~~~~
CORS를 설정하는 방법들
1. Access-Control-Allow-Origin 세팅하기
서버에서 Access-Control-Allow-Origin 헤더에 알맞은 값을 세팅해 주는 것이다.
값을 와일드 카드인 (*) 로 세팅하게 된다면 모든 출처에서 오는 요청을 받겠다. 라는 의미이고 지금 당장은 편하게 사용할지는 모르겠지만 앞으로는 보안적 이슈가 발생할 수 있습니다.
그래서 무조건! 귀찮더라고 Access-Control-Allow-Origin : https://aoxx.co.kr/ 같이 출처를 명시에 주는 것이 좋습니다.
NginX에서 CORS 설정하기
server {
listen 80;
server_name localhost;
location / {
proxy_pass http://localhost:8080;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
# CORS 설정
add_header Access-Control-Allow-Origin *; # 허용할 Origin을 지정할 수도 있습니다.
add_header Access-Control-Allow-Methods 'GET, POST, OPTIONS'; # 허용할 HTTP 메서드 설정
add_header Access-Control-Allow-Headers 'Origin, X-Requested-With, Content-Type, Accept, Authorization'; # 허용할 헤더 설정
if ($request_method = 'OPTIONS') {
# CORS preflight request 처리
add_header Access-Control-Allow-Credentials 'true';
add_header Access-Control-Max-Age 1728000;
add_header Content-Type 'text/plain; charset=utf-8';
add_header Content-Length 0;
return 204;
}
}
}
Spring Boot에서 CORS설정
스프링 부트는 Tomcat이 내장되어 있기 때문에 Config에서 빈등록을 해주어서 사용한다.
@Configuration
public class WebConfig implements WebMvcConfigurer {
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**")
.allowedOrigins("*") // 허용할 오리진 (모든 오리진 허용은 "*")
.allowedMethods("GET", "POST", "PUT", "DELETE") // 허용할 HTTP 메서드
.allowedHeaders("*") // 허용할 헤더 (모든 헤더 허용은 "*")
.allowCredentials(true) // 쿠키 사용을 허용
.maxAge(3600); // preflight 요청 결과를 캐시할 시간 (1시간)
}
}
혹은 컨트롤러에서 어노테이션 하는 방법도 있다.
@RestController
@RequestMapping("/rest")
public class ApiController {
@CrossOrigin(origins = "*")
@GetMapping("/user")
public String userInfo(){
return "코아시스";
}
}
2. Webpack Dev Server로 리버스 프록싱하기
주로 devServer.proxy 옵션을 사용하여 리버스 프록싱을 설정합니다. 이건 거의 프론트엔트 개발자가 구축하는 용도로 사용합니다.
Webpack.config.js파일은 Webpack 빌드 도구의 설정 파일로서, 프로젝트를 빌드하고 번들링 할 때 사용합니다.
또한 일반적으로 Node.js 환경에서 사용하게 되고 react, vue 프로젝트에서도 Webpack.config.js를 사용하여 애플리케이션을 빌드 및 번들링 할 수 있습니다.
const path = require('path');
module.exports = {
// ...기타 설정...
devServer: {
contentBase: path.resolve(__dirname, 'dist'),
proxy: {
'/api': {
target: 'http://localhost:8080', // 백엔드 서버 주소
pathRewrite: { '^/api': '' }, // 프론트엔드에서의 요청 경로 변경
secure: false, // https를 사용하는 경우 false로 설정
changeOrigin: true, // 원본 주소를 변경
},
},
},
};
감사합니다.
'네트워크' 카테고리의 다른 글
[네트워크] TCP/IP 모델: OSI 7계층이 아닌 현대 네트워킹 이해하기 (0) | 2023.10.26 |
---|---|
[네트워크] HTTP 응답 상태코드: 브라우저와의 통신, 의미와 해석 (0) | 2023.10.20 |
- Total
- Today
- Yesterday
- 프로세스
- 디자인패턴
- 코딩테스트
- DBeaver
- 데이터 베이스
- git
- AJAX
- Fetch
- java
- Front
- aws
- 오라클
- 개발환경
- 프론트
- 개발
- 비동기
- JavaScript
- spring
- 템플릿
- Mac
- 네트워크
- 개발블로그
- 개발자
- Cors
- Spring Security
- 자바스크립트
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
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 | 31 |