先挖个坑,今天的学习内容
今天去体检浪费了一天,赶紧补上 20180428凌晨
验证跨域访问安全问题:
测试中8080是服务端,8081是前台端
spring-boot搭建前台和后台页面(web/dev-tools两个依赖,一个提供web服务,一个提供自动重启服务功能)
被调用方后台代码
- 用到的注解:
- @RestController
- @RequestMapping(“/test”)
- @GetMapping(“/get1”)
调用方前台代码
- 修改application.properties为application.yml(有提示功能),端口改成8081
- 编写ajax测试代码
jasmine测试框架
- 减轻工作量,解脱手动测试
- 示例代码:不设置超时时间的话,框架会一直等待
// 每一个测试用例的超时时间
jasmine.DEFAULT_TIMEOUT_INTERVAL = 1000;
// 请求的接口的前缀 // http://localhost:8080/test
var base = "/ajaxserverapache";
//测试模块
describe("晓风轻-ajax跨越完全讲解", function() {
// 测试方法
it("get1请求", function(done) {
// 服务器返回的结果
var result;
$.getJSON(base + "/get1").then(function(jsonObj) {
result = jsonObj;
});
// 由于是异步请求,需要使用setTimeout来校验
setTimeout(function() {
expect(result).toEqual({
"data" : "get1 ok"
});
// 校验完成,通知jasmine框架
done();
}, 100);
});
});
为什么会有跨域问题:
- 浏览器限制(跨域的时候返回是200 ok,后台返回也是成功的,但是浏览器限制了跨域访问)
- 跨域:协议、域名、端口任何一个不一样就认为是跨域
- 发出的是XHR请求,HTTP/XML请求即使跨域浏览器也不会报错
以上3个条件同时满足才会产生跨域安全问题
解决思路
浏览器限制 ->
指定参数,浏览器不做校验,需要在客户端做改动,价值不大
XHR ->
使用JSONP,无法满足现在的开发要求
跨域 ->
- 被调用方修改代码-支持跨域,A发送给B,B返回数据带某些字段给浏览器,表明允许A访问此资源,浏览器校验通过即可
- 调用修改-隐藏跨域,使用代理,浏览器都访问A域名,但是发送到代理服务器,代理服务器再把指定的URL转发到B域名
浏览器禁止检查:
chrome –disable-web-security –user-data-dir=g:\temp3
JSONP:json pending
json的补充使用方式,非官网,利用的是script请求可以跨域的特性
使用jsonp的方式 后台是需要改动的
// 测试方法
it("jsonp请求", function(done) {
// 服务器返回的结果
var result;
$.ajax({
url: base +"/get1",
dataType: "jsonp",
jsonp: "callback",
cache:true,
success: function(json){
result = json;
}
});
// 由于是异步请求,需要使用setTimeout来校验
setTimeout(function() {
expect(result).toEqual({
"data" : "get1 ok"
});
// 校验完成,通知jasmine框架
done();
}, 100);
});
JsonpAdvice.java 被调用端测试
package com.imooc;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.servlet.mvc.method.annotation.AbstractJsonpResponseBodyAdvice;
@ControllerAdvice
public class JsonpAdvice extends AbstractJsonpResponseBodyAdvice {
public JsonpAdvice() {
// TODO Auto-generated constructor stub
super("callback");
}
}
jsonp和普通的ajax请求的差别
- 普通发送的是xhr请求,jsonp发送的是script请求
- 普通返回的content-type是json,jsonp返回是javascript脚本
- 普通请求的url不带参数,jsonp加了一个callback参数,后端接收到callback参数就认为是jsonp请求,会返回js代码:callback值作为函数名,返回数据作为参数
小结:jsonp动态创建一个script,发送请求,使用约定好的”callback”作为参数发送到后台,后台收到这个请求之后,就认为请求是jsonp请求,那么就不返回json,而是返回一段js代码,callback值为函数名,返回数据作为参数
完整的jsonp参数是callback后面跟一个带_的随机数值,防止被缓存,可以再发送ajax的时候设置cache为true可以缓存,默认是不缓存的。
jsonp的弊端:
- 服务器需要改动
- 只支持GET方法(请求类型为script只支持get方法)
- 发送的不是XHR请求(XHR的多种特性就无法使用)
修改被调用方的思路
解决方法:返回头增加字段
可以修改的地方:
服务器代码修改
或者
HTTP服务器修改(Nginx/Apache)
服务器代码实现
Filter解决CrosFilter.java
浏览器是先执行,再判断
如果发现是跨域请求,那么浏览器会在请求头里面增加Origin:http://locathos:8081
字段
等待响应头返回2个字段:
Access-Control-Allow-Origin
Access-Control-Allow-Methods
代码实现:
res.addHeader("Access-Control-Allow-Origin", origin);
res.addHeader("Access-Control-Allow-Methods", "*");
简单请求和非简单请求
简单请求:
- 方法为
- GET
- HEAD
- POST
- 请求头
- 无自定义头
- Content-Type是一下几种
- text/plain
- multipart/form-data
- application/x-www-form-urlencoded
非简单请求:(浏览器)
- put/delete方法的ajax请求
- 发送json格式的ajax请求
- 带自定义头的ajax请求
非简单请求会发送一个预检命令
使用post发送json
比如使用post发送json,那么会添加一个Access-Control-Request-Headers:content-type
字段
在filter中增加一个头res.addHeader("Access-Control-Allow-Headers", headers);
就可以了
这样的话,浏览器会发出2个请求
第一个是Options请求,预检命令,检查服务器是否允许这么跨域
第二个是真正的POST请求
预检命令是可以缓存的,比如缓存一个小时
res.addHeader("Access-Control-Max-Age", "3600");
cookie的跨域
cookie存放sessionid,session依赖于cookies实现
如果把下面这个设置为星号,cookie跨域会失败,带cookie必须是全匹配
res.addHeader("Access-Control-Allow-Origin", "*");
解决方法:filter获取字段,再填充回去
String origin = req.getHeader("Origin");
if (!org.springframework.util.StringUtils.isEmpty(origin)) {
//带cookie的时候,origin必须是全匹配,不能使用*
res.addHeader("Access-Control-Allow-Origin", origin);
}
// enable cookie
res.addHeader("Access-Control-Allow-Credentials", "true");
这里的cookie是在被调用方的!!
发送自定义头
Access-Control-Request-Headers
返回头增加同样字段即可
filter中实现:
String headers = req.getHeader("Access-Control-Request-Headers");
// 支持所有自定义头
if (!org.springframework.util.StringUtils.isEmpty(headers)) {
res.addHeader("Access-Control-Allow-Headers", headers);
}
被调用方的nginx配置
虚拟主机:多个域名指向同一个服务器
hosts:
127.0.0.1 b.com
创建目录 nginx/conf/vhost/
在nginx/conf/nginx.conf末尾增加一句
include vhost/*.conf;
创建文件 nginx/conf/vhost/b.com.conf
server {
listen 80;
server_name b.com;
location /{
proxy_pass http://localhost:8080/;
}
}
把b.com 80端口的访问映射到8080
注意这里不要使用filter,把AjaxserverApplication.java中关于filter的代码注释掉
配置nginx增加响应头,横杠改成下划线,首字母改为小写,if后面有空格
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_allow_headers;
if ($request_method = OPTIONS){
return 200;
}
}
}
被调用方的Apache配置
实现原理和Nginx一样,配置比较复杂
被调用方使用Spring框架配置
增加一个注解即可,TestController.java
@CrossOrigin
调用方解决
反向代理
nginx配置
a.com.conf
server {
listen 80;
server_name a.com;
location /{
proxy_pass http://localhost:8081/;
}
location /ajaxserver{
proxy_pass http://localhost:8080/test;
}
}
这样,浏览器访问a.com的时候,经过http服务器之后,是去访问8081,而8081访问的是ajaxsever,也就是8080