CORS跨域问题梳理

什么是跨域#

浏览器的同源策略:浏览器为确保资源安全,而遵循的一种策略,该策略对访问资源进行了一些限制
https://www.w3.org/Security/wiki/Same_Origin_Policy
https://developer.mozilla.org/zh-CN/docs/Web/HTTP/CORS

1、发生跨域后会出现的问题:#

1、限制DOM访问
<!-- <iframe id="framePage" src="./demo.html"></iframe> -->
<iframe id="framePage" src="https://www.baidu.com"></iframe>

<script type="text/javascript" >
  function showDOM(){
    const framePage = document.getElementById('framePage')
    console.log(framePage.contentWindow.document) //同源的可以获取,非同源的无法获取
  }
</script>
2、限制cookie访问(实际上dom无法访问,cookie也自然无法访问了)
<iframe id="baidu" src="http://www.baidu.com" width="500" height="300"></iframe>

<script type="text/javascript" >
  // 访问的是当前源的cookie,并不是baidu的cookie
  console.log(document.cookie)
</script>
3、限制Ajax获取数据(请求可以发出,但是无法获取源B的响应数据)
const url = 'https://www.toutiao.com/hot-event/hot-board/?origin=toutiao_pc'
let result = await fetch(url)
let data = await result.json();
console.log(data)

2、注意点#

1、跨域限制仅存在浏览器端,服务端不存在跨域限制
2、即使跨域了,Ajax 请求也可以正常发出,但响应数据不会交给开发者
image

3、<link><script>、...... 这些标签发出的请求也可能跨域,只不过浏览器对标签跨域不做严格限制,对开发几乎无影响

跨域的解决方案#

1、解决方案一:CORS#

CORS 全称:Cross-Origin Resource Sharing(跨域资源共享),是用于控制浏览器校验跨域请求的一套规范,服务器依照 CORS 规范,添加特定响应头来控制浏览器校验,大致规则如下:
● 服务器明确表示拒绝跨域请求,或没有表示,则浏览器校验不通过。
● 服务器明确表示允许跨域请求,则浏览器校验通过

(1)处理简单请求

简单请求

  • 请求方法为:GET、HEAD、POST
  • 请求头的Content-Type的值只能是以下三种:
    ● text/plain
    ● multipart/form-data
    ● application/x-www-form-urlencoded

服务器响应时候,添加Access-Control-Allow-Origin响应头,声明允许某个源发起跨域请求,浏览器校验通过
image

服务端核心代码(以express框架为例)
// 处理跨域中间件
function corsMiddleWare(req,res,next){
  // 允许 http://127.0.0.1:5500 这个源发起跨域请求
  // res.setHeader('Access-Control-Allow-Origin','http://127.0.0.1:5500')
  
  // 允许所有源发起跨域请求
  res.setHeader('Access-Control-Allow-Origin','*')
  next()
}

// 配置路由并使用中间件
app.get('/',corsMiddleWare,(req,res)=>{
  res.send('hello!')
})

(2)处理复杂请求

复杂请求:不是简单请求的请求就是复杂请求,比如application/json
复杂请求会自动发送预检请求

image

解决方案

第一步:服务器先通过浏览器的预检请求,服务器需要返回如下响应头:

image

第二步:处理实际的跨域请求(与处理简单请求跨域的方式相同)
image

服务端核心代码
// 处理预检请求
app.options('/students', (req, res) => {
  // 设置允许的跨域请求源
  res.setHeader('Access-Control-Allow-Origin', 'http://127.0.0.1:5500')
  // 设置允许的请求方法
  res.setHeader('Access-Control-Allow-Methods', 'GET')
  // 设置允许的请求头
  res.setHeader('Access-Control-Allow-Headers', 'school')
  // 设置预检请求的缓存时间(可选)
  res.setHeader('Access-Control-Max-Age', 7200)
  // 发送响应
  res.send()
})

// 处理实际请求
app.get('/students', (req, res) => {
  // 设置允许的跨域请求源
  res.setHeader('Access-Control-Allow-Origin', 'http://127.0.0.1:5500')
  // 随便设置一个自定义响应头
  res.setHeader('abc',123)
  // 设置允许暴露给客户端的响应头
  res.setHeader('Access-Control-Expose-Headers', 'abc')
  // 打印请求日志
  console.log('有人请求/students了')
  // 发送响应数据
  res.send(students)
})

2、解决方案二:使用cors库等#

nodejs
// 配置cors库
app.use(cors())
// cors中间件配置
const corsOptions = {
  origin: 'http://127.0.0.1:5500', // 允许的源
  methods: ['GET', 'POST', 'PUT', 'DELETE', 'HEAD', 'OPTIONS'], // 允许的方法
  allowedHeaders: ['school'], // 允许的自定义头
  exposedHeaders: ['abc'], // 要暴露的响应头
  optionsSuccessStatus: 200 // 预检请求成功的状态码
};

app.use(cors(corsOptions)); // 使用cors中间件
django
INSTALLED_APPS = [
    'django.contrib.admin',
	...
    'django.contrib.messages',
    'django.contrib.staticfiles',
    # 处理跨域
    'corsheaders',
]

MIDDLEWARE = [
	...
    'django.contrib.sessions.middleware.SessionMiddleware',
    # 处理跨域
    "corsheaders.middleware.CorsMiddleware",
	...
]

3、解决方案三、JSONP#

  1. JSONP 概述: JSONP 是利用了<script>标签可以跨域加载脚本,且不受严格限制的特性,可以说是程序员智慧的结晶,早期一些浏览器不支持 CORS 的时,可以靠 JSONP 解决跨域。
  2. 基本流程:
    ○ 第一步:客户端创建一个<script>标签,并将其src属性设置为包含跨域请求的 URL,同时准备一个回调函数,这个回调函数用于处理返回的数据。
    ○ 第二步:服务端接收到请求后,将数据封装在回调函数中并返回。
    ○ 第三步:客户端的回调函数被调用,数据以参数的形势传入回调函数。
    image
JavaScript核心代码
<button onclick="getTeachers()">获取数据</button>

<script type="text/javascript" >
  function callback(data){
    console.log(data)
  }

  function getTeachers(url){
    // 创建script元素
    const script = document.createElement('script')
    // 指定script的src属性
    script.src= 'http://127.0.0.1:8081/teachers'
    // 将script元素添加到body中触发脚本加载
    document.body.appendChild(script)
    // script标签加载完毕后移除该标签
    script.onload = ()=>{
      script.remove()
    }
  }
</script>
jQuery 封装的 jsonp
$.getJSON('http://127.0.0.1:8081/teachers?callback=?',(data)=>{
  console.log(data)
})

4、配置代理解决跨域#

4.1 自己配置代理服务器#

借助http-proxy-middleware配置代理
const { createProxyMiddleware } = require('http-proxy-middleware');

app.use('/api',createProxyMiddleware({
  target:'https://www.toutiao.com',
  changeOrigin:true,
  pathRewrite:{
    '^/api':''
  }
}))

4.2#

基于nginx搭建代理服务器,基于Vue等脚手架搭建代理服务器(本质上是对4.1的封装)

posted @   Sherioc  阅读(37)  评论(0编辑  收藏  举报
点击右上角即可分享
微信分享提示
主题色彩