跨域的含义

同源策略以及其限制内容

同源策略是一种约定,它是浏览器最核心也最基本的安全功能,如果缺少了同源策略,浏览器很容易受到XSS、CSFR等攻击。所谓同源是指"协议+域名+端口"三者相同,即便两个不同的域名指向同一个ip地址,也非同源。

使用cors完成跨域请求处理-LMLPHP

同源策略限制内容有:

  • Cookie、LocalStorage、IndexedDB 等存储性内容
  • DOM 节点
  • AJAX 请求不能发送

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

  • <img src="...">
  • <link href="...">
  • <scripts src="...">

常见跨域场景

当协议、子域名、主域名、端口号中任意一个不相同时,都算作不同域。不同域之间相互请求资源,就算作“跨域”。常见跨域场景如下图所示:

使用cors完成跨域请求处理-LMLPHP

特别说明两点:

第一:如果是协议和端口造成的跨域问题“前端”是处理不了的。

第二:在跨域问题上,仅仅是通过“URL的首部”来识别而不会根据域名对应的IP地址是否相同来判断。“URL的首部”可以理解为“协议, 域名和端口必须匹配”

这里你或许有个疑问:请求跨域了,那么请求到底发出去没有?

跨域并不是请求发不出去,请求能发出去,服务端能收到请求并正常返回响应,只是响应被浏览器拦截了。你可能会疑问明明通过表单的方式可以发起跨域请求,为什么 Ajax 就不行?因为归根结底,跨域是为了阻止用户读取到另一个域名下的内容,Ajax 可以获取响应,浏览器认为这不安全,所以拦截了响应。但是表单并不会获取新的内容,所以可以发起跨域请求。同时也说明了跨域并不能完全阻止 CSRF,因为请求毕竟是发出去了。

实现跨域的方式之一:CORS

  • CORS 全称是跨域资源共享(Cross-Origin Resource Sharing),是一种 AJAX 跨域请求资源的方式,支持现代浏览器,IE支持10以上。

  • CORS与JSONP的使用目的相同,但是比JSONP更强大。JSONP只支持GET请求,CORS支持所有类型的HTTP请求。JSONP的优势在于支持老式浏览器,以及可以向不支持CORS的网站请求数据。

  • 浏览器会自动进行 CORS 通信,实现 CORS 通信的关键是后端。只要后端实现了 CORS,就实现了跨域。

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

    • response = HttpResponse('向非同源的网址返回其请求所对应的响应数据')
    • response["Access-Control-Allow-origin"] = "*"

前端发送请求类型

虽然设置 CORS 和前端没什么关系,但是通过这种方式解决跨域问题的话,会在发送请求时出现两种情况,分别为简单请求复杂请求

简单请求

只要同时满足以下两大条件,就属于简单请求

条件1:使用下列方法之一:

  • GET
  • HEAD
  • POST

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

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

请求中的任意 XMLHttpRequestUpload 对象均没有注册任何事件监听器; XMLHttpRequestUpload 对象可以使用 XMLHttpRequest.upload 属性访问。

复杂请求

不符合以上条件的请求就是复杂请求。
复杂请求的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
let xhr = new XMLHttpRequest()
document.cookie = 'name=xiamen' // cookie不能跨域
xhr.withCredentials = true // 前端设置是否带cookie
xhr.open('PUT', 'http://localhost:4000/getData', true)
xhr.setRequestHeader('name', 'xiamen')
xhr.onreadystatechange = function() {
  if (xhr.readyState === 4) {
    if ((xhr.status >= 200 && xhr.status < 300) || xhr.status === 304) {
      console.log(xhr.response)
      //得到响应头,后台需设置Access-Control-Expose-Headers
      console.log(xhr.getResponseHeader('name'))
    }
  }
}
xhr.send()
//server1.js
let express = require('express');
let app = express();
app.use(express.static(__dirname));
app.listen(3000);
//server2.js
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) {
  console.log(req.headers)
  res.setHeader('name', 'jw') //返回一个响应头,后台需设置
  res.end('****')
})
app.get('/getData', function(req, res) {
  console.log(req.headers)
  res.end('****')
})
app.use(express.static(__dirname))
app.listen(4000)

上述代码由http://localhost:3000/index.htmlhttp://localhost:4000/跨域请求,正如我们上面所说的,后端是实现 CORS 通信的关键。

应用django-cors-headers

drf后端+vue前端的项目

在做以drf框架作为后端支撑以及以vue前端框架作为页面展示的前后端分离的web项目中,前端会向后端请求数据,也会涉及到非同源跨域的问题,以下操作将做详细处理

# vue前端域名
www.aliang.cn

# drf后端域名
api.aliang.com

修改vue项目的配置信息

  • 通过浏览器访问前端vue项目,会出现nginx的欢迎页面,主要因为我们当前操作中已经有一个nginx监听了80端口,所以访问www.aliang.cn网址时,会自动被转发到127.0.0.1本机,因为没有网址默认端口是80端口,所以被nginx进行处理了当前请求,因此我们暂时先把nginx关闭。
# 查找nginx的进程
ps -ef|grep nginx
# 关闭进程
sudo kill -9 nginx进程号
  • 修改vue项目的配置文件config/index.js
    host: 'www.aliang.cn', // can be overwritten by process.env.HOST
    port: 8080, // can be overwritten by process.env.PORT, if port is in use, a free one will be determined
    autoOpenBrowser: true,
  • 保存修改信息,并重启项目

  • 通过浏览器访问drf项目,会出现以下错误信息

    • You may need to add 'api.aliang.com' to ALLOWED_HOSTS
  • 可以通过设置drf项目中的配置文件,settings/dev.py的ALLOWED_HOSTS,设置允许访问

# 设置哪些客户端可以通过地址访问到后端
ALLOWED_HOSTS = [
    'api.aliang.cn',
]
  • 现在,前端与后端分处不同的域名,我们需要为后端添加跨域访问的支持否则前端使用axios则无法请求到后端提供的api数据,故使用CORS来解决后端对跨域访问的支持。

使用django-cors-headers扩展

  • 安装
pip install django-cors-headers -i https://pypi.douban.com/simple/
  • drf后端项目配置文件中添加应用
INSTALLED_APPS = (
    ...
    'corsheaders',
    ...
)
  • drf后端项目配置文件中设置中间件【必须写在第一个位置】
MIDDLEWARE = [
    'corsheaders.middleware.CorsMiddleware',
    ...
]
  • drf后端项目配置文件中添加白名单
# CORS组的配置信息
CORS_ORIGIN_WHITELIST = (
    'www.aliang.cn:8080',
)
CORS_ALLOW_CREDENTIALS = True  # 允许ajax跨域请求时携带cookie

完成了上面的步骤,我们就可以通过后端提供数据给前端使用ajax访问了。

  • 前端使用 axios就可以访问到后端提供给的数据接口,但是如果要附带cookie信息,前端还要设置一下。

    • 需要注意的是,如果要发送Cookie,Access-Control-Allow-Origin就不能设为星号,必须指定明确的、与请求网页一致的域名。同时,Cookie依然遵循同源政策,只有用服务器域名设置的Cookie才会上传,其他域名的Cookie并不会上传,且(跨源)原网页代码中的document.cookie也无法读取服务器域名下的Cookie。
  • 前端引入axios插件并配置允许axios发送cookie信息[axios本身也不允许ajax发送cookie到后端]

npm i axios -S
  • 在main.js中引用 axios插件
import axios from 'axios'; // 从node_modules目录中导入包
// 允许ajax发送请求时附带cookie
axios.defaults.withCredentials = true;

Vue.prototype.$axios = axios; // 把对象挂载vue中
08-07 19:48