共计 6409 个字符,预计需要花费 17 分钟才能阅读完成。
目录
结论放在开头:
环境搭建
需求
修改 host
安装
本地启动一个 test.html
关于跨域 CORS
关键
服务端需要做点工作
子域名之间互相访问需要跨域
结论放在开头:
- 服务端必须设置允许跨域
- 客户端带 cookie 需要设置
withCredentials
- 无论服务端是否允许跨域,该 request 都会完整执行
options
预请求需要设置返回空,不然 requestMapping 没有支持该方法则出错
环境搭建
需求
首先需要搭建两个环境。一个是提供 API 的 server A,一个是需要跨域访问 API 的 server B。
Server A 提供了一个 api。完整的请求 request 是:
https://local.corstest.com.net:8443/contentmain/getDepositsRoomAndRatePlanInfo.json?htid=759&_=1490855801818
Server B 有个页面 page:
http://cros.corstest.com.net:3001/test.html
并且这个 page 需要请求 server A 的 api。
但由于跨域保护,请求失败:
No 'Access-Control-Allow-Origin' header is present on the requested resource. Origin 'xxxxx' is therefore not allowed access.
修改 host
首先本地配置两个指向 127.0.0.1 的 host,方便互相跨域。
127.0.0.1 local.corstest.com.net
127.0.0.1 cros.corstest.com.net
启动项目 A,方便提供 API。
至于项目 B,测试跨域只要写个 html 静态页面即可。那么就写一个 test.html,并通过一个工具发布:
browser-sync
安装
npm install -g browser-sync
本地启动一个 test.html
browser-sync start --server --files "*.html" --host "cros.corstest.com.net" --port 3001
关于跨域 CORS
ruanyifeng
的文章里说浏览器将 CORS 请求分成两类:简单请求(simple request)和非简单请求(not-so-simple request)。
其中同时满足一下 2 种标准即为简单跨域:
1) 请求方法是以下三种方法之一:HEAD
GET
POST
2)HTTP 的头信息不超出以下几种字段:Accept
Accept-Language
Content-Language
Last-Event-ID
Content-Type:只限于三个值 application/x-www-form-urlencoded、multipart/form-data、text/plain
而其他情况,非简单请求是那种对服务器有特殊要求的请求,比如请求方法是 PUT
或 DELETE
,或者Content-Type
字段的类型是 application/json
。非简单请求的 CORS 请求,会在正式通信之前,增加一次 HTTP 查询请求,称为 ” 预检 ” 请求(preflight),即options
请求。
关键
跨域的关键是浏览器获得服务器的认可,而服务器的认可就是 header 里的Access-Control-Allow-Origin
。浏览器通过比较服务端返回的 response 中是否包含这个字段,以及包含这个字段的内容是否是当前网址来确定是否跨域。也就是说绕过浏览器是可以不用跨域的。
有个问题,看好多文章并没有指出。
第一点,带 cookie 问题。浏览器设置 withCredentials
为true
则会带 cookie 发送给服务端。而服务端设置 Access-Control-Allow-Credentials
为true
则接收,false
则不接受。关键是到 filter 里的时候才会决定是否设置 response,那么这时候 cookie 已经存在 request 里了吧。(待验证)
验证:server 端确实已经接受了 cookie,即使设置为 false,服务端仍旧接受 cookie。而客户端也仍旧可以发送 cookie。
第二点,简单跨域中,浏览器的请求直接发送给服务器,服务器返回是否支持跨域(即是否 header 加 origin), 那么简单跨域究竟是请求了服务端几次?如果是 1 次,那么如果服务端不支持跨域,即没有设置 allow,还会不会继续走下去,会不会继续 request 得到结果后放入 response?就是不论跨域不跨域服务器是否都会执行这个 request 对应的计算。因为所有的设置 header 都是给浏览器告知的,和服务端限制无关。(待验证)
验证:即使服务端没有设置允许跨域,当客户端请求过来时,服务端仍旧完整执行了请求并返回,只是客户端没有接收。
服务端需要做点工作
针对上述两种跨域。server A 需要写一个 filter。
<filter>
<filter-name>cors</filter-name>
<filter-class>com.test.filter.CorsFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>cors</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
</filter>
Filter:
public class CorsFilter extends OncePerRequestFilter {@Override
protectedvoiddoFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
throws ServletException, IOException {URL requestURL = new URL(request.getRequestURL().toString());
String hostName = requestURL.getHost();
String origin = request.getHeader("Origin");
int index = hostName.indexOf(".");
if(index > -1) {String domainHost = hostName.substring(index, hostName.length());
if(!StringUtils.isEmpty(origin) && origin.contains(domainHost)) {response.addHeader("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS");
response.addHeader("Access-Control-Allow-Origin", origin);
response.addHeader("Access-Control-Allow-Credentials", "true");
response.setHeader("Access-Control-Max-Age", "3600");
response.addHeader("Access-Control-Allow-Headers", "Content-Type, Cookie," +
"Accept-Encoding, User-Agent," +
"Host, Referer," +
"X-Requested-With, Accept," +
"Accept-Language, Cache-Control, Connection");
if (request.getHeader("Access-Control-Request-Method") != null && "OPTIONS".equals(request.getMethod())) {// CORS "pre-flight" request
response.setStatus(200);
return;
}
}
}
filterChain.doFilter(request, response);
}
}
上述 filter 是为了同一个 domain 下,不同子域名可以跨域访问,而其他 domain 则不可以,因为我们需要共享 cookie,所以设置 Access-Control-Allow-Credentials
为true
. 如果设置为 false
则不接受 cookie。
客户端,即 server B 如果想要发送 cookie 则需要设置 withCredentials
为true
.
// 原生
var xhr = new XMLHttpRequest();
xhr.withCredentials = true;
//jquery
$.ajax({
...
xhrFields: {
withCredentials: true
}
...
});
注意,针对非简单跨域的时候发送 options
请求,服务端 A 需要告诉浏览器是否支持跨域即可,不要往下走了,不然到指定的 requestMapping 发现不支持这个方法就会很尴尬了,所以直接返回。
下面针对简单跨域和非简单跨域做测试:
<!DOCTYPE html>
<html lang="en">
<meta charset="UTF-8">
<title>test</title>
<script src="jquery-1.11.3.js"></script>
</head>
<body>
<input type="button" value="GET_Default" onclick="testGetDefault()">
<input type="button" value="GET_JSON" onclick="testGetJSON()">
<input type="button" value="POST_Default" onclick="testPostDefault()">
<input type="button" value="POST_JSON" onclick="testPostJson()">
<input type="button" value="PUT" onclick="testPUT()">
<script>var getUrl ="https://local.corstest.com.net:8443/contentmain/getDepositsRoomAndRatePlanInfo.json?htid=759";var postUrl ="https://local.corstest.com.net:8443/contentmain/saveReservationDeposits.json?htid=759";functiontestGetDefault(){sendAjax("GET",getUrl,"json","application/x-www-form-urlencoded");}functiontestGetJSON(){sendAjax("GET",getUrl,"json","application/json; charset=utf-8");}functiontestPostDefault(){sendAjax("POST",postUrl,"json","application/x-www-form-urlencoded");}functiontestPostJson(){sendAjax("POST",postUrl,"json","application/json; charset=utf-8");}functiontestPUT(){sendAjax("PUT",postUrl,"json","application/json; charset=utf-8");}functionsendAjax(type, url, dataType, contentType){$.ajax({type: type,url: url,xhrFields:{withCredentials:true},dataType : dataType,// accept typecontentType: contentType,//request type, default is application/x-www-form-urlencodedsuccess:function(result){console.log(result);},error:function (xhr) {console.log(xhr);}});}</script>
</body>
</html>
结果:
GET default:
只发送一个正常的 get 请求。
GET json:
先发送一个 options 如下:
General:
Request URL:https://local.corstest.com.net:8443/contentmain/getDepositsRoomAndRatePlanInfo.json?htid=759
Request Method:OPTIONS
Status Code:200 OK
Remote Address:127.0.0.1:8443
Response Headers:
Access-Control-Allow-Credentials:true
Access-Control-Allow-Headers:Content-Type, Cookie, Accept-Encoding, User-Agent, Host, Referer, X-Requested-With, Accept, Accept-Language, Cache-Control, Connection
Access-Control-Allow-Methods:GET, POST, PUT, DELETE, OPTIONS
Access-Control-Allow-Origin:http://cros.corstest.com.net:3001
Content-Length:0
Date:Thu, 30 Mar 2017 12:47:44 GMT
Server:Apache-Coyote/1.1
Request Headers:
Accept:*/*
Accept-Encoding:gzip, deflate, sdch, br
Accept-Language:zh-CN,zh;q=0.8
Access-Control-Request-Headers:content-type
Access-Control-Request-Method:GET
Connection:keep-alive
Host:local.corstest.com.net:8443
Origin:http://cros.corstest.com.net:3001
Referer:http://cros.corstest.com.net:3001/test.html
User-Agent:Mozilla/5.0 (Windows NT 6.3; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/56.0.2924.87 Safari/537.36
然后再发送正常的 Get 请求。
post default:
正常发送请求。
post json: 先发送一个 options 请求。然后再发送正常的请求。
其他同理,总之,非简单跨域会多发一次 options 请求来确认是否支持跨域,这时候服务端一定要返回支持跨域,并且直接返回即可。
本文永久更新链接地址:http://www.linuxidc.com/Linux/2017-08/146075.htm