一.什么是跨域呢?
1.引入:
先讲个故事:从前一个叫8080的大佬和一个8081的大佬各占一方天地,还有一个叫浏览器的大佬和8080还有8081关系都不错。浏览器和8080做着一件事(8080端应用),浏览器和8081做着另一件事(8081端应用),但8080和8081却没有什么交集。有一天8081什么话也没说,就跑到8080的地盘拿东西(ajax返回的数据),浏览器手下的警卫员说:"这种珍贵的东西,无凭无据的,我们可不能给你"(跨域访问错误)。
8081气愤离去,心想:"老子可是大佬,还要凭据,于是打电话给8080。"他们商量了一下,8080说:“警卫也是完成自己分内的事,那好吧,我把东西用保险箱(javascript)包裹起来(json转化为jsonp),警卫就不认得了,你回去用我们约定的密码(callback)打开就行了。”(使用jsonp实现跨域)。
这种方法确实可行,一段时间后,两个大佬觉得挺麻烦的,8080说,给你个令牌(响应头上增加相应字段)算了,那着令牌警卫就不会拦你了。果然,简单了许多。
又过了一段时间,8081想:"我是大佬哎,让我每天拿着令牌进进出出,这不损我形象吗?"打电话给8080,说:"既然咱们都是大佬,还让警卫操心干嘛,以后我直接去找你,咱俩喝喝茶,聊聊天不是更好。"经过一段时间的接触,8080和8081关系也不错了,8080爽快地答应了。(隐藏跨域,大佬背后交接)
跨域错误.png
2.为什么?
[1] 浏览器出于安全的限制,而不是服务器 [2] 跨域:协议/域名/端口必须一致 [3] XHR请求(XMLHttpRequest)
二.解决思路
1: 浏览器放方:8080大佬让浏览器警卫队不要阻拦
浏览器不校验跨域.png
2: jsonp:需要后端修改数据格式,前端修改接受方式
普通ajax请求的Type是:xhr 返回的是json字符串 jsonp的ajax请求的Type是:script 返回的是js脚本 url后有一段callback参数
json和jsonp.png
点击各种url查看:
jsonp返回的:动态创建是一段js脚本,用完再删除/**/jQuery33103437422192155124_1532262222426({"data":"say Ok"}); json返回的:只是json {"data":"say Ok"}
jsonp实现步骤
后端:AbstractJsonpResponseBodyAdvice方法过时,没查到新的方法,但也能用
@ControllerAdvicepublic class JsonpAdvice extends AbstractJsonpResponseBodyAdvice { public JsonpAdvice() { super("callback"); } }
后端:AbstractJsonpResponseBodyAdvice方法过时
<script> var baseUrl = 'http://localhost:8080/ajax';// jasmine.DEFAULT_TIMEOUT_INTERVAL = 1000;//每个测试用例耗时时间 //单元测试用例 it("jsonp", function (done) { var result;//保存返回结果 $.ajax({ url: baseUrl, dataType: "jsonp",//jsonp格式 // cache: true,//结果可被缓存 jsonp:'callback',//默认参数,与后端的callback字符串相对于 success: function (json) { result = json; } }); $.getJSON(baseUrl, function (data) { result = data; }); setTimeout(function () { expect(result).toEqual({ "data": "say Ok" }); done()//完成校验 }, 100); }) });</script>
jsonp.png
弊端
[1]服务器需要改动,若后端非己主宰,则无能为力[2]只支持GET[3]发的不是XHR请求
3.令牌模式:
被调用方(服务端):响应头上增加相应字段告诉浏览器允许
8081跨域的请求头有:Origin:http://localhost:8081
服务端打造令牌:Filter
com.toly1994.ajaxser.AjaxserApplication
@SpringBootApplicationpublic class AjaxserApplication { public static void main(String[] args) { SpringApplication.run(AjaxserApplication.class, args); } @Bean public FilterRegistrationBean registerFilter() { FilterRegistrationBean bean = new FilterRegistrationBean(); bean.addUrlPatterns("/*");//所有请求都经过这个Filter bean.setFilter(new CrosFilter());//设置过滤器 return bean; } }
com.toly1994.ajaxser.CrosFilter
package com.toly1994.ajaxser;import javax.servlet.*;import javax.servlet.http.HttpServletResponse;import java.io.IOException;/** * 作者:张风捷特烈 * 时间:2018/7/22:21:44 * 邮箱:1981462002@qq.com * 说明:CrosFilter */public class CrosFilter implements javax.servlet.Filter { @Override public void init(FilterConfig filterConfig) throws ServletException { } @Override public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException { HttpServletResponse rep = (HttpServletResponse) servletResponse; //允许8081访问:"http://localhost:8081"换为*表示允许所有 rep.addHeader("Access-Control-Allow-Origin", "http://localhost:8081"); //允许访问方法GET:GET"换为*表示允许所有 rep.addHeader("Access-Control-Allow-Methods", "GET"); filterChain.doFilter(servletRequest, rep); } @Override public void destroy() { } }
跨域方式.png
4.非简单请求:post json
4-1:简单请求:先执行,后判断
方法:GET HEAD POST header:无自定义头 Content-Type:rext/plain||multipart/form-data||application/x-www-form-urlencoded
4-2:非简单请求:后判断,先执行
put delete 方法的ajax 发送带有json格式的ajax请求 带自定义头的ajax
4-3:Post请求传Json
8080服务端暴露接口:com.toly1994.ajaxser.controller.AjaxController
@PostMapping(value = "/postJson") public ResultBean postJson(@RequestBody User user) { System.out.println(user); return new ResultBean("post:"+user.getName()); }
8081服务端调用接口:
it("postJson", function (done) { var result;//保存返回结果 $.ajax({ type:"post",//请求类型 url: baseUrl+"/postJson", contentType:"application/json;charset=UTF-8",//请求内容类型 data:JSON.stringify({name: "toly"}),//数据JOSN.stringify // cache: true,//结果可被缓存 success: function (json) { result = json; } }); setTimeout(function () { expect(result).toEqual({ "data": "post:toly"//预期结果 }); done()//完成校验 }, 100); }); });
post发送带有json格式的ajax请求.png
4-4:既然是Header原因,那就放行呗:com.toly1994.ajaxser.CrosFilter#doFilter
rep.addHeader("Access-Control-Allow-Headers", "Content-Type");
请求成功.png
可以看到有两个请求,其中一个是OPTIONS的预检请求,下面一句对这个请求做缓存
com.toly1994.ajaxser.CrosFilter#doFilter
rep.addHeader("Access-Control-Max-Age","3600");//一小时内缓存预检请求
5.带Cookie的跨域
8080服务端暴露接口:com.toly1994.ajaxser.controller.AjaxController
@GetMapping("/getCookie") private ResultBean getCookie(@CookieValue(value = "cookie") String cookie) { System.out.println("//////////////////////"); return new ResultBean("getCookie:"+cookie); }
8081服务端调用接口:
//单元测试用例 it("getCookie", function (done) { var result;//保存返回结果 $.ajax({ type:"get",//请求类型 url: baseUrl+"/getCookie", xhrFields:{ withCredentials: true//发送ajax请求时加cookie }, success: function (json) { result = json; } }); setTimeout(function () { expect(result).toEqual({ "data": "getCookie:toly"//预期结果 }); done()//完成校验 }, 100); }); });
8080种cookie.png
cookie.png
既然是Credentials原因,那就放行呗:com.toly1994.ajaxser.CrosFilter#doFilter
rep.addHeader("Access-Control-Allow-Credentials","true");//允许cookie
请求成功.png
这只解决了8081的跨域,怎么能实现其他的呢?可以获取请求头中的Origin,动态设置。
HttpServletRequest req = (HttpServletRequest) servletRequest; String origin = req.getHeader("Origin"); if (!StringUtils.isEmpty(origin)) { rep.addHeader("Access-Control-Allow-Origin", origin); }
6.跨域带自定义头
8080服务端暴露接口:com.toly1994.ajaxser.controller.AjaxController
@GetMapping("/getHeader")// private ResultBean getHeader(@RequestHeader("x-header1") String header1, @RequestHeader("x-header2") String header2) { System.out.println(header1 + " " + header2); return new ResultBean(header1 + " " + header2); }
8081服务端调用接口:
it("getHeaders", function (done) { var result;//保存返回结果 $.ajax({ type:"get",//请求类型 url: baseUrl+"/getHeader", headers:{ "x-header1": "AAA" }, beforeSend: function (xhr) { xhr.setRequestHeader("x-header2","BBB") }, success: function (json) { result = json; } }); setTimeout(function () { expect(result).toEqual({ "data": "AAA BBB"//预期结果 }); done()//完成校验 }, 100); });
自定义头错误.png
解决方案:添加头
//动态添加自定义头 String headers = req.getHeader("Access-Control-Request-Headers"); if (!StringUtils.isEmpty(headers)) { System.out.println(headers); rep.addHeader("Access-Control-Allow-Headers", headers); }
7.调用方:隐藏跨域--越过浏览器
暂略
作者:张风捷特烈
链接:https://www.jianshu.com/p/1cc97d0fd0cf