Ajax跨域请求安全问题
什么是跨域?
浏览器对于javascript的同源策略的限制,例如a.cn下面的js不能调用b.cn中的js,对象或数据(因为a.cn和b.cn是不同域),所以跨域就出现了。
同域的概念又是什么呢?
简单的解释就是相同域名,端口相同,协议相同。
同源策略:请求的url地址,必须与浏览器上的url地址处于同域上,也就是说:端口、域名、协议必须相同
产生跨域安全问题的条件:
1、当发生跨域请求时,浏览器会针对请求做校验并限制;
2、当发出的请求是XHR(XMLHttpRequest)类型时;
3、发生了跨域(域名、端口或协议不同)。
当同时满足以上三种条件的时候,就会出现跨域请求安全问题。
如何解决跨域安全问题?
既然我们知道了跨域请求安全问题出现的原因,那么解决它的思路肯定是要从以上三个原因入手:
1、使发送的请求不是XHR类型(通常我们使用JSONP,通过动态创建JavaScript标签,在Script中发出跨域请求)。
2.1、被调用方修改代码,让后台服务器支持跨域,支持基于Http协议关于跨域方面的要求进行修改,在A域名调用B域名的时候,在B域名的返回信息中加入一些字段,告诉浏览器我们是允许A域名跨域的,让浏览器对请求的校验通过。
2.2、调用方修改代码,实现隐藏跨域,通过代理,对浏览器发出的请求在代理服务器里面将指定的URL转到B域名里。
使用JSONP解决跨域安全问题:
1、JSONP是JSON的一种补充使用方式,并不是官方协议,它主要是利用动态的创建Script标签请求资源来解决跨域安全问题,它是一种口头的约定(通过前后台对照参数的方式)。
2、使用JSONP依然需要改动后台代码。
前台(调用方前台):$.ajax({
url:base + '/get',
dataType:'jsonp',
...
});
后台(被调用方后台):使用@ControllerAdvice注解
JSONP解决跨域问题的原理:1、JSONP的请求类型使Script请求;
2、返回类型是JavaScript脚本;
3、给前后端加入约定参数。
使用JSOP的弊端:
1、服务器需要改动代码来支持;(如果被调用方是第三方接口,我们无法修改后台代码呢?)
2、只支持GET方法(因为它是通过创建动态JavaScript标签来实现的),并且它不满足RESTful的API风格;
3、它发送的不是XHR(XMLHttpRequest)请求。
通过解决产生跨域安全问题的跨域来寻找解决思路:
1、被调用方解决,是基于支持跨域的思路,是基于Http协议对跨域方面的要求:在响应头里增加指定的字段,允许对方跨域调用。我们可以通过增加Filter代码实现:
1:在SpringBoot的启动类中增加Filter(注册为bean)
@bean
public FilterRegistrationBean regsitrerFilter(){
FilterRegistrationBean bean=new FilterRegistrationBean();
bean.addUrlPatterns("/*");
bean.setFilter(new CrosFilter);
return bean;
}
修改CrosFilter的doFilter方法:
@Override
public void doFilter(HttpServletRequest request,HttpServletResponse response,FilterChain chain) throws IOException{
HttpServletResponse res=(HttpServletResponse )response;//强转response
res.addHeader("Access-Control-Allow-Oringin","*");
res.addHeader("Access-Control-Allow-Methods","*");
chain.doFilter(request,response);
}(那么,这种方法有什么弊端呢?)
在进行后面的内容之前,我们先来了解一下简单请求和非简单请求的区别。
简单请求(浏览器先执行,后检查):
方法为:GET/HEAD/POST;
请求Header里面无自定义头部信息;
Content-Type为以下几种:text/plain、multipart/form-data、application/x-www-form-urlcoded。
非简单请求(浏览器先检查,后执行):
PUT、DELETE方法的Ajax请求;
发送JSON格式的Ajax请求;
带自定义头的Ajax请求。
非简单请求每次会发出两次请求:1:第一次发送一个OPTIONS预检命令,预检通过则发送真正的请求。Http协议里增加了一个响应头,可以用来存储OPTIONS缓存;
res.addHeader("Access-Control-Max-Age","3600");//设置浏览器缓存预检命令的时间为1小时,1小时之内不再发送预检命令。
之前我们使用修改后台的方法,在头部信息中指定了Origin字段为*。在实际的需求中,我们知道Http里面的会话,也就是session是要依赖于Cookie的,sessionID是存放在Cookie里边的
在Ajax中添加属性:xhrFields{
withCredentials:true
}
表示发送请求时会带上Cookie;经过测试发现,当请求中携带Cookie时,Origin必须是请求域的具体地址,不能使用通配符*,Credentials必须返回true。并且Origin是被调用方域名的Cookie而不是调用方域名的Cookie。
我们可以通过修改Filter被调用方的后台代码来实现:
...
HttpServletRequest req=(HttpServletRequest )request;
String origin=req.getHeader("Origin");
if(!org.springframework.util.StringUtils.isEmpty(origin)){
res.addHeader("Access-Control-Allow-Origin",origin);
}
还有一种带自定义头部的跨域请求,解决方法同上。
还用一种基于被调用方的解决方案,那就是使用代理(Proxy)
因为我这里没有下载Nginx和Apache(它们都是HTTP服务器)。
所以简单的说一下配置,详细配置可以自行搜索。
以Nginx为例:
1、先找到Nginx的nginx.conf文件。在最后一行增加include vhost/*.conf;(让Nginx载入这个目录下所有后缀为conf的文件);
2、在vhost文件下新建被调用域名的配置文件(b.com.conf);使用Nginx语法增加节点
server{
listen 80;
server_name b .com;
location/{
proxy_pass http://localhost:8080;(被调用rul)
//添加头部信息
add_header Access-Control-Allow-Methods *;
add_header Access-Control-Max-Age 3600;
add_header Access-Control-Allow-Credentoals true;
add_header Access-Control-Allow-Origin $http_origin;
add_header Access-Control-Allow-Headers $http_access_request_header;#请求头必须小写,横杠变为下划线
#处理OPTIONS预检命令
if($request_methods=OPTIONS){
return 200;
}
}
}
Apache:
1、打开httpd.conf文件,打开虚拟主机的相关配置;
LoadModule vhosts_alias_module modules/mod_vhosts_alias.so;
2、打开配置文件:Virtual hosts增加:Include conf/extra/httpd-vhosts.conf
3、在配置文件中增加代理
<VirtualHost *: 80>
serverName 被调用域名
ErrorLog "log/xxxx"
CustomLog "log/xxxx" common
ProxyPass /http://localhost:8080/(被调用方url)
</VirtualHost>
4、打开Proxy代理模块(Proxy_http_module)
5、配置响应头(VirtualHost),再打开headers模块和rewrite模块
还有一种基于被调用方的解决方案,使用Spring提供的解决方案
使用注解:@CrossOrigin注解可以加在类上,也可以加在方法上。
最后来看,基于调用方的解决方案,隐藏跨域,使用反向代理实现(Reverse Proxy)
什么是反向代理?
反向代理(Reverse Proxy)方式是指以代理服务器来接收internet上的连接请求,然后将请求转发给内部网络上的服务器,并将从服务器上得到的结果返回给internet上请求连接的客户端,此时代理服务器对外就表现为一个反向代理服务器。
实现反响代理的基本配置
server{
listen 80;
server_name a.com;(调用方域名)
location/{
proxy_pass 调用方url;
}
location/{
proxy_pass 被调用方url;
}
}
apache比较复杂,这里大家可以自己在搜索引擎上搜索。