跨域总结:从CORS到Ngnix

前后端数据交互经常会碰到请求跨域 , 什么是跨域 , 以及有哪些跨域方式 , 我觉得我应该记录下来。
一.什么是跨域?

  1. 什么是同源策略及其限制内容?

同源策略是一个安全策略。所谓的同源,指的是协议,域名,端口相同。浏览器处于安全方面的考虑,只允许本域名下的接口交互,不同源的客户端脚本,在没有明确授权的情况下,不能读写对方的资源。
同源策略限制的内容有:

Cookie , LocalStorage ,IndexedDB等存储性内容。

DOM节点

AJAX请求发送后,非同源会被浏览器拦截。

但是有三个标签是允许跨域加载资源:

这也就是JSONP的来源了。
2. 常见的跨域场景
当协议,子域名,主域名,端口号中任意一格不相同时,都算作不同源。
note: 特别说明两点:

第一 : 如果是协议和端口造成的跨域问题"前端"是无能为力的。
第二: 在跨域问题上,仅仅是通过"URL的首部"来识别而不会根据域名对应的IP地址是否相同来判断。“URL的首部"可以理解为”“协议,域名和端口必须匹配”。
第三: 请求跨域了,那么到底发出去没有?
跨域并不是请求发不出去,请求能发出去,服务端能收到请求并正常返回结果,只是结果被浏览器拦截了

二.跨域解决方案

  1. JSONP
    1.1 JSONP原理
    利用

声明一个回调函数,其函数名(如show)当做参数值,要传递给跨域请求数据的服务器,函数形参为要获取目标数据(服务器返回的data)
创建一个 <script src=>标签 ,把那个跨域的API数据接口地址,赋值给script的src, 还要在这个地址中向服务器传递该函数名(可以通过问号传参?callback=show)。
服务器接收到请求后,需要进行特殊的处理:把传递进来的函数名和它需要给你的数据拼接成一个字符串,例如:传递进去的函数名是show,它准备好的数据是 show(‘我不爱你’)。
最后服务器把准备的数据通过HTTP协议返回给客户端,客户端再调用执行之前声明的回调函数(show),对返回的数据进行操作。

现在我们来自己封装一个JSONP函数
// 封装 JSONP函数
function jsonp({ url, params, callback }) {
return new Promise((resolve, reject) => {
let script = document.createElement(‘script’);
params = JSON.parse(JSON.stringify(params));
let arrs = [];
for (let key in params) {
arrs.push(${key}=${params[key]});
}
arrs.push(callback=${callback});
script.src = ${url}?${arrs.join('&')};
document.body.appendChild(script);
function callback(data) {
resolve(data);
docement.body.removeChild(script);
}
window[callback] = function (data) {
resolve(data);
document.body.removeChild(script);
}
})
}
// 前端调用
jsonp({
url: ‘http://localhost:3000/say’,
params: {
wd: ‘I Love you’
},
callback: ‘show’
}).then(data => {
console.log(data)
})

// 后端响应
// 这里用到了 express
var express = require(‘express’);
var router = express.Router();
var app = express();
router.get(’/say’,function(req,res,next) {
//要响应回去的数据
let data = {
username : ‘zs’,
password : 123456
}

let {wd , callback} = req.query;
console.log(wd);
console.log(callback);
// 调用回调函数 , 并响应
res.end(${callback}(${JSON.stringify(data)}));
})
app.listen(3000);
复制代码
2. CORS
CORS需要浏览器和后端同时支持。IE8和 IE9需要通过 XDomainRequest来实现
浏览器会自动进行 CORS通信,实现CORS通信的关键是后端。只要后端实现了CORS,实现了跨域。
服务端设置 Access-Control-Allow-Origin 就可以开启CORS。该属性表示哪些域名可以访问资源,如果设置通配符则表示所有网站都可以访问资源。
虽然设置CORS和前端没有什么关系,但是通过这种方式解决跨域问题的话,会在发送请求时出现两种情况,分别为简单请求和复杂请求。
简单请求
只要同时满足以下两个条件,就属于简单请求
条件1 : 使用下列方法之一:

GET
HEAD
POST

条件2 :Content-Type 的值仅限于下列三者之一 :

text/plain
multipart/form-data
application/x-www-form-urlencoded
请求中的任意 XMLHttpRequestUpload 对象均没有注册任何事件监听器;

复杂请求
不符合以上条件的请求就肯定是复杂请求了。复杂请求的CORS请求,会在正式通信之前,增加一次HTTP查询请,称为"预检"请求,该请求是option方法的 , 通过该请求来知道服务端是否允许跨域请求。
我们用 PUT 向后台请求时, 属于复杂请求,后台需如下配置:
// 允许哪个方法访问我
res.setHeader(‘Access-Control-Allow-Methods’, ‘PUT’)
// 预检的存活时间
res.setHeader(‘Access-Control-Max-Age’, 6)
// OPTIONS请求不做任何处理
if (req.method === ‘OPTIONS’) {
res.end()
}
// 定义后台返回的内容
app.put(’/getData’, function(req, res) {
console.log(req.headers)
res.end(‘我不爱你’)
})
复制代码
接下来我们看下一个完整请求的例子,并且介绍下CORS请求相关的字段
//index.html
// 前端代码

// server1.js
let express = require(‘express’);
let app = express();
app.use(express.static(__dirname))
app.listen(3000)

// 后端代码
let express = require(‘express’)
let app = express()
let whitList = [‘http://localhost:3000’] //设置白名单
app.use(function(req, res, next) {
let origin = req.headers.origin
if (whitList.includes(origin)) {
// 设置哪个源可以访问我
res.setHeader(‘Access-Control-Allow-Origin’, origin)
// 允许携带哪个头访问我
res.setHeader(‘Access-Control-Allow-Headers’, ‘name’)
// 允许哪个方法访问我
res.setHeader(‘Access-Control-Allow-Methods’, ‘PUT’)
// 允许携带cookie
res.setHeader(‘Access-Control-Allow-Credentials’, true)
// 预检的存活时间
res.setHeader(‘Access-Control-Max-Age’, 6)
// 允许返回的头
res.setHeader(‘Access-Control-Expose-Headers’, ‘name’)
if (req.method === ‘OPTIONS’) {
res.end() // OPTIONS请求不做任何处理
}
}
next()
})
app.put(’/getData’, function(req, res) {
let data = {
username : ‘zs’,
password : 123456
}
console.log(req.headers)
res.setHeader(‘name’, ‘jw’) //返回一个响应头,后台需设置
res.end(JSON.stringify(data))
})
app.get(’/getData’, function(req, res) {
console.log(req.headers)
res.end(‘he’)
})
app.listen(4000)
复制代码
3. postMessage
postMessage是HTML5 XMLHttRequest Level 2中的 API, 并且为数不多跨域跨域操作的window属性之一,它可用于解决以下方面的问题:

页面和其打开的新窗口的数据传递
多窗口之间消息传递
页面与嵌套的iframe消息传递
上面三个场景的跨域数据传递

postMessage()方法允许来自不同源的脚本采用异步方式进行有限的通信,可以实现跨文本档,多窗口,跨域消息传递
API大概是这样的
otherWindow.postMessage(message,targetOrigin,[transfer])
复制代码

message:将要发送到其他window的数据
targetOrigin:通过窗口的origin属性来指定哪些窗口能接收到消息事件,其值可以是字符串"*"(表示无限制)。或者一个URL。在发送消息的时候,如果目标窗口的协议,主机地址或端口这三者的任意一项不匹配 targetOrigin提供的值,那么消息就不会被发送;只有三者完全匹配,消息才会被发送。
transfer(可选):是一串和 message同时传递的 Transferable对象。这些对象的所有权将被转移给消息的接收方,而发送一方将不再保有所有权。

下面,我们来用一个例子来说明任何使用
4. websocket
Websocket是HTML5的一个持久化协议,它实现了浏览器与服务器的全双工通信,同时也是跨域的一种解决方案。WebSocket和HTTP都是应用层协议,都是基于TCP协议。
但是, WebSocket是一种双向通信协议,在建立连接之后,WebSocket的server与client都能主动向对方发送或接收数据。 同时,WebSocket在建立连接时需要借助HTTP协议,连接建立好了之后client与server之间的双向通信就与HTTP无关了。
原生WebSocket API使用起来不太方便,我们使用 Socket.io ,它很好地封装了 webSocket接口,提供了更简单,灵活的接口,也对不支持webSocket的浏览器提供了向下兼容。
我们再来看一个例子:
//前端

// 后端
let express = require(‘express’);
let app = express();
let WebSocket = require(‘ws’);
let wss =new WebSocket.Server({port:4000})

// 定义数据
let Data = {
username : ‘zs’,
password : 123456
}

wss.on(‘connection’, function(ws) {
ws.on(‘message’,function(data) {
console.log(JSON.parse(data))
ws.send(JSON.stringify(Data))
})
})
复制代码
5. node中间件代理(两次跨域)
实现原理: **同源策略是浏览器需要遵循的标准,而如果是服务器向服务器请求就无需遵循同源策略。**代理服务器,需要做以下几个步骤:

接受客户端请求
将请求转发给服务器
拿到服务器响应数据
将响应转发给客户端

我们现在再来看一个例子: 本地文件index.html文件,通过代理服务器http://localhost:3000向目标服务器http://localhost:4000请求数据
//前端

// 代理服务器(http://localhost:3000)
const http = require(‘http’);
// 第一步 : 接受客户端请求
const server = http.createServer((request, response) => {
// 代理服务器,直接和浏览器直接交互,需要设置CORS的首部字段
response.writeHead(200, {
‘Access-Control-Allow-Origin’: ‘’,
‘Access-Control-Allow-Methods’: '
’,
‘Access-Control-Allow-Headers’: ‘Content-Type’
})
if(request.method === ‘options’) {
response.end();
}
// 第二步 : 将请求转发给服务器
const proxyRequest = http
.request(
{
host: ‘127.0.0.1’,
port: 4000,
url: ‘/’,
method: request.method,
headers: request.headers,
},
serverResponse => {
// 第三步:收到服务器的响应
var body = ‘’
serverResponse.on(‘data’, chunk => {
body += chunk
})
serverResponse.on(‘end’, () => {
console.log('The data is ’ + body)
// 第四步:将响应结果转发给浏览器
response.end(body)
})
}
)
.end()
})

server.listen(3000, ()=>{
console.log(‘The proxyServer is running at http://localhost:3000’)
})

//后端服务器
// http://localhost:4000
const http = require(‘http’);
const data = { title : ‘zs’, password : ‘123’};
const server = http.createServer((request,response) => {
if(request.url === ‘/’) {
response.end(JSON.stringify(data))
}
})

server.listen(4000,‘127.0.0.1’,()=> {
console.log(‘The server is running at http://127.0.0.1:4000’)
})
复制代码
nginx反向代理
前言
说实话,nginx要熟悉有点花时间,这里我就介绍怎么用nginx跨域好了
这个东西我也不是很会

nginx与node.js
‘Nginx是一款轻量级的HTTP服务器,采用事件驱动的异步非阻塞处理方式框架,这让其具有极好的IO性能,时常用于服务端的反向代理与负载均衡’
复制代码
这似乎与node.js很像啊,但,其实他们都有自己擅长的领域:Nginx更擅长于底层服务端资源的处理(静态资源处理转发,反向代理,负载均衡等),node.js更擅长于上层具体业务逻辑的处理。两者可以实现结合,助力前端开发。
代理服务器
什么是反向代理?互联网应用基本都基于CS基本结构,即 client端和server端。代理其实就是client端和真正的server端之间增加一层提供特定的服务的服务器,即代理服务器。
正向代理
正向代理大家肯定都用过,其实,翻墙工具其实就是一个正向代理工具。它会把浏览器访问墙外服务器server的网页请求,代理到一个可以访问该网站的代理服务器proxy,这个代理服务器proxy把墙外服务器server上的网页内容获取,再转发给客户端。
具体流程如下:

反向代理
反向代理与正向代理相反,先看流程图:

在反向代理中(实际上,这种情况发生在所有大型网站的页面请求中),客户端发送的请求,想要访问server服务器上的内容。但将被发送到一个代理服务器proxy,这个代理服务器将把请求代理到和自己属于同一个LAN下的内部服务器上,而用户真正想获得的内容就存储在这些内部服务器上。
即向外部客户端提供一个统一的代理入口,客户端的请求,都先经过这个proxy服务器,至于在内网真正访问哪台服务器内容,由这个proxy去控制。
概括的说:就是代理服务器和真正server服务器可以直接访问,属于一个LAN(服务器内网);代理对用户是透明的,即无感知的。
为什么要nginx反向代理
原因:

安全及权限。可以看出,使用反向代理后,用户端将无法直接通过请求访问真正的内容服务器,而必须首先通过nginx.可以通过在nginx层上将危险或者没有权限的请求内容过滤,从而保证了服务器的安全。
负载均衡。例如一个网站的内容被部署在若干台服务器上,可以把这些机子看成一个集群,那么nginx可以将接收到的客户端请求"均匀地"分配到这个集群中所有的服务器上(内部模块提供了多种负载均衡算法),从而实现服务器压力的负载均衡。此外,nginx还带有健康检查功能(服务器心跳检查),会定期轮询向集群里的所有服务器发送健康检查请求,来检查集群中是否有服务器处于异常状态,一旦发现某台服务器异常,那么在以后代理进来的客户端请求都不会被发送到该服务器上(直达后面的健康检查发现该服务器恢复正常),从而保证客户端访问的稳定性。

windows下nginx的下载与安装
nginx下载

nginx配置
nginx的一点功能

  1. 快速实现简单的访问控制
    经常会遇到希望让某些特定的用户的群体(比如公司内网)访问,或者控制某个url不让人访问.
    nginx的配置如下:
    location / {
    deny 192.168.1.100; // 禁止
    allow 192.168.1.10/200; // 允许
    allow 10.110.50.16;
    deny all; // 禁止所有
    }
    复制代码
    其实 deny和 allow是 ngx_http_access_module(已内置) 模块中的语法。
    采用的是从上到下匹配方式,匹配到就跳出不再继续匹配。
    比如说,上述配置的意思是,首先禁止 192.168.1.100访问,然后允许 192.168.1.10 - 200 ip段内的访问 (排除 192.168.1.100 ) ,同时允许 10.110.50.16 这个单独ip 的访问,剩下未匹配的全部禁止访问。
    2.解决跨域
    在众多的解决跨域方式中,都不可避免的都需要服务端进行支持,使用Nginx可以纯前端解决请求跨域问题。
    nginx配置如下:
    server
    {
    listen 3002;
    server_name localhost;
    location /ok {
    proxy_pass http://localhost:3000;

     #   指定允许跨域的方法,*代表所有
     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 200;
     }
    

    }
    }
    复制代码
    参考资料
    九种跨域请求实现方式
    nginx与前端开发

发布了68 篇原创文章 · 获赞 1 · 访问量 946

猜你喜欢

转载自blog.csdn.net/A669MM/article/details/104929055