一、了解什么是同源策略
同源策略(Same origin policy)是一种约定,它是浏览器最核心也最基本的安全功能,如果缺少了同源策略,则浏览器的正常功能可能都会受到影响。可以说Web是构建在同源策略基础之上的,浏览器只是针对同源策略的一种实现。
同源策略,它是由Netscape提出的一个著名的安全策略。
现在所有支持JavaScript 的浏览器都会使用这个策略。
所谓同源是指,域名,协议,端口相同。
当一个浏览器的两个tab页中分别打开来 百度和谷歌的页面,
浏览器的百度tab页执行一个脚本的时候会检查这个脚本是属于哪个页面的,
即检查是否同源,只有和百度同源的脚本才会被执行。如果非同源,那么在请求数据时,浏览器会在控制台中报一个异常,提示拒绝访问。
但是如果我百度想要获得谷歌页面的脚本或者某个资源怎么办,这时候就需要发起跨域请求。
二、了解什么是CORS
概念
跨域资源共享(CORS) 是一种机制,它使用额外的 HTTP 头来告诉浏览器 让运行在一个 origin (domain) 上的Web应用被准许访问来自不同源服务器上的指定的资源。当一个资源从与该资源本身所在的服务器不同的域或端口请求一个资源时,资源会发起一个跨域 HTTP 请求。
比如:网站A中有些图片是通过img标签中的src请求B网站的某些图片资源,这就是一个简单的跨域请求。
出于安全原因,浏览器限制从脚本内发起的跨源HTTP请求。 例如,XMLHttpRequest和Fetch API遵循同源策略。 这意味着使用这些API的Web应用程序只能从加载应用程序的同一个域请求HTTP资源,除非使用CORS头文件。
通俗的理解就是,实现跨域的方式主要就是在请求和响应的头部添加额外的信息,告诉浏览器和服务器这是一个跨域请求,不能拦截我,不能阻止我从服务器拿到数据。
主要的三种跨域访问场景
在了解跨域访问场景之前,我们先了解一下有关跨域的请求和响应的头部字段。对于我们理解跨域场景有很大的作用。
HTTP 请求首部字段:
- Origin:表明预检请求或实际请求的源站。
- Access-Control-Request-Method:用于预检请求,将实际请求所使用的 HTTP 方法告诉服务器。
- Access-Control-Request-Headers:用于预检请求,将实际请求所携带的首部字段告诉服务器。
HTTP 响应首部字段:
- Access-Control-Allow-Origin:允许访问该资源的外域 URI。对于不需要携带身份凭证的请求,服务器可以指定该字段的值为通配符,表示允许来自所有域的请求。
- Access-Control-Expose-Headers:服务器把允许浏览器访问的头放入白名单。
- Access-Control-Max-Age:指定了preflight请求的结果能够被缓存多久。
- Access-Control-Allow-Credentials:指定了当浏览器的credentials设置为true时是否允许浏览器读取response的内容。当用在对preflight预检测请求的响应中时,它指定了实际的请求是否可以使用credentials。
- Access-Control-Allow-Methods:用于预检请求的响应。其指明了实际请求所允许使用的 HTTP 方法。
- Access-Control-Allow-Headers:用于预检请求的响应。其指明了实际请求中允许携带的首部字段。
简单请求
发送请求时,请求的header中 Origin 表示这个请求是源于哪个网站,在响应的header中, Origin 和 Access-Control-Allow-Origin 完成最简单的访问控制,其中设置服务端响应的Access-Control-Allow-Origin字段值,可以控制哪些请求访问服务器。
预检请求
首先使用 OPTIONS 方法发起一个预检请求到服务器,以获知服务器是否允许该实际请求。"预检请求“的使用,可以避免跨域请求对服务器的用户数据产生未预期的影响。
简单来说,在向服务器请求资源前,我们先发送一个请求过去告诉服务器,我等一会儿(这个一会儿很快)要从你的这里拿点东西,通过什么方式拿,来拿的时候我还会带些东西。服务器以此决定你等一会儿的请求(实际请求)是否允许。
预检请求发送时头部会携带两个字段Access-Control-Request-Method和Access-Control-Request-Headers,前者告诉服务器,实际请求将使用的方法,后者告诉服务器实际请求将携带的自定义请求首部字段。
预检请求的响应的头部中将携带以下字段:
- Access-Control-Allow-Origin:允许请求资源的网站
- Access-Control-Allow-Methods:允许服务端发送请求的方法
- Access-Control-Allow-Headers:允许服务器请求中携带的字段
- Access-Control-Max-Age:表明请求响应的有效时间
附带身份凭证的请求
CORS可以基于HTTP cookies 和 HTTP 认证信息发送身份凭证。附带身份凭证就是发送请求的同时设置cookie,把cookie信息发送给服务器。
**值得我们注意的一点:**对于附带身份凭证的请求,服务器不得设置 Access-Control-Allow-Origin 的值为“*”。
这是因为请求的首部中携带了 Cookie 信息,如果 Access-Control-Allow-Origin 的值为“*”,请求将会失败。应当设为对应的允许访问的网站的url
参考文档: https://developer.mozilla.org/zh-CN/docs/Web/HTTP/Access_control_CORS
三、了解传统的跨域方式——jsonp
jsonp的产生
在ajax请求中,出于浏览器的同源策略对于跨域的请求是一概禁止的。但是聪明的程序员们发现Web页面上调用js文件不受是否跨域的影响,不仅如此,我们还发现凡是拥有”src”这个属性的标签都拥有跨域的能力,比如
发现问题之后自然是解决问题,既然知道上面的那些方式不受跨域限制,那么我们可不可以在服务器上把数据装进js格式的文件里,供客户端进行处理呢?
那什么类型的数据可以被js支持,同时便于前后台的数据传输呢?当然是json字符串啦。
那么我们便可以在服务端生成js格式的文件,里面用json存储数据,在客户端也就是浏览器便可以通过与调用脚本一样的方式去调用这些数据,然后就可以为所欲为了。
为了便于客户端使用数据,逐渐形成了一种非正式传输协议,人们把它称作JSONP,该协议的一个要点就是允许用户传递一个callback参数给服务端,然后服务端返回数据时会将这个callback参数作为函数名来包裹住JSON数据,这样客户端就可以随意定制自己的函数来自动处理返回数据了。
jsonp(JSON with Padding) 是 json 的一种"使用模式",可以跨域读取数据。
jsonp的使用
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<!-- jsonp实现跨域-->
<button id="btnOne">click me</button><br/>
<script src="../js/jquery.js"></script>
<script>
$(document).ready(function () {
// jsonp完整请求http://localhost:8080/aa?callBack=callBack&_=1543219203733
$("#btnOne").click(function(){
$.ajax({
url: "http://localhost:8080/getJsonp",
type: "GET",
dataType: "jsonp", //指定服务器返回的数据类型
jsonp: "callBack", //指定方法参数名称,默认为callBack
jsonpCallback: "callBack", //自定义的jsonp回调函数名称,默认为jQuery自动生成的随机函数名,
success: function (data) {
console.info("调用success");
}
});
});
</script>
</body>
</html>
ajax和jsonp其实本质上是不同的东西。ajax的核心是通过XmlHttpRequest获取非本页内容,而jsonp的核心则是动态添加,因为jsonp和ajax的调用方式高度类似,所以query把jsonp作为ajax的一种形式进行了封装。但是现在这种方式不推荐使用,因为它是一种“取巧”的方式,不安全。
四、自定义过滤器实现简单的跨域访问控制
@WebFilter(filterName = "CorsFilter")
public class CorsFilter implements Filter {
@Override
public void destroy() {
}
String[] allowOrigin = null;
@Override
public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) throws ServletException, IOException {
HttpServletRequest request = (HttpServletRequest) req;
HttpServletResponse response = (HttpServletResponse) resp;
String origin = request.getHeader("Origin");
if (origin != null && !origin.isEmpty()){
for (String o: allowOrigin) {
if ("*".equals(o) || o.equals(origin)){
response.setHeader("Access-Control-Allow-Origin", origin);
break;
}
}
}
chain.doFilter(req, resp);
}
@Override
public void init(FilterConfig config) throws ServletException {
String webFilter = config.getInitParameter("webFilter");
if (webFilter != null) {
if ("*".equals(webFilter)) {
allowOrigin = new String[]{"*"};
} else {
allowOrigin = webFilter.split(",");
}
}
}
}
web.xml配置
<!--简单的跨域用使用自定义过滤器实现-->
<filter>
<filter-name>CorsFilter</filter-name>
<filter-class>com.lzx.vo_filter.CorsFilter</filter-class>
<init-param>
<param-name>webFilter</param-name>
<param-value>*</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>CorsFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
这里是我们是对简单的跨域请求进行过滤。此外还可以尝试国旅一下预检请求和带凭证的请求。
五、SpringMVC跨域配置
从Spring MVC 4.2 开始增加支持跨域访问,使用4.2之后的版本才能采用以下配置
注解配置
@CrossOrigin
@RestController
public class CroController {
//...
}
spring-mvc.xml配置文件配置
<!--全局配置有一定的局限性,开放了所有的访问-->
<mvc:cors>
<!--允许所有的网站进行跨域访问-->
<!--<mvc:mapping path="/**"/>-->
<!--详细配置符合条件的跨域访问-->
<mvc:mapping path="/**"
allowed-headers="Accept,Accept-Language,Content-Language,Content-Type"
allowed-methods="GET,POST,DELETE,PUT"
allowed-origins="http://127.0.0.1:8081"
allow-credentials="true"
/>
</mvc:cors>