​ 浏览器同源策略: 跨域及解决办法

目录

跨域产生原因:

同源的定义:

解决办法

方法一:后端设置响应头,告诉浏览器不进行拦截(主流方法,适用于开发,生产环境)

1.1后端nodeJs 设置中间件统一为所有请求添加响应头

2.2 反向代理: 使用express + http-proxy-middleware 增加转发功能

 2.3 设置反向代理服务器 使用http-proxy-middleware 实现反向代理服务器,也可直接ngnix设置代理转发

方法二:配置viter/webpack (仅开发环境有效)

方法3:JSONP

补充:跨域时,简单请求,和非简单请求的区别

简单请求的定义:

浏览器不同的处理方式

参考资料


跨域产生原因:

        浏览器基于浏览器的同源策略 - Web 安全 | MDN在 一个的文档或者它加载的脚本在与另一个源(非同源)的资源进行交互 的时候经行了限制。

同源的定义:

        如果两个 URL 的协议端口(如果有指定的话)和主机都相同的话,则这两个 URL 是同源

下表给出了与 URL http://example.com/v1/page.html 的源进行对比的示例:

URL结果原因
示例1

http://example.com/v1/404.html

http://example.com/v2/login.html

同源只有路径不同
示例2https://example.com/v1/page.html失败协议不同
示例3http://example.com:81/v1/page.html失败端口不同(http:// 默认端口是 80)
示例4http://sub.example.com/v1/page.html失败主机不同

tips: 二级域名不同仍旧会跨域

在示例4中,虽然官方文档(Same-origin policy - Security on the web | MDN)是说主机(host),但是指完整的:域名/主机(Domain/Host)+ 子域名(Subdomain),而不仅仅指狭义的主域名。

故:协议,端口,主域名,相同,但二级(3...4...)域名不相同仍会触发浏览器的同源策略

【已弃用】:通过主域名与子域名设置相同的 document.domain ,可以绕过同源策略浏览器的同源策略 - 源的更改 | MDN

// http://example.com/v1/page.html 页面和
// http://sub.example.com/v2/login.html 页面设置相同的 document.domain

document.domain = "example.com"; // 此处设置为其当前域或其当前域的父域 example.com ,要是其他例如 test.com 也不能绕过同源策略
​
// 警告: 这里描述的方法(使用 document.domain setter)已被弃用,因为它破坏了同源策略所提供的安全保护,并使浏览器中的源模型复杂化,导致互操作性问题和安全漏洞。

tips:cookie 是有域限制的,但二级域访问访问顶级域是可以的,而二级域名之间的访问是存在跨域的。

解决办法

CORS:可以使用 CORS 来允许跨源访问。CORS 是 HTTP 的一部分,它允许服务端来指定哪些主机可以从这个服务端加载资源。(Cross-Origin Resource Sharing (CORS) - HTTP | MDN

方法一:后端设置响应头,告诉浏览器不进行拦截(主流方法,适用于开发,生产环境)

Headervalue是否必填说明
Access-Control-Allow-Originip/*必填*表示允许所有源访问;或者设置具体的ip,仅该ip可访问
Access-Control-Allow-Headers'Content-Type, client, devicesid, registersign'允许的自定义请求头
Access-Control-Allow-Methods'GET, POST, OPTIONS'允许的请求方式
Access-Control-Allow-Credentials'true'唯一值 true(如果不是true,删掉该响应头):允许带Cookie;如果用了allowCredentials(true),allowedOrigins不能为*(必须明确指定域名)。

1.1后端nodeJs 设置中间件统一为所有请求添加响应头

npm install express

const express = require('express');
const app = express();

app.use((req, res, next) => {
  res.header('Access-Control-Allow-Origin', '*');
  res.header('Access-Control-Allow-Headers', 'Origin, X-Requested-With, Content-Type, Accept');
  next();
});

app.get('/data', (req, res) => {
  res.json({ message: 'This is data from example.com' });
});

app.listen(3000, () => {
  console.log('Server running on port 3000');
});

2.2 反向代理: 使用express + http-proxy-middleware 增加转发功能

npm install express http-proxy-middleware

const express = require('express');
const { createProxyMiddleware } = require('http-proxy-middleware');

// 创建Express应用
const app = express();

// 自定义CORS处理逻辑
app.use((req, res, next) => {
  // 设置允许的来源
  res.setHeader('Access-Control-Allow-Origin', 'http://10.1.15.6:3000');
  // 设置允许的请求头
  res.setHeader('Access-Control-Allow-Headers', 'Content-Type, client, devicesid, registersign');
  // 设置允许的请求方法
  res.setHeader('Access-Control-Allow-Methods', 'GET, POST, OPTIONS');

  if (req.method === 'OPTIONS') {
    // 处理预检请求
    res.sendStatus(200);
  } else {
    // 继续处理实际请求
    next();
  }
});

// 创建代理中间件
// 使用代理中间件
app.use('/jk_flv', createProxyMiddleware({
  target: 'https://sample-videos.com',
  // target: 'https://sample-videos.com/video321/flv/720/big_buck_bunny_720p_1mb.flv',
  changeOrigin: true,
}));

//'https://apis.map.qq.com/ws/geocoder/v1/?key=111&address=广东省深圳市' //真实地址
// http://localhost:8080/ditu/ws/geocoder/v1/?key=111&address=广东省深圳市 // 请求地址
app.use('/ditu', createProxyMiddleware({
  target: 'https://apis.map.qq.com',
  changeOrigin: true, // true 时 真实路径将失去 一个 /ditu false 不知道什么意思😂猜测是服务器返回给浏览器的
  // pathRewrite: {  // 此处写不写都是一样的
  //   '^/ditu': ''
  // }
  onProxyReq(proxyReq, req, res) {
    // 打印转发请求的实际地址
    // 这里不打印
    console.log(`Request forwarded to:${proxyReq.protocol}//${proxyReq.host}${proxyReq.path}`);
  }
}));

// 启动开发服务器
const port = 8080;
app.listen(port, () => {
  console.log(`开发服务器已启动,监听端口 ${port}`)
});

 2.3 设置反向代理服务器 使用http-proxy-middleware 实现反向代理服务器,也可直接ngnix设置代理转发

npm install http-proxy

创建一个名为 proxy-server.js 的文件,并添加以下代码:

const http = require('http');
const httpProxy = require('http-proxy');

// 创建一个代理服务器实例
const proxy = httpProxy.createProxyServer({});

// 创建一个 HTTP 服务器
const server = http.createServer((req, res) => {
    // 打印每次请求的真实地址
    console.log(`Received request for: ${req.url}`);

    // 目标服务器地址,这里假设目标服务器运行在本地的 3000 端口
    const target = 'http://localhost:3000';

    // 将请求代理到目标服务器
    proxy.web(req, res, { target });
});

// 监听错误事件
proxy.on('error', (err, req, res) => {
    console.error('Proxy error:', err);
    res.writeHead(500, { 'Content-Type': 'text/plain' });
    res.end('Proxy error');
});

// 启动服务器,监听 8080 端口
const port = 8080;
server.listen(port, () => {
    console.log(`Proxy server is running on port ${port}`);
});

 node proxy-server.js 启动服务

方法二:配置viter/webpack (仅开发环境有效)

vue3 vite 代理  @/vite.config.js  核心代码:server.proxy

import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import Components from 'unplugin-vue-components/vite';
import { VantResolver } from 'unplugin-vue-components/resolvers';

import path from 'path'
// import CONFIG from './src/utils/config.js'

// defineConfig 工具函数获取类型提示
export default defineConfig({
  plugins: [
    vue(),
    Components({
      resolvers: [VantResolver()],
    }),
  ],
  resolve: {
    alias: {
      "@": path.resolve(__dirname, "src"),
    },
  },
  // vite 相关配置
  server: {
    port: 3000,
    host: true,
    open: true,
    proxy: {
      // https://cn.vitejs.dev/config/#server-proxy
      '/wen': {
        target: "http://192.168.10.38:8099/api",
        changeOrigin: true,
        rewrite: (path) => path.replace(/^\/wen/, '')
      }, // 请求地址 http://localhost:3000/wen/login => http://192.168.10.38:8099/api/login
      '/map': {
        target: "https://apis.map.qq.com/",
        // target: "https://10.9.31.176/",
        changeOrigin: true,
        rewrite: (path) => path.replace(/^\/map/, '')
      },
    }
  },
  // 打包配置
  base: './',//引入路径相当于webpack中的 baseUrl 或 publicPath
  build: {
    // sourcemap: false,
    // outDir: 'dist', // 指定输出路径,要和库的包区分开
    // assetsDir: 'static/img/', // 指定生成静态资源的存放路径
    // rollupOptions: {
    //   output: {
    //     chunkFileNames: 'static/js/[name]-[hash].js',
    //     entryFileNames: 'static/js/[name]-[hash].js',
    //     assetFileNames: 'static/[ext]/[name]-[hash].[ext]'
    //   },
    // }
  }
})

vue2 webpack @/vue.config.js 核心代码:devServer.proxy

/**
 * 配置参考: https://cli.vuejs.org/zh/config/
 */
const MonacoEditorPlugin = require('monaco-editor-webpack-plugin')
const TerserPlugin = require('terser-webpack-plugin')

const path = require('path')
function resolve (dir) {
  return path.join(__dirname, dir)
}
module.exports = {
  publicPath: process.env.NODE_ENV === 'production' ? './' : '/',
  chainWebpack: config => {
    config.module
      .rule('svg')
      .exclude.add(resolve('src/icons'))
      .end()
    config.module
      .rule('icons')
      .test(/\.svg$/)
      .include.add(resolve('src/icons'))
      .end()
      .use('svg-sprite-loader')
      .loader('svg-sprite-loader')
      .options({
        symbolId: 'icon-[name]'
      })
      .end()
  },
  // 默认打开eslint效验,如果需要关闭,设置成false即可
  lintOnSave: false,
  productionSourceMap: false,
  devServer: {
    open: true,
    port: 8001,
    overlay: {
      errors: true,
      warnings: true
    },

    proxy: {
      '/dev': {
        // http://localhost:8001/dev/login => http://192.168.10.126:8050/asset-admin/login 
        target: 'http://192.168.10.126:8050/asset-admin/', // 跨域请求的地址
        changeOrigin: true, // 开启跨域
        logLevel: 'debug',
        // 路径重写
        pathRewrite: {
          '^/dev': ''
        }
      }
    }
  },
  // configureWebpack: { // webpack 配置
  //   plugins: [
  //     new MonacoEditorPlugin({
  //       // https://github.com/Microsoft/monaco-editor-webpack-plugin#options
  //       languages: ['javascript', 'typescript', 'sql', 'java']
  //     })
  //   ]
  // }
  configureWebpack: config => {
    if (process.env.NODE_ENV === 'production') {
      // 自定义TerserPlugin压缩配置
      return {
        optimization: {
          minimize: true, // 启用压缩
          minimizer: [
            new TerserPlugin({
              test: /\.js(\?.*)?$/i, // 匹配参与压缩的文件
              terserOptions: {
                compress: {
                  drop_console: true, // 移除console
                  drop_debugger: true, // 移除debugger
                  pure_funcs: ['console.log'] // 移除指定函数
                }
              }
            })
          ]
        }
      }
    }
  }
}

方法3:JSONP

本质是利用script标签不受同源策略限制,需要注意只能get请求,长度有限制,然后就是回调函数名前后端保持一致

补充:跨域时,简单请求,和非简单请求的区别

简单请求的定义:

两个条件:

  1. 请求方法是:HEAD,GET,POST
  2. header中只能包含以下请求字段:
    1. Accpet
    2. Accpet-Language
    3. Conten-Language
    4. Content-Type(所指的媒体类型值仅仅限于下列三者之一)
      1. text/plain
      2. multipart/form-data
      3. application/x-www-form-urlencoded
浏览器不同的处理方式

对于简单请求来说,如果请求跨域,那么浏览器会放行让请求发出。浏览器会发出cors请求,并携带origin。然后浏览器会拦截响应,并检查返回的response的header中有没有Access-Control-Allow-Origin,要是其的值为*或者为请求的Origin值,表示该资源是共享的,浏览器将放行。

对于非简单请求来说,浏览器先发送OPTIONS 预检请求(preflight request)(对于OPTIONS请求浏览器将按照简单请求的方式处理)并附加如下两个请求头

  • Access-Control-Request-Method:告诉服务器实际发送的HTTP请求方法。
  • Access-Control-Request-Header:告诉服务器实际请求所携带的自定义的头部信息。

目的:保护客户端的安全,防止不受信任的网站利用用户的浏览器向其他网站发送恶意请求。

由服务器决定,该请求是否被允许。

详细请参考文档 HTTP之跨域_access-control-allow-credentials-CSDN博客

参考资料

Same-origin policy - Security on the web | MDN

Cross-Origin Resource Sharing (CORS) - HTTP | MDN

HTTP之跨域_access-control-allow-credentials-CSDN博客

二级域名不同算跨域 - 腾讯云开发者社区 - 腾讯云

浏览器跨域 Access-Control-Allow-Headers 问题当浏览器发送接口请求出现跨域问题时,目前的做法 - 掘金

前后端解决跨域问题的6种方案分享_java_脚本之家

node后端与Vue前端跨域处理方法详解_node.js_脚本之家

前端那些事(一)跨域浏览器基于 同源策略 做出的限制。 同源策略 限制了从同一个源加载的文档或脚本如何与来自另一个源的资 - 掘金

📈一图详解URL的构成

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值