Javascript跨域后台设置拦截
子域名之间互相访问需要跨域
结论放在开头:
- 服务端必须设置允许跨域
- 客户端带cookie需要设置
withCredentials
- 无论服务端是否允许跨域,该request都会完整执行
-
options
预请求需要设置返回空,不然requestMapping没有支持该方法则出错
环境搭建
需求
首先需要搭建两个环境。一个是提供API的server A,一个是需要跨域访问API的server B。
Server A提供了一个api。完整的请求request是:
https://local.corstest.com.net:8443/contentmain/getDepositsRoomAndRatePlanInfo.json?htid=759&_=1490855801818
Server B有个页面page:
http://cros.corstest.com.net:3001/test.html
并且这个page需要请求server A的api。
但由于跨域保护,请求失败:
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,方便互相跨域。
127.0.0.1 local.corstest.com.net
127.0.0.1 cros.corstest.com.net
启动项目A,方便提供API。 至于项目B,测试跨域只要写个html静态页面即可。那么就写一个test.html,并通过一个工具发布:
安装
npm install -g browser-sync
本地启动一个test.html
browser-sync start --server --files "*.html" --host "cros.corstest.com.net" --port 3001
关于跨域CORS
ruanyifeng
的文章里说浏览器将CORS请求分成两类:简单请求(simple request)和非简单请求(not-so-simple request)。
其中同时满足一下2种标准即为简单跨域:
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。
<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:
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())) {
// CORS "pre-flight" request
response.setStatus(200);
return;
}
}
}
filterChain.doFilter(request, response);
}
}
上述filter是为了同一个domain下,不同子域名可以跨域访问,而其他domain则不可以,因为我们需要共享cookie,所以设置Access-Control-Allow-Credentials
为true
. 如果设置为false
则不接受cookie。
客户端,即server B如果想要发送cookie则需要设置withCredentials
为true
.
//原生
var xhr = new XMLHttpRequest();
xhr.withCredentials = true;
//jquery
$.ajax({
...
xhrFields: {
withCredentials: true
}
...
});
注意,针对非简单跨域的时候发送options
请求,服务端A需要告诉浏览器是否支持跨域即可,不要往下走了,不然到指定的requestMapping发现不支持这个方法就会很尴尬了,所以直接返回。
下面针对简单跨域和非简单跨域做测试:
<!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.corstest.com.net:8443/contentmain/getDepositsRoomAndRatePlanInfo.json?htid=759";
var postUrl = "https://local.corstest.com.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, // accept type
contentType: contentType, //request type, default is application/x-www-form-urlencoded
success: function(result){
console.log(result);
},
error: function (xhr) {
console.log(xhr);
}
});
}
</script>
</body>
</html>
结果:
GET default: 只发送一个正常的get请求。
GET json:
先发送一个options如下:
General:
Request URL:https://local.corstest.com.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.corstest.com.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.corstest.com.net:8443
Origin:http://cros.corstest.com.net:3001
Referer:http://cros.corstest.com.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请求来确认是否支持跨域,这时候服务端一定要返回支持跨域,并且直接返回即可。
- 平面上给定n条线段,找出一个点,使这个点到这n条线段的距离和最小。
- python接口自动化4-绕过验证码登录(cookie)
- 洛谷P1313 计算系数【快速幂+dp】
- python接口自动化5-Json数据处理
- Numpy教程第1部分 - 阵列简介(常用基础操作总结)
- Session和Cookies的基本原理
- 浅析Numpy.genfromtxt及File I/O讲解
- 损失函数详解
- 排查Java的内存问题
- 使用两种方法让 ASP.NET Core 实现遵循 HATEOAS 结构的 RESTful API
- 设计模式六大原则(5):迪米特法则
- Selenium2+python自动化61-Chrome浏览器(chromedriver)
- 区块链可以减少社会不平等吗?
- 【干货】不止准确率:为分类任务选择正确的机器学习度量指标(附代码实现)
- JavaScript 教程
- JavaScript 编辑工具
- JavaScript 与HTML
- JavaScript 与Java
- JavaScript 数据结构
- JavaScript 基本数据类型
- JavaScript 特殊数据类型
- JavaScript 运算符
- JavaScript typeof 运算符
- JavaScript 表达式
- JavaScript 类型转换
- JavaScript 基本语法
- JavaScript 注释
- Javascript 基本处理流程
- Javascript 选择结构
- Javascript if 语句
- Javascript if 语句的嵌套
- Javascript switch 语句
- Javascript 循环结构
- Javascript 循环结构实例
- Javascript 跳转语句
- Javascript 控制语句总结
- Javascript 函数介绍
- Javascript 函数的定义
- Javascript 函数调用
- Javascript 几种特殊的函数
- JavaScript 内置函数简介
- Javascript eval() 函数
- Javascript isFinite() 函数
- Javascript isNaN() 函数
- parseInt() 与 parseFloat()
- escape() 与 unescape()
- Javascript 字符串介绍
- Javascript length属性
- javascript 字符串函数
- Javascript 日期对象简介
- Javascript 日期对象用途
- Date 对象属性和方法
- Javascript 数组是什么
- Javascript 创建数组
- Javascript 数组赋值与取值
- Javascript 数组属性和方法
- 【DIY数字仪表】RT-Thread移植touchgfx使用sd卡升级固件和图片资源(4)
- Transformers中的Beam Search高效实现
- 多边形裁剪图片升级啦!Cocos Creator !
- 使用curl进行服务器测试
- 打卡群刷题总结0805——不同的二叉搜索树
- 利用STS临时密钥服务快速搭建直传页面的实践
- Clickhouse在大数据分析平台-留存分析上的应用
- Rancher 高可用部署
- 为什么Web端登录需要验证码?
- 原创分享 TiDB 的列式存储引擎是如何实现的?
- Qt音视频开发5-vlc事件订阅
- matlab中使用VMD(变分模态分解)
- TKE中挂载文件到CFS子目录
- Nginx Ingress on TKE 部署最佳实践
- 腾讯地图点聚合开发-实现地图找房功能