前端跨域解决方案

跨域请求

全称:非同源策略请求(同源即同协议、同域名和同端口),而ajax专门用于处理同源策略请求,因此对于非同源请求,特别是在前后端分离的项目当中,我们就会面临这类问题。
这里要声明一下:跨域是浏览器行为,而不是服务器行为(当前台向服务器发起请求时,后台并没有阻止请求操作,但后台返回数据时,浏览器基于安全考虑,若非同源请求,将可能禁止对请求数据的处理)

常用跨域解决方案

基于Jsonp

我们可以发现在html当中含有src属性的标签,如<srcipt>/<img>/<link>/<iframe>标签,在请求资源时都不存在跨域请求的限制。而jsonp则是基于<script>标签去请求资源,服务器则在返回数据资源时将其包在一个本地可调用的全局函数里返回,然后本地则调用这个函数

原理详解

首先因由于同源策略,我们无法通过ajax请求来获取不符合条件的资源,但是假如我们用<script>标签请求的js文件中有这样的语句:

test({"x": 1})

而我们前台的请求如下:

<script src="http://xxx.xxx.com/xxx.js"></script>

那么获取到请求以后就会执行test这个函数,并将一个对象作为参数传入,此时如果我们的前台本身有test这个函数,那么就可以成功执行这段代码。换个说法,如果现在我们通过访问一个后台接口获取到跟前面一样的字符串(之前是通过访问一个远程js资源得到的),那么因为都是用<script>标签获取的,获取的结果也一样,只是请求的url稍微有些改变,可以发现结果都是是一样的:正常执行test这个函数,例如下面这段代码:

<script src="http://xxx.xxx.com/api"></script>
<script>
function test(data) {
  console.log(data);
}
</script>

那么控制台就会成功输出data的内容。这就是jsonp的原理,可以看出和ajax请求的本质是完全不一样的,ajax是基于XmlHttpRequest,而jsonp则是基于标签动态请求,可以说是一种伪请求

缺点:由于是基于标签的src属性请求的,只能使用get请求、并且安全性不好

基于CORS跨域资源共享配置

CORS是十分常用的一种跨域解决方案,其只需要在服务端配置对应的响应头属性即可,而无需前端做任何操作,最关键的就是在返回头里配置Access-Control-Allow-Origin属性,举例(这里基于flask进行示例):

from flask import Flask, make_response
import json

app = Flask(__name__)

@app.route('/test', methods=['GET'])
def user_info():
    response = make_response(json.dumps({'data': 'content'}))
    response.headers['Access-Control-Allow-Origin'] = '*'
    return response

if __name__ == '__main__':
    app.run(debug=True, port=5000)

还有一些其他访问控制的配置如下:

Access-Control-Allow-Methods: POST, GET, PUT, DELETE, HEAD, OPTIONS
# 允许的请求方式
#(注意只有POST, GET以及HEAD是默认允许的请求,其他的请求都必须先发送一个OPTIONS的预请求
# 当预请求得到认可后才可以进行请求,而只有在该配置当中配置的方法才会得到认可)
Access-Control-Allow-Headers: 'Content-Type, ...'
# 允许请求的头部信息,没有设置在里面的header都是不允许的
Access-Control-Max-Age: '1000'
# 设置允许跨域的时间,此时在有效期内无需再发送预请求进行验证,直接发请求就可以了,单位是s
Access-Control-Allow-Credentials: true
# 是否允许发送cookie

详细参考:https://developer.mozilla.org/zh-CN/docs/Web/HTTP/Headers/Access-Control-Allow-Credentials

缺点:cors配置源地址只能配置*(多源)或者一个地址,并且如果配置的是*,那么基于安全性问题,请求时将无法携带cookie

注:
如果不想配置成*,又希望多个地址可以,那么可以在程序当中判断访问的地址是否是允许的,如果是,则加入到cors配置当中

基于Http Proxy

由于cors的弊端,又出了Http Proxy的方案,需要在webpack中配置,常在基于vue、react的前后端分离项目中使用,首先要安装对应模块包:

npm install webpack webpack-dev-server

然后在webpack的配置文件webpack.config.js中配置:

devServer: {
  port: 8080,
  proxy: {
    '/': {
      target: 'http://127.0.0.1:6666',
      // 代理的地址
      // secure: false,
      // 如果是https接口,需要配置这个参数
      changeOrigin: true
      // 是否跨域
    }
  }
}
vue-cli中配置

如果是基于vue-cli的项目工程,那么这里介绍两种方式实现proxy代理的配置:

  • 第一种:配置devServer,在build/webpack.base.conf.js中的module.exports进行和上面相同的配置
  • 第二种:配置proxyTable,直接在config/index.js中的module.exports.dev配置如下:
module.exports = {
  dev: {
    ...
    proxyTable: {
        // 代理配置
        '/': {
          target: 'http://127.0.0.1:6666',
          changeOrigin: true,
          // pathRewrite: {
          //   '^/apis': '/api'  //重写的路径  
          // }
        }
      },
  },
  ...
}

注:
配置proxy之后,实际上前端请求还是发给本身的node服务器(查看网络请求就可以发现),例如前端是8080,配置了6666的代理,那么前端会先请求本身,即8080,然后8080再通过node去访问6666,由于服务器之间的请求不存在跨域问题,所以6666返回数据给node,node再返回给前端,由于同源,所以此时数据也就没有跨域问题了
注:
在vue-cli3.0+以后,则直接在项目根目录下创建文件vue.config.js,并添加如下内容即可:

module.exports = {
  devServer: {
    proxy: {
      "/": {
        target: "http://127.0.0.1:6666/",
        changeOrigin: true
      }
    }
  }
};
基于nginx配置CORS

在nginx中也可以配置cors,举例:

server {
    listen 80;
    server_name  localhost;
    ...
    
    location / {
        root   html;
        index  index.html index.htm;
        proxy_pass http://xxx;
        add_header Access-Control-Allow-Origin *;  # 配置cors
        ...
    }
基于nginx配置反向代理

假如前台服务端口为http://127.0.0.1:3000,后台服务端接口为http://127.0.0.1:8000/api,那么因为不同源,必然存在跨域问题,此时可以在nginx中进行配置如下:监听3000端口,并配置/api的反向代理地址为:http://127.0.0.1:8000/api,配置文件示例如下:

server {
    listen 3000;
    server_name  localhost;
    ...
    
    location /api/{
        ...
        proxy_pass http://127.0.0.1:8000/api/;    # 配置反向代理
    }
}

再将前台请求的接口改为:http://127.0.0.1:3000/api,即可解决跨域问题。
(原理:经过nginx的反向代理配置,现在访问http://127.0.0.1:3000/api就相当于访问http://127.0.0.1:8000/api,而前台请求的url因为改成了http://127.0.0.1:3000/api,在浏览器看来前台和请求接口同源,也就不存在跨域问题了)

参考:https://blog.csdn.net/larger5/article/details/81286324

其他跨域解决方案

基于修改本地host文件(不推荐)

例如前台地址:127.0.0.1:6666,而后台接口地址:http://api.xxx.com,此时如果想要访问后台接口,可以在本地host文件中加一行:

127.0.0.1:6666  http://api.xxx.com

但这种方式实际上只是在模仿同源请求,并没有实质地解决跨域问题

基于Iframe的postMessage

postMessage方法允许页面间基于Iframe进行消息传递,例如A和B页面进行消息传递:

  • a.html
<html>
  <body>
    <iframe
      id="iframe"
      src="http://127.0.0.1:5500/b.html"
      style="display: none;"
    ></iframe>
  </body>
  <script>
    iframe.onload = function() {
      iframe.contentWindow.postMessage("来自A的信息...", "http://127.0.0.1:5500/b.html");
      // 发送消息给页面B
    };
    // 监听页面B发来的消息
    window.onmessage = function(ev) {
      console.log("A收到B的信息:", ev.data);
    };
  </script>
</html>
  • b.html
<html>
  <body></body>
  <script>
    //   监听页面A发来的消息
    window.onmessage = function(ev) {
      console.log("B收到A的信息:", ev.data);
      ev.source.postMessage("回信...", "*");
    };
  </script>
</html>

参考:https://www.cnblogs.com/yyy6/p/9481671.html

基于H5的web socket

由于原生web socket不太好使用,这里使用socket.io(对web socket进行了封装的框架)进行示例:

  • 服务端(基于node):
const server = require("http").createServer();
const io = require("socket.io")(server);
io.on("connection", client => {
  client.on("event", data => {
    console.log(data);
  });
  client.on("message", msg => {
    console.log(msg);
  });
  client.on("disconnect", () => {
    console.log("server has closed!");
  });
});
server.listen(3000);
  • 客户端:
<html>
  <body></body>
  <script src="https://cdn.bootcss.com/socket.io/2.3.0/socket.io.js"></script>
  <script>
    let socket = io('http://127.0.0.1:3000/');

    socket.on('connect', () => {
      socket.on('message', msg => {
        console.log(msg);
      })

      socket.on('disconnect', () => {
        console.log('server has closed!');
      })
    })

    socket.send('test');

  </script>
</html>

web socket使用参考:https://zhuanlan.zhihu.com/p/74326818
使用WebSocket进行跨域数据请求参考:https://blog.csdn.net/itkingone/article/details/83818278

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,271评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,275评论 2 380
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,151评论 0 336
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,550评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,553评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,559评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,924评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,580评论 0 257
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,826评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,578评论 2 320
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,661评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,363评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,940评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,926评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,156评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,872评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,391评论 2 342

推荐阅读更多精彩内容