跨域以及相关问题

发布于:2024-04-25 ⋅ 阅读:(12) ⋅ 点赞:(0)

1. 同源策略以及跨域的产生

  1. 同源策略Same-Origin Policy是一种浏览器安全机制,用于限制一个源origin加载的文档或脚本如何与来自不同源的资源进行交互。
  2. 两个网页只有当它们的协议、主机名和端口号完全一致时,才被视为“同源”。
  3. 在一个网页中的脚本只能读取、写入和操作来自同一源的窗口和文档属性,而不能对不同源的资源执行这些操作,这就是跨域问题的产生。

2. 解决跨域的方法

  1. JSONP (JSON with Padding)JSONP是一种非官方跨域数据交互协议,利用<script>标签不受同源策略限制的特点,通过动态插入脚本标签从服务器获取数据。服务器返回的数据通常是一个JavaScript函数调用,参数即为要传递的数据。
  2. CORS (Cross-Origin Resource Sharing)CORS是一种现代的、W3C标准推荐的跨域通信机制,允许服务器明确指示哪些其他源的网站可以请求哪些资源。服务器通过在响应头中设置Access-Control-Allow-Origin来允许特定源或者所有源的请求。
    • 对于简单请求,如GET、POST等,且不包含自定义头部或特殊请求头,浏览器会自动添加预检请求OPTIONS或者直接附加必要的CORS头部发送请求。
    • 对于复杂请求,如带自定义头部或某些特殊的HTTP方法,浏览器首先发送一个预检OPTIONS请求以询问服务器是否允许实际的请求。
  3. iframe + postMessage API: 使用HTML5postMessage API可以在嵌入的iframe之间实现跨域通信,通过消息事件的形式发送和接收跨域数据。
  4. 代理服务器: 在后端设置代理服务器,前端所有的请求都通过代理转发到目标服务器,这样对于前端来说,实际上仍然是同源请求。
  5. WebSocket协议: WebSocket协议支持跨域连接,但同样要求服务端开启相应的CORS设置以允许客户端发起跨域WebSocket连接。

3. JSONP(JSON with Padding)解决跨域

  1. 该方式利用了浏览器允许跨域加载脚本资源的特性。它通过动态创建<script>标签并设置其src属性指向需要请求的远程URL来实现。
  2. JSONP利用了浏览器可以跨域加载脚本资源的“漏洞”,绕过了同源策略对Ajax请求的限制,从而实现了跨域数据交互。但是JSONP只能支持GET请求,而且安全性较低,因为它依赖于不受同源策略保护的脚本注入机制。随着现代浏览器对CORS(Cross-Origin Resource Sharing)的支持越来越广泛,JSONP已逐渐被更安全和灵活的CORS方案所取代。

具体步骤

  1. 客户端请求: 假设客户端网页想要从不同源的服务器获取数据,它客户端不是直接发起AJAX请求,而是动态创建一个<script>标签,并将其src属性指向目标服务器的URL,并且在URL中附带上回调函数名作为查询参数。
  2. 服务端响应: 服务端收到带有回调函数名的GET请求后,将要返回的数据包装在一个JavaScript函数调用中,这个函数名就是客户端提供的回调函数名,当浏览器接收到这个响应时,会当作脚本执行,调用这个函数。
  3. 客户端在页面中预先定义好了名为handleResponse的全局函数。当服务器返回的数据被浏览器解析为JavaScript并执行时,handleResponse函数会被调用,并且传递的数据作为参数传入。客户端就可以在这个回调函数内部处理来自不同源的数据了。
// 这是网页在的地址 http://example1.com/index.html
// 需要请求数据的服务器的地址 http://example2.com/getdata.php
// 1. 客户端发送请求并定义好要后端需要返回的函数参数
<script src="http://example2.com/getdata.php?callback=handleResponse"></script>
function handleResponse(data) {
  // data 就是要接收的数据 
  console.log(data) 
}
// 2. 服务器返回咱们定义的函数名的调用,并把数据当参数传入
handleResponse({name: '小明', age: 18})
// 3. 解析时,浏览器会执行返回的东西,自认就调用了我们预先设定的方法,并且我们可以从参数中得到后端返回的数据

4. CORS(Cross-Origin Resource Sharing)跨源资源共享

  1. 了解CORS原理得先知道简单请求与非简单请求。

    • 非简单请求:当发起跨域请求的方法不是GETHEADPOST,或者POST请求包含非简单头部(如自定义请求头)或请求体内容类型Content-Type不为application/x-www-form-urlencodedmultipart/form-datatext/plain
    • 简单请求:不是非简单请求就为简单请求。
  2. 预检机制,当发起跨域的请求为非简单请求时,浏览器会先发送一个OPTIONS预检请求到目标服务器,询问是否允许实际的跨域请求。预检请求中包含了Origin头部以及其他相关CORS头部。

  3. 响应预检请求: 服务器收到预检请求后,会在响应头中添加相应的CORS相关头部,以决定是否允许这次跨域请求。关键的响应头包括:

    • Access-Control-Allow-Origin:指定允许跨域访问的源,可以是具体的源地址或表示任何源。
    • Access-Control-Allow-Methods:列出允许的HTTP方法列表,比如GET, POST, PUT, DELETE等。
    • Access-Control-Allow-Headers:如果客户端在请求中使用了自定义头部,则这里需要列出这些头部名称,否则浏览器会拒绝请求。
    • Access-Control-Max-Age:设置预检请求结果的有效期,以便缓存和重用。
  4. 实际请求与响应: 如果预检请求得到服务器许可,浏览器才会发出实际的请求,并且在响应中,服务器依然需要携带合适的CORS响应头,特别是Access-Control-Allow-Origin

    • 对于简单请求,浏览器通常不会发送预检请求,而是会自动在请求头中添加Origin字段,服务器收到请求后,如果在响应头中设置了Access-Control-Allow-Origin并且其值允许当前请求的源,那么浏览器就会认为此次跨域请求是合法的,允许 JavaScript 访问响应数据,不需要设置更多的响应头参数。
  5. Cookie和认证信息: 跨域请求默认情况下不会携带CookieHTTP认证信息。若要使它们随请求一起发送,服务器必须在响应头中明确声明: Access-Control-Allow-Credentials: true:表明服务器允许客户端发送包含凭据(如Cookies)的请求。客户端也需要在AJAX请求中设置credentials: 'include'来传递凭据。

  6. 成功跨域: 只有当服务器响应中的CORS相关头部符合要求时,浏览器才会将响应的数据提供给JavaScript代码进行进一步处理。否则,浏览器会阻止这种跨域行为,并可能抛出安全相关的错误。

5. 通过前端代理服务器解决跨域问题

  1. 通过代理服务器解决跨域问题主要应用于前端开发环境,为了方便本地调试与后端API服务器之间的跨域请求,可以通过配置代理服务器来透明地转发请求,使得前端应用看起来像是直接与同源的代理服务器进行通信,而非直接与真正的跨域后端API通信。
 // vue.config.js , 使用 vite 可以到 vite官网 上查看
   module.exports = {
     devServer: {
       proxy: {
          // 这里的 '/api' 是前置路径,所有发往 '/api' 的请求都会被代理
         '/api': {
           // 目标后端API服务器的地址
           target: 'http://target-server.com', 
           // 是否需要改变原始请求头中的host信息
           changeOrigin: true, 
           pathRewrite: {
             // 重写路径,去掉'/api'前缀,保持请求路径与后端API的一致性
             '^/api': '' 
           },
           // 是否验证HTTPS证书(如果是https则需要设置为false)
           secure: false, 
           // 可以添加额外的请求头
           headers: {}
         }
       }
     }
   }
  1. 在前端代码中,我们只需像平常那样向代理路径发起请求,而不是直接向跨域的后端API发起请求。
// 这个请求将被代理到 'http://target-server.com/users'
   axios.get('/api/users') 
  1. 当前端应用在开发环境下启动时,内置的开发服务器会监听一个特定端口。当它检测到请求中有匹配到代理规则的URL时,会拦截这个请求,然后将请求的内容转发到目标服务器,并等待目标服务器的响应。当目标服务器响应后,代理服务器再将响应内容原封不动地转发回前端应用,此时浏览器看到的是从同源的代理服务器接收到的数据,从而避免了跨域问题。
  2. 通过这种方式,可以在开发阶段专注于业务逻辑,无需考虑跨域带来的影响。在生产环境中,通常服务器配置会支持CORS或者其他跨域解决方案,确保前后端间能够正常通信。
  3. 正向代理与反向代理
    • 前端正向代理是代理客户端(如浏览器)去访问目标服务器的过程。使用正向代理服务器时,所有的网络请求都通过这个代理服务器转发到目标服务器。前端正向代理通常用来突破网络限制(如公司内网对外网的访问限制)、隐藏客户端的真实IP地址,或者在开发过程中解决跨域问题。
    • 后端反向代理是指代理服务器代表后端服务接收客户端请求,并将请求转发到内部网络的实际服务器的过程。一般是由运维人员在服务器端配置的,比如使用NginxApache等作为反向代理服务器。反向代理主要用于负载均衡、提高服务器性能、隐藏真实服务器地址、简化架构、集中处理SSL/TLS加密握手等问题,也可以用来解决前端跨域问题。

6. iframe + postMessage API 解决跨

  1. 这种方法允许在不同源的上下文之间安全地传递数据,一般是父与子窗口之间传输,那么什么是父与子窗口呢。

    • 父窗口:通常是指最初打开或载入的窗口,或者是承载其它窗口的容器窗口。在一个网页中通过JavaScript或者其他方式如window.open()打开了一个新的窗口或iframe标签加载了新的页面时,原有的那个窗口就被称作父窗口。
    • 子窗口:由父窗口创建或引入的新窗口,它相对于父窗口而言具有一定的依赖关系。如果是通过window.open()方法打开的新窗口,那么新窗口就是子窗口,原来的窗口是父窗口。若是通过<iframe>标签嵌入在父页面中的另一个网页,则该iframe中的页面也可以视作子窗口。
    • 父窗口与子窗口之间可以进行通信,例如通过window.postMessage()方法进行跨窗口的消息传递。在关闭或刷新子窗口时,有时会对父窗口的状态或行为产生影响,子窗口通常受父窗口的生命周期管理,例如当父窗口关闭时,某些情况下子窗口也会随之关闭。
  2. 父窗口首先在子iframe加载完成后,通过window.postMessage()方法向子窗口发送一条消息。

// 父窗口
window.onload = function () {
  var targetOrigin = 'http://example.com' // 子iframe的源地址
  window.frames[0].postMessage('Hello from parent!', targetOrigin)
}
  1. 子窗口通过监听message事件来接收消息,并对消息作出响应。
// 子窗口
window.addEventListener('message', function (event) {
  // 验证发送消息的源是否可信
  if (event.origin === 'http://parent-origin.com') { 
      console.log('Received message:', event.data)
      // 可以在这里做出相应的处理
  }
})
  1. 子窗口主动向父窗口发送消息。
// 子窗口
function sendMessageToParent() {
  var parentOrigin = 'http://parent-origin.com' // 父窗口的源地址
  parent.postMessage('Hello from iframe!', parentOrigin)
}

// 调用该函数发送消息
sendMessageToParent()
  1. 父窗监听message事件来接收来自子窗口的消息。
// 父窗口
 window.addEventListener('message', function (event) {
   // 验证发送消息的源是否可信
   if (event.origin === 'http://example.com') {      
     console.log('Received message:', event.data)
     // 可以在这里做出相应的处理
   }
})

7. 使用 WebSocket 协议

  1. WebSocket协议支持跨域连接,但同样要求服务端开启相应的CORS设置以允许客户端发起跨域WebSocket连接。
  2. 服务端
  // 例子
 const WebSocket = require('ws')
   wss.on('headers', (headers) => {
     headers['Access-Control-Allow-Origin'] = '*' // 允许任何源
     // 或者
     headers['Access-Control-Allow-Origin'] = 'http://example.com' // 允许特定源
   });
  1. 客户端代码中创建WebSocket连接时,浏览器会自动添加Origin头部,标识本次请求的源。服务端会检查这个Origin头部,并基于其设置的CORS策略来决定是否允许连接。
 const socket = new WebSocket('wss://example.com/ws')

   socket.onopen = function(event) {
     // 连接成功,可以开始发送和接收数据
   }

   socket.onerror = function(error) {
     // 处理错误,包括可能的跨域错误
   }
  1. 携带凭证Cookies的跨域,服务端还需要设置Access-Control-Allow-Credentials响应头为true,同时客户端在建立WebSocket连接时也需要设置credentials选项为true
// 服务端
 headers['Access-Control-Allow-Credentials'] = true
 // 客户端
 const socket = new WebSocket('wss://example.com/ws', { credentials: 'include' }

8. 总结

一般情况下我们在开发环境使用前端代理来解决跨域问题,在生产环境下使用CORS跨源资源共享来解决跨域问题。