cors执行过程摘自自由的维基百科
子域名之间互相访问需要跨域 结论放在开头: 服务端必须设置允许跨域 客户端带cookie需要设置withCredentials
无论服务端是否允许跨域,该request都会完整执行 options
预请求需要设置返回空,不然requestMapping没有支持该方法则出错环境搭建 需求 首先需要搭建两个环境。一个是提供API的server A,一个是需要跨域访问API的server B。
Server A提供了一个api。完整的请求request是:
1 https://local.expediapartnercentral.com.lisqa7.sb.karmalab.net:8443/contentmain/getDepositsRoomAndRatePlanInfo.json?htid=759&_=1490855801818
Server B有个页面page:
1 http://cros.expediapartnercentral.com.lisqa7.sb.karmalab.net:3001/test.html
并且这个page需要请求server A的api。
但由于跨域保护,请求失败:
1 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,方便互相跨域。
1 2 127.0.0.1 local.expediapartnercentral.com.lisqa7.sb.karmalab.net 127.0.0.1 cros.expediapartnercentral.com.lisqa7.sb.karmalab.net
启动项目A,方便提供API。 至于项目B,测试跨域只要写个html静态页面即可。那么就写一个test.html,并通过一个工具发布:
browser-sync
安装 1 npm install -g browser-sync
本地启动一个test.html 1 browser-sync start --server --files "*.html" --host "cros.expediapartnercentral.com.lisqa7.sb.karmalab.net" --port 3001
关于跨域CORS ruanyifeng
的文章里说浏览器将CORS请求分成两类:简单请求(simple request)和非简单请求(not-so-simple request)。
其中同时满足一下2种标准即为简单跨域:
1 2 3 4 5 6 7 8 9 10 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。
1 2 3 4 5 6 7 8 9 <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:
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 32 33 34 35 public class CorsFilter extends OncePerRequestFilter { @Override protected void doFilterInternal (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())) { response.setStatus(200 ); return ; } } } filterChain.doFilter(request, response); } }
上述filter是为了同一个domain下,不同子域名可以跨域访问,而其他domain则不可以,因为我们需要共享cookie,所以设置Access-Control-Allow-Credentials
为true
. 如果设置为false
则不接受cookie。
客户端,即server B如果想要发送cookie则需要设置withCredentials
为true
.
1 2 3 4 5 6 7 8 9 10 11 12 var xhr = new XMLHttpRequest();xhr.withCredentials = true ; $.ajax({ ... xhrFields: { withCredentials: true } ... });
注意,针对非简单跨域的时候发送options
请求,服务端A需要告诉浏览器是否支持跨域即可,不要往下走了,不然到指定的requestMapping发现不支持这个方法就会很尴尬了,所以直接返回。
下面针对简单跨域和非简单跨域做测试:
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 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 <!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.expediapartnercentral.com.lisqa7.sb.karmalab.net:8443/contentmain/getDepositsRoomAndRatePlanInfo.json?htid=759" ; var postUrl = "https://local.expediapartnercentral.com.lisqa7.sb.karmalab.net:8443/contentmain/saveReservationDeposits.json?htid=759" ; function testGetDefault ( ) { sendAjax("GET" ,getUrl, "json" , "application/x-www-form-urlencoded" ); } function testGetJSON ( ) { sendAjax("GET" ,getUrl, "json" , "application/json; charset=utf-8" ); } function testPostDefault ( ) { sendAjax("POST" ,postUrl, "json" , "application/x-www-form-urlencoded" ); } function testPostJson ( ) { sendAjax("POST" ,postUrl, "json" , "application/json; charset=utf-8" ); } function testPUT ( ) { sendAjax("PUT" ,postUrl, "json" , "application/json; charset=utf-8" ); } function sendAjax (type, url, dataType, contentType ) { $.ajax( { type: type, url: url, xhrFields: { withCredentials: true }, dataType : dataType, contentType: contentType, success: function (result ) { console .log(result); }, error: function (xhr ) { console .log(xhr); } }); } </script > </body > </html >
结果: GET default: 只发送一个正常的get请求。
GET json:
先发送一个options如下:
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 General: Request URL:https://local.expediapartnercentral.com.lisqa7.sb.karmalab.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.expediapartnercentral.com.lisqa7.sb.karmalab.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.expediapartnercentral.com.lisqa7.sb.karmalab.net:8443 Origin:http://cros.expediapartnercentral.com.lisqa7.sb.karmalab.net:3001 Referer:http://cros.expediapartnercentral.com.lisqa7.sb.karmalab.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请求来确认是否支持跨域,这时候服务端一定要返回支持跨域,并且直接返回即可。
参考: