一个 URL 有三部分组成:协议、域名(指向主机)、端口,只有这三个完全相同的 URL 才能称之为同源。下表给出了与 URL http://store.company.com/dir/page.html
的源进行对比的示例:
URL | 结果 | 原因 |
---|---|---|
http://store.company.com/dir2/other.html |
同源 | 只有路径不同 |
http://store.company.com/dir/inner/another.html |
同源 | 只有路径不同 |
https://store.company.com/secure.html |
失败 | 协议不同 |
http://store.company.com:81/dir/etc.html |
失败 | 端口不同 ( http:// 默认端口是 80) |
http://news.company.com/dir/other.html |
失败 | 主机不同 |
在页面中通过 about:blank
打开新的页面(about:blank)或通过使用javascript:执行脚本时(<
a href="javascript:js_method();"/>,
url 执行的脚本会继承打开该 URL 的文档的源,因为这些类型的 URLs 没有包含源服务器的相关信息。
满足某些限制条件的情况下,页面可以修改它的源。可以通过脚本将 document.domain 的值设置为其当前域或其当前域的父域。如果将其设置为其当前域的父域,则这个较短的父域将用于后续源检查。
例如:假设 http://store.company.com/dir/other.html 文档中的一个脚本执行以下语句:
document.domain = "company.com";
这条语句执行之后,页面将会成功地通过与 http://company.com/dir/page.html
的同源检测(通过检查的前提是http://company.com/dir/page.html
也将其 document.domain
设置为“company.com
”,以表明它允许子域名通过修改document.domain 的方式与其进行通信 ,使用 document.domain
来允许子域安全访问其父域时,必须在父域和子域中设置document.domain 为相同的值)。
company.com
不能设置 document.domain
为 othercompany.com
,因为它不是 company.com
的父域。
跨域请求的本质是请求别人的信息,所以能否跨域请求,是由被请求的服务器决定的。
同时满足以下条件的请求称之为简单请求:
简单请求的响应头:
https://store.company.com,https://example.company.com
"我们前面提到过,同源策略是浏览器的限制,不是服务器的限制,所以对于简单请求而言,服务器实际上已经将数据返回给了浏览器,同时服务器通过设置这些response header来通知浏览器它返回的数据可以给哪些源进行使用。浏览器会对response header进行检查,判断服务器返回的数据是否能返回给当前源的执行脚本。
如果服务器返回的Access-Control-Allow-Origin包含当前源,并且请求满足Access-Control-Allow-Credentials,那浏览器就会将数据返回给请求,否则的话就会报错。对于Access-Control-Expose-Headers,只有当脚本试图读取不被允许的header时才会报错,不会影响脚本拿到server返回的数据。
非简单请求即为复杂请求。复杂请求在实际进行请求之前,需要发起预检请求。
预检请求头
复杂请求的响应头(预检请求和实际请求的响应头):
https://store.company.com,https://example.company.com
"
4.0.0
org.springframework.boot
spring-boot-starter-parent
2.7.0
com.jessica
cors
jar
0.0.1-SNAPSHOT
cors
spring boot project with cors example
org.springframework.boot
spring-boot-starter-web
org.projectlombok
lombok
1.18.24
provided
package com.jessica;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.web.servlet.ServletComponentScan;
@SpringBootApplication
@ServletComponentScan
public class CorsApplication {
public static void main(String[] args) {
SpringApplication.run(CorsApplication.class, args);
}
}
package com.jessica.controller;
import javax.servlet.http.HttpServletResponse;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import lombok.extern.slf4j.Slf4j;
@RestController
@RequestMapping("/cors")
@Slf4j
public class CorsController {
@RequestMapping(path = "/test", method = RequestMethod.GET)
public String test(@RequestParam(name = "type") OriginTestType type, HttpServletResponse res) {
res.addHeader("test", "test-header");
return "test";
}
}
package com.jessica.controller;
public enum OriginTestType {
NO_ORIGIN, DIFFERENT_ORIGIN, NO_CREDENTIAL, FALSE_CREDENTIAL, NOT_ALLOWED_HEADER, SUCCESS;
}
package com.jessica.filter;
import java.io.IOException;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.annotation.WebFilter;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.RequestMethod;
import com.jessica.controller.OriginTestType;
import lombok.extern.slf4j.Slf4j;
@WebFilter(urlPatterns = { "/cors/*" })
@Slf4j
public class CorsFilter implements Filter {
private static final String ACCESS_CONTROL_ALLOW_HEADERS = "content-type,cookie,test";
private static final String ACCESS_CONTROL_EXPOSE_HEADERS = "test";
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
HttpServletRequest req = (HttpServletRequest) request;
HttpServletResponse res = (HttpServletResponse) response;
String type = req.getParameter("type");
if (type != null) {
OriginTestType originTestType = OriginTestType.valueOf(type);
if (OriginTestType.DIFFERENT_ORIGIN.equals(originTestType)) {
res.addHeader(HttpHeaders.ACCESS_CONTROL_ALLOW_ORIGIN, "http://test.com");
} else if (OriginTestType.NO_CREDENTIAL.equals(originTestType)) {
res.addHeader(HttpHeaders.ACCESS_CONTROL_ALLOW_ORIGIN, req.getHeader(HttpHeaders.ORIGIN));
} else if (OriginTestType.FALSE_CREDENTIAL.equals(originTestType)) {
res.addHeader(HttpHeaders.ACCESS_CONTROL_ALLOW_ORIGIN, req.getHeader(HttpHeaders.ORIGIN));
res.addHeader(HttpHeaders.ACCESS_CONTROL_ALLOW_CREDENTIALS, "false");
} else if (OriginTestType.NOT_ALLOWED_HEADER.equals(originTestType)) {
res.addHeader(HttpHeaders.ACCESS_CONTROL_ALLOW_ORIGIN, req.getHeader(HttpHeaders.ORIGIN));
res.addHeader(HttpHeaders.ACCESS_CONTROL_ALLOW_CREDENTIALS, "true");
} else if (OriginTestType.SUCCESS.equals(originTestType)) {
res.addHeader(HttpHeaders.ACCESS_CONTROL_ALLOW_ORIGIN, req.getHeader(HttpHeaders.ORIGIN));
res.addHeader(HttpHeaders.ACCESS_CONTROL_ALLOW_CREDENTIALS, "true");
res.addHeader(HttpHeaders.ACCESS_CONTROL_ALLOW_HEADERS, ACCESS_CONTROL_ALLOW_HEADERS);
res.addHeader(HttpHeaders.ACCESS_CONTROL_EXPOSE_HEADERS, ACCESS_CONTROL_EXPOSE_HEADERS);
}
}
res.addHeader(HttpHeaders.ACCESS_CONTROL_MAX_AGE, "300");
if (RequestMethod.OPTIONS.name().equals(req.getMethod())) {
res.setStatus(HttpStatus.OK.value());
return;
}
chain.doFilter(request, response);
}
}
Simple Request
Test no ACCESS_CONTROL_ALLOW_ORIGIN header set by server
Test origin not allowed by ACCESS_CONTROL_ALLOW_ORIGIN header set by server
Test withCredentials is not set by server
Test withCredentials is set to false by server
Test get not allowed response header
Test request success
Complex Request
Test no ACCESS_CONTROL_ALLOW_ORIGIN header set by server
Test origin not allowed by ACCESS_CONTROL_ALLOW_ORIGIN header set by server
Test withCredentials is not set by server
Test withCredentials is set to false by server
Test get not allowed response header
Test request success
Test request success
function testRequest(type, withHeader, test) {
var xhr = new XMLHttpRequest();
const requestUrl = test ? `http://localhost:8080/cors/test?type=${type}&test=2`:`http://localhost:8080/cors/test?type=${type}`;
xhr.open('GET', requestUrl, true);
xhr.withCredentials = true;
if(withHeader) {
xhr.setRequestHeader('Content-Type', 'application/json;charset=UTF-8');
xhr.setRequestHeader('test', window.location.href);
}
xhr.send();
xhr.onreadystatechange = function() {
if(this.readyState === 4) {
console.log(`respose: ${xhr.responseText}`);
}
if(this.readyState === this.HEADERS_RECEIVED) {
console.log(`test header: ${xhr.getResponseHeader("test")}`);
}
}
}
GitHub - JessicaWin/cors