Ajax跨域三(被调用方解决跨域以及spring中解决方式)
一、被调用方解决:
(1)服务器端实现(通过后台filter):跨域的时候通过后台filter去设置允许跨域,将可以被跨域访问的请求URL和方法都添加进去。
如何判断跨域?如果存在跨域(请求头中会存在请求的url,在请求头中存在origin字段):
如何实现?可以通过Filter代码实现。
指定响应头信息(允许什么域,调用什么方法):
代码:
在服务器端Ajaxserver1Application类中加入以下代码:
@Bean public FilterRegistrationBean registrationBean(){ FilterRegistrationBean bean = new FilterRegistrationBean(); bean.addUrlPatterns("/*"); bean.setFilter(new ConsFilter()); return bean ; }
继承filter并实现dofilter方法:
import java.io.IOException; import javax.servlet.Filter; import javax.servlet.FilterChain; import javax.servlet.FilterConfig; import javax.servlet.ServletException; import javax.servlet.ServletRequest; import javax.servlet.ServletResponse; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.springframework.util.StringUtils; public class ConsFilter implements Filter { @Override public void destroy() { } @Override public void doFilter(ServletRequest arg0, ServletResponse arg1, FilterChain arg2) throws IOException, ServletException { //响应 HttpServletResponse res = (HttpServletResponse) arg1; //只开启指定的ip与端口,只开启指定的方法 res.addHeader("Access-Control-Allow-Origin", "http://localhost:8081"); res.addHeader("Access-Control-Allow-Methods", "GET"); arg2.doFilter(arg0, arg1); } @Override public void init(FilterConfig arg0) throws ServletException { } }
允许所有域,所有方法:
代码:
//允许所有域,所有方法 res.addHeader("Access-Control-Allow-Origin", "http://localhost:8081"); res.addHeader("Access-Control-Allow-Methods", "*");
补充:
是否所有请求都是先执行后判断?
答:需要知道是简单请求、非简单请求。
1)简单请求:先执行后判断
定义: 方法为: GET HEAD POST 请求header里面: 无自定义头 Content-Type为以下几种: Text/plain mutipart/form-data application/x-www-form-urlencoded
2)非简单请求:先发出一个预检命令,然后在发出请求。先判断后执行。
工作中常见的【非简单请求】有:
Put,delete方法的ajax请求
发送json格式的ajax请求
带自定义头的ajax请求
当浏览器要发送跨域请求时,如果请求是复杂请求,浏览器会先发送一个options预检命令即一个options请求,当该请求通过时才会再发送真正的请求。
该option请求会根据请求的信息去询问服务端支不支持该请求。比如发送的数据是json类型(通过content-type设置)的话,会携带一个请求头Access-Control-Request-Headers: content-type去询问支不支持该数据类型,如果支持,则请求就会通过,并发送真正的请求。
前端报错日志:
查看头信息(查看预解命令信息):
分析:从信息头中可以查看预解命令中多个一个字段:Access-Control-Request-Headers:content-type,请求后台是否允许这个头信息,但是由于Response头中并没有给予授权该请求响应信息。
解决方案:在服务器filter中添加这个请求信息。
@Override public void doFilter(ServletRequest arg0, ServletResponse arg1, FilterChain arg2) throws IOException, ServletException { HttpServletResponse res = (HttpServletResponse) arg1; // //只开启指定的ip与端口,只开启指定的方法 // res.addHeader("Access-Control-Allow-Origin", "http://localhost:8081"); // res.addHeader("Access-Control-Allow-Methods", "GET"); //允许所有域,所有方法 res.addHeader("Access-Control-Allow-Origin", "*"); res.addHeader("Access-Control-Allow-Methods", "*"); res.addHeader("Access-Control-Allow-Headers", "content-type"); arg2.doFilter(arg0, arg1); }
查看前端信息:
分析:可以看出,postJSON发出了2个请求,一个是预解命令(options),当该请求通过时才会再发送真正的POST请求。
复杂请求每次都要发送两条请求,效率很低,可以通过将预检命令缓存来减少请求,设置方法是服务端响应头设置Access-Control-Max-Age,值是缓存时间(单位是秒)。
@Override public void doFilter(ServletRequest arg0, ServletResponse arg1, FilterChain arg2) throws IOException, ServletException { HttpServletResponse res = (HttpServletResponse) arg1; // //只开启指定的ip与端口,只开启指定的方法 // res.addHeader("Access-Control-Allow-Origin", "http://localhost:8081"); // res.addHeader("Access-Control-Allow-Methods", "GET"); //允许所有域,所有方法 res.addHeader("Access-Control-Allow-Origin", "*"); res.addHeader("Access-Control-Allow-Methods", "*"); res.addHeader("Access-Control-Allow-Headers", "content-type"); res.addHeader("Access-Control-Max-Age", "3600"); arg2.doFilter(arg0, arg1); }
分析:第一次依然是2次请求,但是超过第二次就只有一次请求,看上面的前台信息就可以看出。
带cookie跨域
问:Access-Control-Allow-Origin:*,是否可以满足所有跨域请求?
答:带有Cookie的ajax跨域 Access-Control-Allow-Origin的value不能为"*", 其值必须是当前调用方的url,否则浏览器会报错,例如:
servletResponse.addHeader("Access-Control-Allow-Origin", "http://localhost:8081");
补充:带cookie的跨域(发送的cookie只能是被调用方的cookie,而不是调用方的cookie)。
另外,浏览器还会报另一个错误,这是只需要在响应头中设置Access-Control-Allow-Credentials的值为true。
后台代码:
@GetMapping("/getCookie") public ResultBean getCookie(@CookieValue(value="cookie") String cookie){ System.out.println("TestController.getCookie()"); return new ResultBean("getCookie " + cookie); }
注意:被调用方(服务器方)设置cookie,前端就可以设置cookie,document.cookie="xxxx" 即可。
报错:
这里报错的意思是指:响应头中的信息Access-Control-Allow-Origin不能是*,必须是精确匹配,即:res.addHeader("Access-Control-Allow-Origin", "http://localhost:8081");
依旧报错:
那么就在头信息中加上字段:res.addHeader("Access-Control-Allow-Credentials", "true");即可解决问题。
总结:带cookie 的时候,origin必须是全匹配,不能试用*;第二就是要增加一个响应头。
带Cookie的跨域:
- "Access-Control-Allow-Credentials","true" ;
- Access-Control-Allow-Origin 不能为* ,必须为【调用方】的域名+端口;
- 发送的Cookie是【被调用方】的cookie。
在上面的信息都不变情况下,如果浏览器中请求url:127.0.0.1:8080,但是会报错(报域名错误,这里的信息中指验证localhost域名)
问题:上面的配置有限制,如服务器url等等?如何解决可以匹配多个不同的服务器?
解决方案:在请求中添加判断,使其通过不同的域名。
代码:
//请求 HttpServletRequest req = (HttpServletRequest) arg0; String origin = req.getHeader("Origin"); if(!StringUtils.isEmpty(origin)){ res.addHeader("Access-Control-Allow-Origin", origin); }
带自定义头的跨域
后端代码: @GetMapping("/getHeader") public ResultBean getHeader(@RequestHeader("x-header1") String header1,@RequestHeader("x-header2") String header2){ System.out.println("TestController.getHeader()"); return new ResultBean("getHeader " + header1 + " " + header2); }
前端代码:
//自定义头测试代码,方式一 it("getHeader请求", function(done) { // 服务器返回的结果 var result; //初始化是undifined $.ajax({ type : "get", url : base + "/getHeader", headers : { //这里的意思是指允许cookie "x-header1" : "AAA" }, beforeSend : function(xhr){ xhr.setRequestHeader("x-header2" , "BBB") }, success : function(json){ result = json; } }); // 由于是异步请求,需要使用setTimeout来校验 setTimeout(function() { expect(result).toEqual({ "data" : "getHeader AAA BBB" }); // 校验完成,通知jasmine框架 done(); }, 100); });
前端报错,但是已经请求到自定义头信息:
查看报错信息:
根据报错信息,在Filter中加入以下配置即可:
res.addHeader("Access-Control-Allow-Headers", "content-type,x-header1,x-header2"); //响应头信息被写死
补充:
针对上面写死的信息,可以动态配置。
代码: //灵活定义响应头信息,支持所有自定义头信息 Strig header = req.getHeader("Access-Control-Request-Headers"); if(!StringUtils.isEmpty(header)){ res.addHeader("Access-Control-Allow-Headers", header); }
(2) NGINX配置
首先配置本地域名信息:
Nginx配置:
首先将配置信息放在conf下,创建文件vhost,并在其下创建文件b.com.conf文件信息,在conf下nginx.conf下引入vhost下的所有配置信息:include vhost/*.conf;
在配置文件信息(b.com.conf):
server{ listen 80; server_name b.com; location /{ proxy_pass http://localhost:8080/; #设置请求头信息 add_header Access-Control-Allow-Methods *; add_header Access-Control-Max-Age 3600; add_header Access-Control-Allow-Credentials true; add_header Access-Control-Allow-Origin $http_origin; add_header Access-Control-Allow-Headers $http_access_control_request_headers; #设置预解命令 if ($request_method = OPTIONS){ return 200; } } }
补充(在nginx安装目下打开命令操作窗口):
1)验证配置信息:nginx.exe -t
2)启动nginx:start nginx.exe
3)重启nginx:nginx.exe -s reload
停止nginx:nginx.exe -s stop
调用方式:
1)服务器端:http://b.com/test/get1
2)客户端:http://localhost:8081
js中url基础路径:
var base = "http://b.com/test";
(3)APACHE配置
参考地址:https://www.imooc.com/video/16592
(4)被调用方-spring方式配置
Spring跨域方法:在类或者方法上添加注解——@CrossOrigin。
二、调用方解决
(1)隐藏跨域(nginx)
aginx实现隐藏跨域:在下面的a.com.conf文件中配置参数后,在前端代码中把请求http://localhost:8081/test地址改成代理地址/ajaxserver。
反向代理:访问同一域名的的两个URL,去到两个不同的服务器。
配置信息如下:
server{ listen 80; server_name a.com; location /{ proxy_pass http://localhost:8081/; } location /ajaxserver{ proxy_pass http://localhost:8080/test/; } }
服务端:http://a.com/ajaxserver/get1
客户端:http://a.com/
js中url基础路径:
var base = "/ajaxserver";
补充:
nginx中引入配置文件信息(如下在配置文件中引入vhost下所有配置信息):
(2)隐藏跨域(APACHE)
参考地址:https://www.imooc.com/video/16595
理论参考:http://www.cnblogs.com/lojun/articles/9426409.html
代码下载地址:https://download.****.net/download/lowi313804/10584874