什么是跨域?
mdn: 同源策略是一个重要的安全策略, 它用于限制一个origin的文档或者他加载的脚本如何能与另一个源进行交互, 他能帮助阻隔恶意文档,减少可能被攻击的媒介。
上图可知:例如我们的项目如果跑在本地的话,浏览器去请求本地资源时注意端口即可,可以很顺利的拿到资源。而如果我们去请求云服务器的资源时不做任何跨域处理的情况下是无法获取到数据的。(同源策略要求端口,协议,ip地址一致)这样的操作可以一定程度上防止黑客的入侵。
- 事实上跨域的产生和前后端分离的发展有很大的关系: 早期服务端渲染是没有跨域的.但是随着前后端的分离目前前端开发代码和服务器开发的API接口往往是分离的,甚至部署在不同的服务器上的.
- 正是因为访问静态资源服务器和API接口服务器很有可能不是同一个服务器或者不是同一个端口,浏览器发现静态资源服务器和API接口(XHR,Fetch)请求不是来自同一个地方时(同源策略),就产生了跨域.
跨域案例
我们先使用koa搭建一个服务器 (用koa是因为才学完koa, 小伙伴在学习的时候可以根据自己习惯去写一个服务)
const Koa = require('koa')
const KoaRouter = require('@koa/router')
// const static = require('koa-static')
const app = new Koa()
// app.use(static('./client'))
const userRouter = new KoaRouter({ prefix: '/users' })
userRouter.get('/list', (ctx, next) => {
ctx.body = [
{ id: 111, name: "why", age: 18 },
{ id: 112, name: "kobe", age: 18 },
{ id: 113, name: "james", age: 25 },
{ id: 114, name: "curry", age: 30 },
]
})
app.use(userRouter.routes())
app.use(userRouter.allowedMethods())
app.listen(8000, () => {
console.log('koa服务器启动成功~')
})
服务搭建成功,我们来调取接口
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>网易云音乐</title>
</head>
<body>
<h1>网易云音乐项目</h1>
<script>
// 1.XHR网络请求
const xhr = new XMLHttpRequest()
xhr.onreadystatechange = function() {
if (xhr.readyState === XMLHttpRequest.DONE) {
console.log(JSON.parse(xhr.responseText))
}
}
xhr.open('get', 'http://localhost:9000/api/users/list')
xhr.send()
// 2.fetch网络请求
// fetch('http://localhost:9000/api/users/list').then(async res => {
// const result = await res.json()
// console.log(result)
// })
</script>
</body>
</html>
上面的代码如果我们使用 Live Server 创建一个服务来启动html文件, 这样会跨域的, 虽然主机名一样但是端口不一样,还是会触发同源策略.
koa中有一个koa-static是可以加载静态文件的(koa服务代码中注释的内容), 如果我们使用它启动,因为他们的源是一样的就不会触发同源策略.(通俗点来说就是把接口和html文件放到了一个服务上启动)
跨域解决方案
跨域的解决方案几乎都和服务器有关系,单独的前端解决跨域的方案基本不会使用,可以做一个了解. 我们使用最多的其实还是webpack中devserver配置的代理来解决跨域问题, 刚开始写项目的小伙伴可能并不知道webpack配置的本质也是在webpack-server的服务器配置了代理.
方案一: 静态资源和API服务器部署在同一个服务器中
这种方案和上述的跨域案例类似,不多做赘述
方案二: CORS(跨域资源共享)
跨域资源共享他是一个基于http header的机制,该机制通过允许服务器标识除了自己以外的其他源(域,协议和端口), 使得浏览器允许这些origin访问加载自己的资源.
- 简单请求
请求方法: HEAD,GET,POST
HTTP请求头不超过这些字段: Accept , Accept-Language , Content-Language , Last-Event- ID , Content-Type: 只限于三个值application/x-www-form-urlencoded, multipat/form-data,text/plain
app.use(async (ctx, next) => {
// 1.允许简单请求开启CORS
ctx.set("Access-Control-Allow-Origin", "*")
await next()
})
// 没有学过koa的也没有关系, 上述代码的意思就是服务端允许那些源可以访问资源, * 代表所有
- 非简单请求
不满足简单请求条件则为非简单请求
app.use(async (ctx, next) => {
// 1.允许简单请求开启CORS
ctx.set("Access-Control-Allow-Origin", "*")
// // 2.非简单请求开启下面的设置
// 允许那些头属性
ctx.set("Access-Control-Allow-Headers", "Accept, AcceptEncoding, Connection, Host, Origin")
// 允许凭证 cookie
ctx.set("Access-Control-Allow-Credentials", true) // cookie
// 允许那些请求方式
ctx.set("Access-Control-Allow-Methods", "PUT, POST, GET, DELETE, PATCH, OPTIONS")
// 3.发起的是一个options请求
if (ctx.method === 'OPTIONS') {
ctx.status = 204
} else {
await next()
}
})
方案三: node代理服务器(webpack)
浏览器由于同源策略访问资源服务器是会跨域, 而采用代理服务器去请求资源服务器的内容,服务器和服务器之间是不受同源策略影响的,所以可以拿到数据.
使用node代理解决跨域
// node服务器代理
// webpack => webpack-dev-server
const express = require('express')
const { createProxyMiddleware } = require('http-proxy-middleware')
const app = express()
app.use(express.static('./client'))
app.use('/api', createProxyMiddleware({
target: "http://localhost:8000",
pathRewrite: {
'^/api': ''
}
}))
app.listen(9000, () => {
console.log('express proxy服务器开启成功')
})
上述代码是采用express写的一个代理demo, 看了上述代码大家是不是有点熟悉呢?
devServer:{
proxy:{
'/api':{
target:"目标地址",
pathRewrite:{'^/api':""}
}
}
}
webpack的代理和我们上述写的node代理本质是一样的. 无论是node服务器还是webpack代理都是使用了http-proxy-middleware这个插件实现了代理的操作. (关于浏览器为什么可以获取代理服务器上的数据, 是因为代理服务器和浏览器静态资源服务器是同源的)
方案四: Nginx反向代理
nginx反向代理: 浏览器去访问的时候会直接访问nginx, nginx会去访问资源服务器获取数据.
nginx反向代理一般都是在上线时使用, 一般都是在服务器中下载配置好nginx,在nginx.config.js中配置:
反向代理和webpack的正向代理其实都差不多, 这里说一下正向代理和反向代理的区别, 通俗易懂来说就是,代理服务器和客户端同源就是正向代理, 代理服务器和服务器同源就是反向代理.
不常见的方案
jsonp: 现在几乎很少用了;
postMessage: 了解;
webscoket: 为了解决跨域,所有的接口都变成socket通信? 它是可以解决跨域,但是它主要还是做即时通信的,很少用来解决跨域.