登录/注册

谈一谈跨域,以及跨域解决方法

发布于 2020-10-16 15:02:35 阅读 55
  • 前端
  • JavaScript
原创

01 前言

跨域是几乎我们在请求数据是都会遇到问题,那究竟什么是跨域,又如何解决呢?话不多说我们直接开始。

02 同源策略

同源策略 是一个安全策略。所谓的同源,指的是协议,域名,端口相同。

同源策略限制了一下行为:

  • Cookie、LocalStorage、SessionStorage 和 IndexDB 无法读取
  • DOM 和 JS 对象无法获取
  • Ajax 请求发送不出去

03 什么是跨域

跨域,是指浏览器不能执行其他网站的脚本。它是由浏览器的同源策略造成的,是浏览器对JavaScript实施的安全限制。

04 解决方法

JSONP跨域

JSONP跨域是利用script标签src没有跨域限制的漏洞,将前端方法作为参数传递到服务器端,由服务器端注入参数后返回,实现服务器端向客户端通信。
「实现」

  • 创建script标签
  • 设置script标签的src属性,以问号传递参数,设置好回调函数callback名称
  • 插入html文本中
  • 调用回调函数,res参数就是获取的数据
let script = document.createElement('script');

script.src = 'http://www.xxx.com/login?username=xxx&callback=callback';

document.body.appendChild(script);

function callback(res) {
  console.log(res);
}

jquery方法

$.ajax({
  url: 'http://www.baidu.cn/login',
  type: 'GET',
  dataType: 'jsonp', //请求方式为jsonp
  jsonpCallback: 'callback',
  data: {
    "username": "xxx"
  }
})

「JSONP的优点」

  • 它不像XMLHttpRequest对象实现的Ajax请求那样受到同源策略的限制
  • 它的兼容性更好,在更加古老的浏览器中都可以运行,不需要XMLHttpRequest或ActiveX的支持
  • 它在请求完毕后可以通过调用callback的方式回传结果。

「JSONP的缺点」

  • 它只支持GET请求,具有局限性,不安全,可能会受到XSS攻击
  • 它只能解决跨域HTTP请求这种情况,不能解决不同域的两个页面之间如何进行JavaScript调用的问题

跨域资源共享CORS

1.什么是CORS

CORS(Cross-Origin Resource Sharing)跨域资源共享,是一个W3C的标准。CORS背后的基本思想是使用自定义的HTTP头部让浏览器与服务器进行沟通,从而决定请求或响应是应该成功还是失败。

CORS需要浏览器和服务器同时支持。目前,虽然所有浏览器都支持该功能,但IE8/9需要使用XDomainRequest对象来支持CORS。

整个CORS通信过程,都是浏览器自动完成,不需要用户参与。对于前端开发者来说,CORS通信与同源的AJAX通信没有差别,代码完全一样。浏览器一旦发现AJAX请求跨源,就会自动添加一些附加的头信息,有时还会多出一次附加的请求,但用户不会有感觉。

因此,实现CORS通信的关键是服务器,服务端设置 Access-Control-Allow-Origin 就可以开启 CORS。 该属性表示哪些域名可以访问资源,如果设置通配符(*)则表示所有网站都可以访问资源。

2.简单请求与非简单请求

CORS通行的这种方式分为两种请求:一种是 简单请求,另一种是 非简单请求,浏览器对这两种请求方式的处理方式是不同的。

所谓的简单请求,就是满足一下条件的请求,反之则为非简单请求。

条件1:请求方式为以下三者之一

  • GET
  • HEAD
  • POST

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

  • text/plain
  • multipart/form-data
  • application/x-www-form-urlencoded

「简单请求」
对于简单请求,浏览器直接发出CORS请求。具体来说,就是浏览器发现这次跨源AJAX请求是简单请求,就自动在头信息之中,添加一个Origin字段。

Origin: http://localhost:5678

Origin字段用来说明,本次请求来自哪个源(协议 + 域名 + 端口)。服务器根据这个值,决定是否同意这次请求。

如果Origin指定的源不在许可范围内,服务器会返回一个正常的HTTP响应,但响应头信息没有包含Access-Control-Allow-Origin字段,从而抛出一个错误被XMLHttpRequest的onerror回调函数捕获。

注意,这种错误无法通过状态码识别,因为HTTP回应的状态码有可能是200。

如果Origin指定的源在许可范围内,服务器返回的响应,会多出几个头信息字段:

Access-Control-Allow-Origin: http://api.bob.com
Access-Control-Allow-Credentials: true
Access-Control-Expose-Headers: FooBar
Content-Type: text/html; charset=utf-8

Access-Control-Allow-Origin: 必须。它的值要么是请求时Origin字段的值,要么是一个表示接受任意域名的请求。

Access-Control-Allow-Credentials: 可选。它是一个布尔值且只能为true,表示允许浏览器发送Cookie。默认情况下,Cookie不包括在CORS请求之中。如果服务器不允许浏览器发送Cookie,删除该字段即可。

Access-Control-Expose-Headers: 可选。CORS请求时,XMLHttpRequest对象的getResponseHeader()方法只能拿到6个基本字段:Cache-Control、Content-Language、Content-Type、Expires、Last-Modified、Pragma。如果想拿到其他字段,就必须在Access-Control-Expose-Headers里面指定。

需要注意的是,如果需要发送Cookie,Access-Control-Allow-Origin就不能设为星号,必须指定明确的、与请求网页一致的域名。同时,Cookie依然遵循同源政策,只有用服务器域名设置的Cookie才会上传,其他域名的Cookie并不会上传,且跨源原网页代码中的document.cookie也无法读取服务器域名下的Cookie。

「非简单请求」

非简单请求一般对服务器有特殊要求的请求,比如请求方法是PUT或DELETE,Content-Type字段的类型是application/json等。

非简单请求的CORS请求,会在正式通信之前,增加一次HTTP查询请求,称为 "预检"请求(preflight)。 该请求的方法是OPTIONS方法,浏览器先询问服务器,当前网页所在的域名是否在服务器的许可名单之中,以及可以使用哪些HTTP动词和头信息字段。只有得到肯定答复,浏览器才会发出正式的XMLHttpRequest请求,否则就报错。

WebSocket协议跨域

Websocket 是 HTML5 的一个持久化的协议,它实现了浏览器与服务器的全双工通信,同时也是跨域的一种解决方案。

WebSocket 和 HTTP 都是应用层协议,都基于 TCP 协议。但是 WebSocket 是一种 双向通信协议,在建立连接之后,WebSocket 的 server 与 client 都能主动向对方发送或接收数据。虽然 WebSocket 在建立连接时需要借助 HTTP 协议,但连接建立好了之后 client 与 server 之间的双向通信就与 HTTP 无关了。

来看下面这个例子

client向localhost:5678发送数据和接受数据👇

let socket = new WebSocket('ws://localhost:5678');
socket.onopen = ()=> {
  socket.send('test');//向服务器发送数据
}
socket.onmessage = (e)=> {
  console.log(e.data);//接收服务器返回的数据
}
socket.onclose = (e)=> {
  //当客户端收到服务端发送的关闭连接请求时,触发onclose事件
  console.log("close");
}
socket.onerror = (e)=> {
  //如果出现连接、处理、接收、发送数据失败的时候触发onerror事件
  console.log(error);
}

后端部分👇

// server.js
const WebSocket = require('ws');
let socketServer = new WebSocket.Server({port:5678});
socketServer.on('connection',(ws)=> {
  ws.on('message', (data)=> {
    console.log(data);
    ws.send('server data')
  });
});

想要深入了解WebSocket,可以尝试使用Socket.io

nginx代理跨域

1.nginx配置解决iconfont跨域

浏览器跨域访问js、css、img等常规静态资源被同源策略许可,但iconfont字体文件(eot/otf/ttf/woff/svg)例外,此时可在nginx的静态资源服务器中加入以下配置。

location / {
  add_header Access-Control-Allow-Origin *;
}

2.nginx反向代理接口跨域

「原理」
同源策略是浏览器的安全策略,不是HTTP协议的一部分。服务器端调用HTTP接口只是使用HTTP协议,不会执行JS脚本,因此不需要同源策略,也就不存在跨越问题。

「实现」
通过nginx配置一个代理服务器(域名与domain1相同,端口不同)做跳板机,反向代理访问domain2接口,并且可以顺便修改cookie中domain信息,方便当前域cookie写入,实现跨域登录。

nginx配置👇

#proxy服务器
server {
  listen 81;
  server_name www.domain1.com;
  location / {
    proxy_pass http://www.domain2.com:8080;  #反向代理
    proxy_cookie_domain www.domain2.com www.domain1.com; #修改cookie里域名
    index index.html index.htm;
    # 当用webpack-dev-server等中间件代理接口访问nignx时,此时无浏览器参与,故没有同源限制,下面的跨域配置可不启用
    #add_header Access-Control-Allow-Origin http://www.domain1.com;  #当前端只跨域不带cookie时,可为*
    add_header Access-Control-Allow-Credentials true;
  }
}

前端部分👇

let xhr = new XMLHttpRequest();

// set browser read/write cookie
xhr.withCredentials = true;

// open nginx proxy
xhr.open('get', 'http://www.domain1.com:81/?user=admin', true);
xhr.send();

后端部分👇

const http = require('http');
const qs = require('querystring');

let server = http.createServer();
server.on('request', (req, res)=> {
  let params = qs.parse(req.url.substring(2));
  // write cookie
  res.writeHead(200, {
    'Set-Cookie': 'l=a123456;Path=/;Domain=www.domain2.com;HttpOnly'  // HttpOnly:脚本无法读取
  });
  res.write(JSON.stringify(params));
  res.end();
});
server.listen('8080');

评论区

Tz_BB
1粉丝

励志做一条安静的咸鱼,从此走上人生巅峰。

0

0

0