跨域问题

跨域问题

跨域问题

跨域概述?

什么是跨域?

在浏览器上当前访问的网站向另一个网站发送请求获取数据的过程就是跨域请求。浏览器有同源策略,只有当 " 协议 "、" 域名 "、" 端口号 " 都相同时,才能称之为是同源,其中有一个不同,即是跨域

同源策略

同源策略的限制,同源策略限制了从同一个源加载的文档或脚本如何与来自另一个源的资源进行交互。这是一个用于隔离潜在恶意文件的重要安全机制。同源策略是一个浏览器的安全机制。

同源策略又分为以下两种:

如果没有 DOM 同源策略,也就是说不同域的 iframe 之间可以相互访问,那么黑客可以这样进行攻击:

如果 XMLHttpRequest 同源策略,那么黑客可以进行 CSRF(跨站请求伪造) 攻击:

为什么需要跨域?

  1. 前端和服务器分开部署,接口请求需要跨域
  2. 我们可能会加载其它网站的页面作为 iframe 内嵌

跨域的方法

WebView 跨域设置

在 Android Webview 中,由于安全性的考虑,跨域访问是默认被禁止的。如果您需要在 Webview 中加载来自不同域的资源(如跨域的 CSS、JS、图片等),就需要针对跨域问题做出相应的处理。

// API >= 16
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.JELLY_BEAN)
{
    webView.getSettings().setAllowUniversalAccessFromFileURLs(true);
}
webSettings.setAllowFileAccessFromFileURLs(true);

Nginx 反向代理

使用 nginx 反向代理实现跨域,只需要修改 nginx 的配置即可解决跨域问题。
A 网站向 B 网站请求某个接口时,向 B 网站发送一个请求,nginx 根据配置文件接收这个请求,代替 A 网站向 B 网站来请求。
nginx 拿到这个资源后再返回给 A 网站,以此来解决了跨域问题。

WebSocket

Websocket 是 HTML 5 的一个持久化的协议,它实现了浏览器与服务器的全双工通信,同时也是跨域的一种解决方案。
Websocket 不受同源策略影响,只要服务器端支持,无需任何配置就支持跨域。

CORS

什么是 CORS?

CORS 是一个 W3C 标准,全称是 " 跨域资源共享 "(Cross-origin resource sharing)。它允许浏览器向跨源服务器,发出 XMLHttpRequest 请求,从而克服了 AJAX 只能同源使用的限制。它通过服务器增加一个特殊的 Response Header[Access-Control-Allow-Origin] 来告诉客户端跨域的限制,如果浏览器支持 CORS、并且判断 Origin 通过的话,就会允许 XMLHttpRequest 发起跨域请求。

CORS 需要浏览器和服务器同时支持。目前,所有浏览器都支持该功能,IE 浏览器不能低于 IE 10。
整个 CORS 通信过程,都是浏览器自动完成,不需要用户参与。对于开发者来说,CORS 通信与同源的 AJAX 通信没有差别,代码完全一样。浏览器一旦发现 AJAX 请求跨源,就会自动添加一些附加的头信息,有时还会多出一次附加的请求,但用户不会有感觉。
因此,实现 CORS 通信的关键是服务器。只要服务器实现了 CORS 接口,就可以跨源通信。
CORS 简单请求

浏览器发现这次跨源 AJAX 请求是简单请求,就自动在头信息之中,添加一个 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

CORS 非简单请求
跨域资源共享 CORS 详解 --阮一峰
服务器响应:CORS Response Header 示例:

Access-Control-Allow-Origin: http://www.xxx.com
Access-Control-Max-Age:86400
Access-Control-Allow-Methods:GET, POST, OPTIONS, PUT, DELETE
Access-Control-Allow-Headers: content-type
Access-Control-Allow-Credentials: true

含义解释:

CORS 讲解的很详细,值得看

JSONP

JSONP 是一种跨域解决方案,它允许您在不同域名的 Web 应用程序之间进行数据传输。
是 js 是可以跨域下载的,所以只要把要请求的东西,封装在一个 js 文件中,就可以很容易的读取和解析了。

其他

在 Android API 21 之前,是可以跨域读取 Cookie 的,比如 A 域写入一个 userId 的 cookie,B 域可以读取该值。LOLLIPOP(21)及以上,默认不允许跨域访问 cookie 信息

if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
      cookieManager.setAcceptThirdPartyCookies(mWvCustom, true);//TODO 跨域cookie读取
}

WebView 跨域漏洞

2017 年底,腾讯爆了一个 支付宝被克隆漏洞,仔细看原理的话,就是利用了 Android WebView 跨域的漏洞。

满足这两个条件时,攻击者可以通过 URL Scheme 的方式,可远程打开并加载恶意 HTML 文件,远程获取 APP 中包括用户登录凭证在内的所有本地敏感数据。

跨域示例

Messenger chat plugin 只提供了 js sdk,要在 App 的 webview 集成,就有了跨域问题,需要 facebook 配置白名单
arop8
配置好了之后,跨域请求的时候带上 Origin,facebook 就会根据白名单,返回 CORS 跨域请求的相关头允许跨域请求

Vue 跨域 - 代理服务器配置

跨域问题

问题: 跨域访问,会出现违反 CORS policy 问题
z5s3r问题分析:
Client 已经将跨域请求送给了 server,server 也收到了请求,也做出了响应,但响应到了浏览器,违反了 CORS policy,被浏览器拦截了

解决:

示例: 存在跨域问题的代码

<template>
	<div id="root">
		<button @click="getStudents">获取学生信息</button>
		<button @click="getCars">获取汽车信息</button>
	</div>
</template>
<script>
import axios from 'axios'
export default {
	name: 'App',
	methods: {
		getStudents() {
			axios.get('http://localhost:5000/students').then(
				response => {
					console.log('成功了', response.data)
				},
				error => {
					console.log('失败了', error.message)
				}
			)
		},
		getCars() {
			axios.get('http://localhost:5001/cars').then(
				response => {
					console.log('成功了', response.data)
				},
				error => {
					console.log('失败了', error.message)
				}
			)
		}
	}
}
</script>
<style></style>

解决 1:单个 devServer proxy

解决:

const { defineConfig } = require('@vue/cli-service')
module.exports = defineConfig({
  transpileDependencies: true,
  lintOnSave: false, /*关闭语法检查*/
  devServer: {
    proxy: "http://localhost:5000"
  }
})
<template>
	<div id="root">
		<button @click="getStudents">获取学生信息</button>
		<button @click="getCars">获取汽车信息</button>
	</div>
</template>
<script>
import axios from 'axios'
export default {
	name: 'App',
	methods: {
		getStudents() {
			axios.get('http://localhost:8080/students').then(
				response => {
					console.log('成功了', response.data)
				},
				error => {
					console.log('失败了', error.message)
				}
			)
		},
		getCars() {
			axios.get('http://localhost:8080/cars').then(
				response => {
					console.log('成功了', response.data)
				},
				error => {
					console.log('失败了', error.message)
				}
			)
		}
	}
}
</script>
<style></style>

GetStudents () 不会存在跨域问题了,getCars () 还存在,因为只配置了一个代理服务器

缺点:

解决 2:多个 devServer proxy

可以配置多个 proxy serer

const { defineConfig } = require('@vue/cli-service')
module.exports = defineConfig({
  transpileDependencies: true,
  lintOnSave: false, /*关闭语法检查*/
  // // 开启代理服务器(方式一)
  // devServer: {
  //   proxy: "http://localhost:5000"
  // },
  // 开启代理服务器(方式二)
  devServer: {
    proxy: {
      '/api': {
        target: 'http://localhost:5000',
        pathRewrite: { '^/api': '' }, // 重写路径,去掉路径中开头的/api,保证交给后台服务器的是正常请求路径(必须配置)
        ws: true, // 用于支持websocket
        changeOrigin: true  // 用于控制请求头中的host值,保证请求的是配置的target,而不是当前的服务器地址
      },
      '/demo': {
        target: 'http://localhost:5001',
        pathRewrite: { '^/demo': '' }, // 重写路径,去掉路径中开头的/api,保证交给后台服务器的是正常请求路径(必须配置)
        ws: true, // 用于支持websocket
        changeOrigin: true  // 用于控制请求头中的host值,保证请求的是配置的target,而不是当前的服务器地址
      }
    }
  }
})
<template>
	<div id="root">
		<button @click="getStudents">获取学生信息</button>
		<button @click="getCars">获取汽车信息</button>
	</div>
</template>
<script>
import axios from 'axios'
export default {
	name: 'App',
	methods: {
		getStudents() {
			axios.get('http://localhost:8080/api/students').then(
				response => {
					console.log('成功了', response.data)
				},
				error => {
					console.log('失败了', error.message)
				}
			)
		},
		getCars() {
			axios.get('http://localhost:8080/demo/cars').then(
				response => {
					console.log('成功了', response.data)
				},
				error => {
					console.log('失败了', error.message)
				}
			)
		}
	}
}
</script>
<style></style>

解决 3:devServer axios 跨域问题

const { defineConfig } = require('@vue/cli-service')
module.exports = defineConfig({
  devServer: {
    host: 'localhost', // 本地域名/ip地址
    port: '8080', // 端口号
    
    proxy: { // 配置跨域
      '/tag_api/v1/query_category_briefs': {
        target: 'https://api.juejin.cn/', // 需要代理的地址
        secure: false, // 如果是不是https接口,可以不配置这个参数
        changeOrigin: true, // 允许跨域
        pathRewrite: {
          '^/apis': '', // 路径重写,将前缀/apis转为"/",也可以理解为"/apis"代替target里面的地址
          // 如果本身的接口地址就有"/api"这种通用前缀,也就是说https://www.exaple.com/api,就可以把pathRewrite删掉,如果没有则加上
        },
      },
    },
  },
})

axios({
      method: "GET",
      url: "apis/tag_api/v1/query_category_briefs",// 使用代理后的 apis
    })
    .then((response) => {
      console.log("请求成功", response);
    })
    .catch((err) => {
      console.log("请求失败", err);
    });