CORS는 함축 단어로써 이를 풀면 Cross-Origin Resourse Sharing 이라는 단어로 이루어져 있다.
이는 "교차 출처 리소스 공유 정책"이라고 해석할 수 있다.
교차 출처는 엇갈린 다른 출처를 의미한다.
출처(origin)
출처는 Protocol과 Host 그리고 Post까지 모두 합친 URL을 의미한다. 아래를 통해 현재 origin을 알 수 있다.
console.log(location.origin);
동일 출처 정책 (Same-Origin Policy) - SOP
이 정책은 단어 그ㄹ대로 동일한 출처에 대한 정책을 말한다. -> 동일한 출처에서만 리소스를 공유.
=> 동일 출처 서버에 있는 리소스는 자유로이 가져올 수 있지만, 다른 출처 서버에 있는 리소스는 상호작용이 불가능하다.
출처가 다른 두 어플이 자유로이 소통할 수 있는 환경은 위험한 환경(해커가 심어놓은 코드가 실행되어 개인정보를 가로챌 수 있어서)인데, 이런 악의적인 경우를 방지하기 위해, SOP 정책으로 동일하지 않는 다른 출처의 스크립트가 실행되지 않도록 브라우저에서 사전에 방지를 하는 것이다.
CORS&SOP의 출처는 url의 구성요소 앞서 얘기한 3가지만 동일하면 동일 출처로 판단하다.
출처 비교와 차단은 서버가 아닌 브라우저에서 한다. -> 프록시 서버로 CORS 에러로부터 자유로워지자.
교차 출처 리소스 공유(Cross-Origin Resource Sharing) - CORS
단어 그대로 다른 출처의 리소스 공유에 대한 허용/비허용 정책이다.
개발을 하다보면 기능상 다른 출처 간의 상호작용을 해야 하는 케이스도 있고, 다른 회사 서버 API를 이용해야 할 때가 존재한다. 그래서 CORS 정책을 허용하는 리소스에 한해 다른 출처를 받아들일 수 있다.
CORS는 SOP 정책을 위반해도 CORS 정책에 따르면 다른 출처의 리소스라도 허용한다는 뜻이다.
클라이언트에서 http 요청의 헤더에 origin을 담아서 전달하면, 서버는 응답헤더에 Access-Control-Allow-Origin을 담아서 클라이언트로 전달한다. 응답헤더에 이 필드를 추가하는 이유는 이 리소스를 접근하는 것이 허용된 출처 url 을 내보내기 위해서이다.
클라이언트에서는 다시 origin과 서버가 보내준 Access-Control-Allow-Origin을 비교해서 차단할지의 여부를 결정하고, 유효하지 않다면 그 응답을 사용하지 않고 버린다.
=> CORS 해결책은 서버의 허용이 필요. 이건 백엔드 개발자가 고쳐야 될 부분이다. -> 클라인언트에서 미리 origin 헤더값을 위조해도 브라우저에서 이를 감지해서 차단하기 때문에 불가능하다.
CORS 작동 방식 3가지
CORS를 정복하기 위해서 서버&클라이언트 관계성이 중요하다. -> 쿠키나 토큰과 같은 인증 데이터를 다른 출처의 서버에 요청을 해야 한다면 이 섹션의 지식은 필수적이다.
1. 예비 요청 (Preflight Request): 브라우저는 요청을 보낼 때 바로 보내지 않고, 먼저 예비 요청을 보내 서버와 잘 통신되는지 확인하고 본 요청을 보낸다. - 미리 확인, 이 예비요청의 http 메소드를 GET이나 POST가 아닌 OPTIONS라는 요청이 사용된다는 것이 특징.
하지만, 예비 요청이 보안을 강화하는 목적의 취지에는 좋지만, 실제 걸리는 시간이 늘어나게 되어 성능이 저하된다. 예비 요청 캐싱 기간은 파이어폭스는 하루 정도, 크로미엄 기반 브라우저는 2시간이 최대..
2. 단순 요청 (Simple Request): 예비 요청을 생략하고 바로 서버에 직행으로 본 요청을 보내고, 서버가 이에 대한 응답의 헤더에 Access-Control-Allow-Origin 헤더를 보내주면 브라우저가 CORS정책 위반 여부를 검사라는 방식이다. -> 특정 조건을 만족하는 경우에만 예비 요청을 생략할 수 있음.
요청의 메소드가 GET, HEAD, POST 중 하나여야 하고, 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 중 하나여야 하는데 해당하지 않는 경우 예비 요청을 보낸다. -> 대부분 API 요청은 예비 요청으로 이루어진다고 이해하자.
3. 인증된 요청 (Credentialed Request): 클라이언트에서 서버에게 자격 인증 정보를 실어 요청할 때 사용되는 요청이다. 자격 인증 정보는 세션 ID가 저장되어 있는 쿠키 혹은 Authorization 헤더에 설정하는 토큰 등을 일컬음.
-> 클라이언트에서 일반적인 JSON 데이터 외에도 쿠키 같은 인증 정보를 포함해서 다른 출처의 서버로 전달할 때 CORS의 3가지 요청 중 하나인 인증된 요청으로 동작된다.
- 클라이언트에서 인증 정보를 보내도록 설정하기 - 별도의 옵션 없이 브라우저의 쿠키와 같은 인증과 관련된 데이터를 함부로 요청 데이터에 담지 않도록 되어있다. -> credentails옵션. 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));
}
});
- 서버에서 인증된 요청에 대한 헤더 설정하기 - 서버도 마찬가지로 인증된 요청에 대해서 일반적인 CORS 요청과는 다르게 대응해야 한다. 응답 헤더의 Access-Control-Allow-Credentials를 true로 설정해야 하고, Access-Control-Allow-Origin, Access-Control-Allow-Methods, Access-Control-Allow-Headers의 값에 "*"를 사용할 수 없고, 즉 헤더에 "*"이 아닌 분명한 origin으로 설정되어야 한다는 뜻이다.
CORS 해결 방법
- Chrome 확장 프로그램 이용.
- 프록시 사이트 이용.
- 서버에서 Access-Control-Allow-Origin 헤더 세팅하기. -> 직접 서버에서 HTTP 헤더 설정을 통해.
-
# 헤더에 작성된 출처만 브라우저가 리소스를 접근할 수 있도록 허용함. # * 이면 모든 곳에 공개되어 있음을 의미한다. Access-Control-Allow-Origin : https://naver.com # 리소스 접근을 허용하는 HTTP 메서드를 지정해 주는 헤더 Access-Control-Request-Methods : GET, POST, PUT, DELETE # 요청을 허용하는 해더. Access-Control-Allow-Headers : Origin,Accept,X-Requested-With,Content-Type,Access-Control-Request-Method,Access-Control-Request-Headers,Authorization # 클라이언트에서 preflight 의 요청 결과를 저장할 기간을 지정 # 60초 동안 preflight 요청을 캐시하는 설정으로, 첫 요청 이후 60초 동안은 OPTIONS 메소드를 사용하는 예비 요청을 보내지 않는다. Access-Control-Max-Age : 60 # 클라이언트 요청이 쿠키를 통해서 자격 증명을 해야 하는 경우에 true. # 자바스크립트 요청에서 credentials가 include일 때 요청에 대한 응답을 할 수 있는지를 나타낸다. Access-Control-Allow-Credentials : true # 기본적으로 브라우저에게 노출이 되지 않지만, 브라우저 측에서 접근할 수 있게 허용해주는 헤더를 지정 Access-Control-Expose-Headers : Content-Length
-
Node.jsvar http = require('http'); const PORT = process.env.PORT || 3000; var httpServer = http.createServer(function (request, response) { // Setting up Headers response.setHeader('Access-Control-Allow-origin', '*'); // 모든 출처(orogin)을 허용 response.setHeader('Access-Control-Allow-Methods', 'GET, POST, OPTIONS, PUT, PATCH, DELETE'); // 모든 HTTP 메서드 허용 response.setHeader('Access-Control-Allow-Credentials', 'true'); // 클라이언트와 서버 간에 쿠키 주고받기 허용 // ... response.writeHead(200, { 'Content-Type': 'text/plain' }); response.end('ok'); }); httpServer.listen(PORT, () => { console.log('Server is running at port 3000...'); });
-
Express.jsconst express = require('express') const cors = require("cors"); // cors 설정을 편안하게 하는 패키지 const app = express(); // ... app.use(cors({ origin: "https://naver.com", // 접근 권한을 부여하는 도메인 credentials: true, // 응답 헤더에 Access-Control-Allow-Credentials 추가 optionsSuccessStatus: 200, // 응답 상태 200으로 설정 })); // ...
-
JSP / Servletimport javax.servlet.*; public class CORSInterceptor implements Filter { private static final String[] allowedOrigins = { "http://localhost:3000", "http://localhost:5500", "http://localhost:5501", "http://127.0.0.1:3000", "http://127.0.0.1:5500", "http://127.0.0.1:5501" }; @Override public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException { HttpServletRequest request = (HttpServletRequest) servletRequest; String requestOrigin = request.getHeader("Origin"); if(isAllowedOrigin(requestOrigin)) { // Authorize the origin, all headers, and all methods ((HttpServletResponse) servletResponse).addHeader("Access-Control-Allow-Origin", requestOrigin); ((HttpServletResponse) servletResponse).addHeader("Access-Control-Allow-Headers", "*"); ((HttpServletResponse) servletResponse).addHeader("Access-Control-Allow-Methods", "GET, OPTIONS, HEAD, PUT, POST, DELETE"); HttpServletResponse resp = (HttpServletResponse) servletResponse; // CORS handshake (pre-flight request) if (request.getMethod().equals("OPTIONS")) { resp.setStatus(HttpServletResponse.SC_ACCEPTED); return; } } // pass the request along the filter chain filterChain.doFilter(request, servletResponse); } private boolean isAllowedOrigin(String origin){ for (String allowedOrigin : allowedOrigins) { if(origin.equals(allowedOrigin)) return true; } return false; } }
-
// 스프링 서버 전역적으로 CORS 설정 @Configuration public class WebConfig implements WebMvcConfigurer { @Override public void addCorsMappings(CorsRegistry registry) { registry.addMapping("/**") .allowedOrigins("http://localhost:8080", "http://localhost:8081") // 허용할 출처 .allowedMethods("GET", "POST") // 허용할 HTTP method .allowCredentials(true) // 쿠키 인증 요청 허용 .maxAge(3000) // 원하는 시간만큼 pre-flight 리퀘스트를 캐싱 } }
-
Spring// 특정 컨트롤러에만 CORS 적용하고 싶을때. @Controller @CrossOrigin(origins = "*", methods = RequestMethod.GET) public class customController { // 특정 메소드에만 CORS 적용 가능 @GetMapping("/url") @CrossOrigin(origins = "*", methods = RequestMethod.GET) @ResponseBody public List<Object> findAll(){ return service.getAll(); } }
-
Apache<IfModule mod_headers.c> Header set Access-Control-Allow-Origin "*" </IfModule>
-
Tomcat<filter> <filter-name>CorsFilter</filter-name> <filter-class>org.apache.catalina.filters.CorsFilter</filter-class> <init-param> <param-name>cors.allowed.origins</param-name> <param-value>*</param-value> </init-param> <init-param> <param-name>cors.allowed.methods</param-name> <param-value>GET,POST,HEAD,OPTIONS,PUT,DELETE</param-value> </init-param> <init-param> <param-name>cors.allowed.headers</param-name> <param-value>Content-Type,X-Requested-With,accept,Origin,Access-Control-Request-Method,Access-Control-Request-Headers</param-value> </init-param> <init-param> <param-name>cors.exposed.headers</param-name> <param-value>Access-Control-Allow-Origin,Access-Control-Allow-Credentials</param-value> </init-param> <init-param> <!-- 쿠키 통신을 안하는데 이걸 true로 하면 4XX 서버 에러가 뜬다 --> <param-name>cors.support.credentials</param-name> <param-value>false</param-value> </init-param> <init-param> <param-name>cors.preflight.maxage</param-name> <param-value>10</param-value> </init-param> </filter> <filter-mapping> <filter-name>CorsFilter</filter-name> <url-pattern>/*</url-pattern> </filter-mapping>
-
Nginxlocation / { root html; add_header 'Access-Control-Allow-Origin' '*'; index index.html index.htm; }
-
AWS (S3 호스팅) - S3 콘솔 메뉴에서 버킷 -> 권한 탭 -> CORS 창에서 편집 -> 텍스트 밑 JSON CORS 규칙 입력.[ { "AllowedHeaders": [ "Authorization" ], "AllowedMethods": [ "GET", "HEAD" ], "AllowedOrigins": [ "http://www.example.com" ], "ExposeHeaders": [ "Access-Control-Allow-Origin" ] } ]
출처: https://inpa.tistory.com/entry/WEB-📚-CORS-💯-정리-해결-방법-👏#요청_방식에_따라_다른_cors_발생_여부 [Inpa Dev 👨💻:티스토리]