浏览器限制了什么?
浏览器对js中非同源的请求做了限制,所以不包含 文件加载中src的请求,以及form表单的提交
提交form表单到另外一个域名,原来页面是无法获取新页面的内容,或者说form提交后不需要返回,但是ajax是需要返回的。
源的更改
大概就是,通过document.domain 将当前域名修改为父级域名(只能修改为父级域名)去获取父级域名下的信息
跨域请求解决方案
JSONP
通常一些静态文件不受同源策略的影响:script/css/img
<script src="..."></script>
<img src="...">
<video src="..."></video>
<audio src="..."></audio>
<embed src="...">
<frame src="...">
<iframe src="..."></iframe>
<link rel="stylesheet" href="...">
<applet code="..."></applet>
<object data="..." ></object>
利用标签没有跨域限制的漏洞,网页可以得到从其他来源动态产生的 JSON 数据。
过程:
- 声明全局的回调函数 window.callbackFunction
- 创建标签,标签的src 指向跨域的资源地址,url?params+callback = callbackFunction
- 向dom中添加该标签,dom.appendChild 添加即调用
- 服务器接收到请求,将需要返回的数据放在callback中 callbackFunction(data)
java 中 使用 response.getWriter().print(callbackFunction+”(“+resultJSON.toString(1,1)+”)”)
- 前端接收到返回,执行回调,removeChild
function jsonp({ url, params, callback }) {
return new Promise((resolve, reject) => {
let script = document.createElement('script')
window[callback] = function(data) {
resolve(data)
document.body.removeChild(script)
}
params = { ...params, callback } // wd=b&callback=show
let arrs = []
for (let key in params) {
arrs.push(`${key}=${params[key]}`)
}
script.src = `${url}?${arrs.join('&')}`
document.body.appendChild(script)
})
}
jsonp({
url: 'http://localhost:3000/say',
params: { wd: 'Iloveyou' },
callback: 'show'
}).then(data => {
console.log(data)
})
安全以及防范
1、有类似于callback的参数传递函数名
2、没有类似于callback这类参数,但是可以通过fuzz找到可能隐藏的这类参数,从而利用
防范
问题:后端getWriter 在response 添加字节流,(getWriter 也可以直接返回html 或者文案)在web容器中组合为http response 返回给浏览器,浏览器直接做了解析? 怎么做的?
设置了content-type=text/html,浏览器遇到此种类型的文件时会自动调用html解析器对文件进行处理
弊端
jsonp只支持get请求
在调用失败的时候不会返回http的状态码
CORS
Cross-Origin Resource Sharing 跨域资源共享
原理
CORS 是一个系统,它由一系列传输的HTTP头组成,这些HTTP头决定浏览器是否阻止前端 JavaScript 代码获取跨域请求的响应。
CORS 是 w3c的标准,浏览器与服务端开发的一些交互行为
比如,服务端约定,给A同学开了白名单,A同学拿着自己的身份证(请求地址)来请求,就不会受到同源的限制
通过这种方式解决跨域问题的话,会在发送请求时出现两种情况,分别为简单请求和复杂请求
简单请求
(1) 请求方法是以下三种方法之一:
HEAD
GET
POST
(2)HTTP的头信息不超出以下几种字段:
Accept
Content-Language
Last-Event-ID
Content-Type:只限于三个值application/x-www-form-urlencoded、multipart/form-data、text/plain**
application/x-www-form-urlencoded、multipart/form-data 为表单格式的数据
兼容表单
这是为了兼容表单(form),因为历史上表单一直可以发出跨域请求。AJAX 的跨域设计就是,只要表单可以发,AJAX 就可以直接发。
form请求会刷新浏览器,不会把数据返回给js,历史原因认为它是安全的,但实际上它是发送了数据的
浏览器对这两种请求的处理,是不一样的。
浏览器处理:
对于简单请求,浏览器直接发出CORS请求。在头信息之中,只增加一个Origin字段。
如果Origin指定的源,不在许可范围内,服务器会返回一个正常的HTTP回应。浏览器发现,这个回应的头信息没有包含Access-Control-Allow-Origin字段,就知道出错了,从而抛出一个错误,被XMLHttpRequest的onerror回调函数捕获。注意,这种错误无法通过状态码识别,因为HTTP回应的状态码有可能是200。
另一方面如果服务端同意客户端发送cookie:Access-Control-Allow-Credentials: true,需要设置,客户端才会发送cookie
var xhr = new XMLHttpRequest();
xhr.withCredentials = true;
复杂请求
非简单请求是那种对服务器有特殊要求的请求,比如请求方法是PUT或DELETE,或者Content-Type字段的类型是application/json。
非简单请求的CORS请求,会在正式通信之前,增加一次HTTP查询请求,称为”预检”请求(preflight)。
浏览器先询问服务器,当前网页所在的域名是否在服务器的许可名单之中,以及可以使用哪些HTTP动词和头信息字段。只有得到肯定答复,浏览器才会发出正式的XMLHttpRequest请求,否则就报错。
Ngnix代理
https://www.cnblogs.com/PengfeiSong/p/12993446.html
以ngnix为例看跨域的解决配置
ngnix配置地址代理
server{
listen 5000;
server_name serverHostName;
location = / {
proxy_pass http://serverHostName:5501/xx.html;
}
location /test {
proxy_pass http://serverHostName:3000/test;
}
}
客户端js
http.get('http://serverHostName:5000/test').then(response=>{})
客户端发送请求http://serverHostName:5000/test 到代理服务器,代理服务器开了个 5000端口监听请求,代理配置了 / 和 /test 两种路径,最终发送请求到 http://serverHostName:3000/test 拿到结果 返回给 浏览器
ngnix配置响应头response-hesder
server{
listen 5000;
server_name localhost;
location = / {
proxy_pass http://localhost:3000/;
}
location /test {
# 指定允许跨域的方法,*代表所有
add_header Access-Control-Allow-Methods *;
# 预检命令的缓存,如果不缓存每次会发送两次请求
add_header Access-Control-Max-Age 3600;
# 带cookie请求需要加上这个字段,并设置为true
add_header Access-Control-Allow-Credentials true;
# 表示允许这个域跨域调用(客户端发送请求的域名和端口)
# $http_origin动态获取请求客户端请求的域 不用*的原因是带cookie的请求不支持*号
add_header Access-Control-Allow-Origin $http_origin;
# 表示请求头的字段 动态获取
add_header Access-Control-Allow-Headers
$http_access_control_request_headers;
# OPTIONS预检命令,预检命令通过时才发送请求
# 检查请求的类型是不是预检命令
if ($request_method = OPTIONS){
return 204;
}
}
}
客户端js
http.get('http://serverHostName:5000/test').then(response=>{})
客户端发送请求http://serverHostName:5000/test 到代理服务器,代理服务器开了个 5000端口监听请求,拦截/test 的请求,对浏览器的预检 option请求做校验,校验通过给浏览器返回 204,在response header中设置跨域参数,告诉浏览器通过了跨域的校验。
跟在服务端用java,node…加的response-header一样,好处就是对于一类请求可能不在一个服务里,在网关统一配置,减少服务端的重复工作。
nodejs代理
原理跟ngnix相同,只不过ngnix是天然的入口,代理这个事情比较专业,nodejs也可做这件事而已
postMessage
实现页面和页面之间的通信,用于页面和iframe之间,他是window的api
WebSocket
原理是利用http头的方式,请求时在头部添加origin,只要服务端支持,浏览器支持,就可以进行跨域通讯,但它的连接是长连接的,在需要服务端推送时可使用此方案。
dev-server
在开发环境(我们为了模拟浏览器从服务器获取资源进行渲染,但开发阶段服务器并未部署前端代码,我们在本地开了个服务模拟服务端,浏览器访问这个模拟的本地服务端,进行开发,前端通常叫dev-server)
webpack 中的webpack-dev-server,vue-cli 以及相关打包工具 和 脚手架等,基本都能起个dev-server,webpack和vue-cli 中提供了 关于 proxy 的配置
webpack:
devServer: {
contentBase: path.resolve(__dirname, '..'),
publicPath: path.resolve(__dirname, '..', '/', router.path),
compress: true,
port: 9001
proxy: {
'/fuwu1': {
target: `http://${env}.fuwu1.com/`,
pathRewrite: { '^/fuwu1': '' },
changeOrigin: true,
onProxyReq: req => {
req.setHeader('x-weimai-token', token);
},
onProxyRes: res => {
}
},
'/fuwu2': {
target: `http://${env}.fuwu2.com/`,
pathRewrite: { '^/fuwu2': '' },
changeOrigin: true
}
},
},
在浏览器 请求 /fuwu1/接口1 会被 本地服务转发 到 http://${env}.fuwu1.com/接口1
如果不设置pathRewrite 就是 http://${env}.fuwu1.com/fuwu1/接口1
在这里也可以对请求响应的上下文进行更改 和 处理
浏览器的 请求永远都是到本地服务器 http://localhost:9001/fuwu1/接口1
vue-cli的配置同理
他们都是集成了 http-proxy-middleware ,他封装在 http-proxy
源码 https://github.com/http-party/node-http-proxy