跨域
1. 什么是跨域
不同源之间的数据访问称之为跨域
当跨域访问数据时, 如果没有特地做过处理, 则无法获取不同源的数据(通过标签引用 js, img, css 是可以的, 因为虽然我们通过标签获取到了这些资源, 但是我们仍然不知道对应文件中的内容是什么)
1.1 什么是源
所谓的源是指 协议+域名+端口号
如果两个 url 之间的 协议, 域名, 端口号 中有任意一个不同, 则称这两个 url 不同源(即使是两个不同的域名指向同一个 IP 地址也一样是不同源的)
1.2 为什么会有同源策略
1995年, 同源策略由 Netscape 公司引入浏览器. 最初, 它的含义是指: A网页设置的 Cookie, B网页不能打开, 除非这两个网页同源
如果没有同源策略的话, 那么在你访问B网站的同时, B网站也可以获取A网站的 Cookie
- 而如果此时的
Cookie包含了好友信息, 账户余额等隐私, 就容易导致隐私的泄露. - 而且
Cookie经常用来保存用户的登录状态, 如果B网站在用户登录A网站的同时获取了Cookie, 那么B网站也能够登录A网站从而进行更多的操作
由此可见, 同源策略的存在是必要的. 否则 Cookie 能够共享, 那么上网将毫无安全性可言
1.3 同源策略的具体限制
随着时代的发展, 同源策略的限制也越来越严格
Cookie,LocalStorage和IndexDB无法获取DOM无法获得AJAX请求的数据无法获取
虽然同源策略是必要的, 但很多时候还是会带来很多不便. 尤其是现在 web 服务器和资源服务器基本都是不同源的情况下. 所以出现了一些即使不同源, 也能获取数据的方法
2. 如何跨域
2.1 使用代理服务器进行跨域
只有浏览器上应用了同源策略, 所以即使浏览器与服务器之间的不同源访问会有限制, 但是服务器与服务器之间的不同源访问就不会有限制.
故而, 我们令代理服务器与浏览器访问的页面同源, 从而使得浏览器可以访问代理服务器, 而代理服务器又能够访问目的服务器, 最终使得浏览器能够获取目的服务器的资源. 大致流程如下图所示:

这是相应的代理服务器的 node.js 代码
// server.js 代码
const express = require('express')
const request = require('request')
const app = express()
app.listen(2333, () => {
console.log('listening 2333 port')
})
app.get('/resource', (req, res) => {
// 当收到来自浏览器的 /resourse 请求时, 将会请求指定url的资源, 并将其返还给浏览器
let url = `https://www.bilibili.com`
req.pipe(request(url)).pipe(res)
})
/*
- src
- index.html
- main.js
- style.css
- server.js
*/
app.use(express.static('./src/')) // 托管静态文件
// http://localhost:2333/index.html 相当于直接访问 index.html 页面
2.2 JSONP
回顾一下, 我们是可以通过 script 标签获取到对应的文件的, 主要的问题是我们无法通过这个方法获取到数据. 但如果我们获取的这个 js 文件可以返回数据呢
-
在客户端设置: 可以在
script标签中的src属性上添加一定的参数(比如说callback=fn, 在这之前, 需要存在一个全局函数fn) -
在服务端设置: 服务端可以根据相应的路径和参数, 获取对应的数据, 并返回
fn(returnData)形式的数据. 而返回给浏览器的数据会被当做js代码执行, 从而达到跨域的目的(当然也可以返回其他形式的数据, 只要前后端协调好就行了) -
前端页面代码
<script>
const getData = (data) => {
// ... 处理数据
}
</script>
<script src="xxx/resource?callback=getData" />
- 后端处理代码
app.get('/resource', (req, res) => {
let { callback } = req.query
fs.readFile('./resource.js', (err, data) => { // 这里请求的文件的类型可以是 js, 也可以是 css, img 等等, 并不局限于某一类
res.send(`${callback}(${data.toString()})`)
})
})
优点:
- 支持 IE
- 可以跨域
缺点:
- 只有
GET方式, 并不支持POST
2.3 CORS
当浏览器向不同源发送数据请求时, 浏览器会自动给请求头添加 Origin 字段, 其值为当前源. 如果返回的相应头中没有包含 Access-Control-Allow-Origin 字段, 或者该字段的值并不包含当前源, 则浏览器会阻止前端获取该数据
通过在后端设置响应头的一些属性, 从而使得跨域能够成功, 如在响应头设置
Access-Control-Allow-Origin: *: 该字段是必须的.*表示允许来自任何源的资源请求. 同理, 只要相应的值设置为所允许访问的源, 则可实现跨域资源共享Access-Control-Allow-Credentials: true: 该字段可选, 表示是否允许发送cookie给服务器, 如果不允许直接不添加该字段即可Access-Control-Expose-Headers: 该字段可选. CORS请求时,xhr对象的getResponseHeader方法只能拿到6个基本字段:Cache-Control、Content-Language、Content-Type、Expires、Last-Modified、Pragma。如果想拿到其他字段,就必须在Access-Control-Expose-Headers里面指定 对应的属性即为浏览器额外能够获取的字段
代码示例:
- 前端
js请求代码
// 前端设置了一个 div 块级元素, 当点击该元素, 会向后端发送请求
const div = document.querySelector('.container')
div.addEventListener('click', (e) => {
const service = axios.create({
baseURL: 'http://127.0.0.1:2333',
timeout: 12000,
headers: {
'Content-Type': 'application/json'
},
withCredentials: true // 发送的请求中携带 cookie
})
service.get('/cookie_test').then(result => {
console.log(result)
}).catch(e => {
console.log(e)
})
})
- 后端
nodejs相应代码
const express = require('express')
const request = require('request')
const fs = require('fs')
const app = express()
app.listen(2333, () => {
console.log('listening 2333 port')
})
app.get('/resource', (req, res) => { // 通过 JSONP 获取数据
let {callback} = req.query // 获取参数
fs.readFile('./data.json', (err, data) => {
res.setHeader('Set-Cookie', ['name=saber; HttpOnly', 'gender=female; HttpOnly', 'age=20; HttpOnly']) // 为浏览器设置 cookie 缓存(不允许前端访问)
res.send(`${callback}(${data})`)
})
})
app.get('/cookie_test', (req, res) => { // 前端的 AJAX 请求
res.setHeader('Access-Control-Allow-Origin', 'http://127.0.0.1:5500') // 设置允许相应源的访问
res.setHeader('Access-Control-Allow-Credentials', 'true') // 允许请求头中携带 cookie
console.log(req.headers['cookie'])
res.send('response')
})
access-control-allow-methods: GET, POST, OPTIONS: 表示请求方法只允许GET,POST和OPTIONS三种
在 CORS 跨域请求中, 浏览器发送请求时会在请求头中增加一个 origin 字段, 对应的值记录的是本次请求所来自的源
2.4 Nginx 反向代理
给出一个专门进行反向代理的服务器的 BASE_URL
- 当用户需要获取前端页面时, 直接通过 BASE_URL/path/querystring 访问
- 当前端页面需要与后端进行交互时, 可以通过 BASE_URL/specialTag/path/querystring 获取

Nginx 反向代理配置(/etc/nginx/nginx.conf)
worker_processes 2; // 指定工作线程的数量
http {
server {
listen 8081; // nginx 的监听端口号
server_name localhost; // 服务器的域名或IP地址
location / {
// 如果访问的 URL 以 localhost/ 开头, 则访问该 location 块下的 proxy_pass 所对应的地址
proxy_pass http://localhost:8080;
}
location /api {
// 如果访问的 URL 以 localhost/api 开头, 则会访问该 location 块下的 proxy_pass 所指定的地址
proxy_pass http://localhost:8000;
// 将客户端请求中的 Host 头信息原封不动地传递给后端服务器
proxy_set_header Host $host;
}
}
}
关于 Nginx 的一些基本指令:
nginx: 启动 Nginxnginx -s reload: 重启 Nginxnginx -s stop: 停止 Nginxnginx -t: 检查 Nginx 的配置文件是否正确

浙公网安备 33010602011771号