Ajax跨域请求安全问题

最近遇到群里的学友在讨论请求跨域问题,正好自己对这块也不是非常的理解,通过群里大佬给的视频资料。自己也去看了一下跨域请求,顺便记下这篇笔记。
>>最全面的Java面试大纲及答案解析(建议收藏)  

什么是跨域?

浏览器对于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比较复杂,这里大家可以自己在搜索引擎上搜索。