文章

跨域问题

跨域问题

跨域问题

跨域问题

跨域概述?

什么是跨域?

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

  • 跨域只存在于浏览器端,不存在于 Android / iOS / Node.ss /Python/Java 等其它环境,但存在有有 Webview 的 App
  • 跨域请求能发出去,服务端能收到请求并正常返回结果,只是结果被浏览器拦截了

同源策略

  • 跨域的原因?

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

  • 浏览器出于安全的考虑,使用 XMLHttpRequest 对象发起 HTTP 请求时必须遵守同源策略,否则就是跨域的 HTTP 请求,

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

  • DOM 同源策略:禁止对不同源页面 DOM 进行操作。这里主要场景是 iframe 跨域的情况,不同域名的 iframe 是限制互相访问的。

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

  • 做一个假网站,里面用 iframe 嵌套一个银行网站 http://mybank.com
  • 把 iframe 宽高啥的调整到页面全部,这样用户进来除了域名,别的部分和银行的网站没有任何差别。
  • 这时如果用户输入账号密码,我们的主网站可以跨域访问到 http://mybank.com 的 dom 节点,就可以拿到用户的账户密码了
  • XMLHttpRequest 同源策略:禁止使用 XMLHttpRequest 对象向不同源的服务器地址发起 HTTP 请求。

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

  • 用户登录了自己的银行页面 http://mybank.comhttp://mybank.com 向用户的 cookie 中添加用户标识。
  • 用户浏览了恶意页面 http://evil.com,执行了页面中的恶意 AJAX 请求代码。
  • http://evil.comhttp://mybank.com 发起 AJAX HTTP 请求,请求会默认把 http://mybank.com 对应 cookie 也同时发送过去。
  • 银行页面从发送的 cookie 中提取用户标识,验证用户无误,response 中返回请求数据。此时数据就泄露了。
  • 而且由于 Ajax 在后台执行,用户无法感知这一过程

为什么需要跨域?

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

跨域的方法

WebView 跨域设置

  • setAllowFileAccessFromFileURLs

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

1
2
3
4
5
6
// API >= 16
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.JELLY_BEAN)
{
    webView.getSettings().setAllowUniversalAccessFromFileURLs(true);
}
webSettings.setAllowFileAccessFromFileURLs(true);
  • setAllowUniversalAccessFromFileURLs

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 简单请求

  • 在请求头信息之中,增加一个 Origin 字段

浏览器发现这次跨源 AJAX 请求是简单请求,就自动在头信息之中,添加一个 Origin 字段

  • 如果 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 示例:

1
2
3
4
5
Access-Control-Allow-Origin: http://www.xxx.com
Access-Control-Max-Age86400
Access-Control-Allow-MethodsGET, POST, OPTIONS, PUT, DELETE
Access-Control-Allow-Headers: content-type
Access-Control-Allow-Credentials: true

含义解释:

  • Access-Control-Allow-Origin 允许http://www.xxx.com域(自行设置,这里只做示例)发起跨域请求
  • Access-Control-Max-Age 设置在 86400 秒不需要再发送预校验请求
  • Access-Control-Allow-Methods 设置允许跨域请求的方法
  • Access-Control-Allow-Headers 允许跨域请求包含 content-type
  • Access-Control-Allow-Credentials 设置允许 Cookie

  • 跨域资源共享 CORS 详解 –阮一峰

CORS 讲解的很详细,值得看

JSONP

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

  • jsonp 的核心则是动态添加 <script> 标签来调用服务器提供的 js 脚本。
  • 缺点就是只支持 GET 请求。

其他

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

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

WebView 跨域漏洞

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

  • WebView 中 setAllowFileAccessFromFileURLssetAllowUniversalAccessFromFileURLsAPI 配置为 true
  • WebView 可以直接被外部调用,并能够加载外部可控的 HTML 文件

满足这两个条件时,攻击者可以通过 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,被浏览器拦截了

解决:

  • 通过 src?绕过浏览器同源策略,只能用于 GET,POST 用不了
  • Vue 中配置代理服务器绕过,由代理服务器发送请求

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
<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

解决:

  • vue.config.js 中配置代理服务器
1
2
3
4
5
6
7
8
const { defineConfig } = require('@vue/cli-service')
module.exports = defineConfig({
  transpileDependencies: true,
  lintOnSave: false, /*关闭语法检查*/
  devServer: {
    proxy: "http://localhost:5000"
  }
})
  • 修改请求的 host 为代理服务器
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
<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 () 还存在,因为只配置了一个代理服务器

缺点:

  • DevServer proxy 中只能配置一台代理服务器
  • 如果 proxy 和本服务器存在相同的资源,优先本服务器资源响应
  • 不行的话注意可能需要重新启动 server

解决 2:多个 devServer proxy

可以配置多个 proxy serer

  • 配置 vue.config.js:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
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,而不是当前的服务器地址
      }
    }
  }
})
  • Client 代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
<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 跨域问题

  • Vue. Config. Js 配置
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
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删掉,如果没有则加上
        },
      },
    },
  },
})

  • 使用
1
2
3
4
5
6
7
8
9
10
axios({
      method: "GET",
      url: "apis/tag_api/v1/query_category_briefs",// 使用代理后的 apis
    })
    .then((response) => {
      console.log("请求成功", response);
    })
    .catch((err) => {
      console.log("请求失败", err);
    });
本文由作者按照 CC BY 4.0 进行授权