跨域
什么是跨域
跨域是指一个人域下的文档或脚本试图去请求另一个域下的资源,这里跨域是广义的,我们所说的跨域是狭义的,是由浏览器同源策略限制的一类请求场景
什么是同源策略?
所谓同源是指 协议+域名+端口 相同,这是一种约定,他是浏览器最核心也最基本的安全功能,如果缺少同源策略,浏览器很容易受到 XSS、CSFR 等攻击
即使两个人不同的域名指向同一个ip地址,也不算同源 (www.xxx.com、m.xxx.com、blog.xxx.com)
同源策略限制一下几种行为
- Cookie、LocalStrage 和 IndexDB 无法读取
- DOM 和 JS对象无法获得
- AJAX 请求不能发送
JSONP跨域解决方案
因为基本用不到了,不学了hhh
Node.js中间件代理跨域方案
初始化项目并安装 axios 和 express 依赖
npm init --yes
npm i axios express -s
服务器
const fs = require('fs')
const express = require('express')
const { ppid } = require('process')
const app = express()
// 中间件方法
// 设置node_modules为静态资源目录
// 将来在模板中如果使用了src属性 http://localhost:3000/node_modules
app.use(express.static('node_modules'))
// 当请求/路径时返回index.html
app.get('/',(req,res) => {
fs.readFile('./index.html',(err,data) => {
if(err) {
res.statusCode = 500
res.end('500 Interval Server Error')
}
res.statusCode = 200
res.setHeader('Content-Type', 'text/html')
res.end(data)
})
})
app.get('/api/user', (req,res) => {
res.json({name: 'Max'})
})
app.listen(3000)
index.html内
<!DOCTYPE html>
<html>
<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>Document</title>
</head>
<body>
<script src='/axios/dist/axios.js'></script>
<h1>中间件代理跨域</h1>
<script>
// 服务器域为localhost:3000,现在向8080发送请求
axios.defaults.baseURL = 'http://localhost:8080'
axios.get('/user')
.then(res => {
console.log(res);
})
.catch(err => {
console.log(err);
})
</script>
</body>
</html>
现在启动服务器
nodemon server.js
访问域名localhost:3000,报错请求失败,因为找不到8080
这时因为在前端请求这个接口的时候,可能这个接口会和后端接口不一样,就会导致非同源的状况,为了解决这个问题,可以做一个中间件服务器,设置代理服务器允许跨域的访问这个服务,将每一次请求都转向 localhost:3000 端口,这样在axios请求的路径都会在当前原有服务器上找对应的路径
接下来安装中间件依赖
npm i http-proxy-middleware -s
const express = require('express')
// ***引入中间件模块
const { createProxyMiddleware } = require('Http-proxy-niddleware')
const app = express()
app.all('*', function (req, res, next) {
res.header('Access-Control-Allow-Origin', 'http://localhost:3000')
res.header('Access-Control-Allow-Headers', 'Content-Type')
res.header('Access-Control-Allow-Methord', '*')
res.header('Content-Type', 'application/json;charset=utf-8')
next()
})
// ***使用 http-proxy-middleware
// 中间件 筛子 每个请求来之后 都会转发到 http://localhost:3000 后端服务器
app.use('/', createProxyMiddleware({ target: 'http://localhost:3000', changeOrigin: true}))
app.listen(8080)
新建一个终端运行代理服务器
nodemon proxyServer.js
这时在地址栏输入 localhost:3000,就会以8080端口的跨域请求,访问代理服务器,代理服务器通过中间件将当前请求转到 localhost:3000 处理这个请求
CORS跨域解决方案
同样是上面的例子,稍微修改一下
const fs = require('fs')
const express = require('express')
const app = express()
// 中间件方法
// 设置node_modules为静态资源目录
// 将来在模板中如果使用了src属性 http://localhost:3000/node_modules
app.use(express.static('node_modules'))
app.get('/',(req,res) => {
fs.readFile('./index.html',(err,data) => {
if(err) {
res.statusCode = 500
res.end('500 Interval Server Error')
}
res.statusCode = 200
res.setHeader('Content-Type', 'text/html')
res.end(data)
})
})
app.post('/login', (req,res) => {
res.json({status: 0, message: '登陆成功'})
})
app.listen(3000)
index.html
<!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>Document</title>
</head>
<body>
<script src='/axios/dist/axios.js'></script>
<h1>CORS跨域</h1>
<script>
axios.defaults.baseURL = 'http://127.0.0.1:3000'
axios.post('/login')
.then(res => {
console.log(res);
})
.catch(err => {
console.log(err);
})
</script>
</body>
</html>
运行服务器,访问localhost:3000,axios向127.0.0.1:3000发起请求会报错
解决这个问题,只需要在服务器端上方加入这段
const fs = require('fs')
const express = require('express')
const app = express()
// 设置允许跨域访问该服务
app.all('*', function (req, res, next) {
// 允许跨域访问的域名:若有端口需写全(协议+域名+端口),若没有端口末尾不用加 '/'
res.header('Access-Control-Allow-Origin', 'http://localhost:3000')
res.header('Access-Control-Allow-Headers', 'Content-Type')
res.header('Access-Control-Allow-Methord', '*')
res.header('Content-Type', 'application/json;charset=utf-8')
next()
})
// 中间件方法
刷新页面,请求成功了
axios.post(url [, data[, config]])
使用说明 · Axios 中文说明 · 看云 (kancloud.cn)
当使用axios发起post请求时,第一个为url路径,第二个为请求体数据,第三个为配置选项
配置选项中有一个属性 withCredentials 表示跨域请求时是否需要使用凭证
withCredentials: false, // 默认的
默认情况下关闭,是不允许携带cookie作为登录凭证的,需要打开
在登录鉴权时还需要传输 Token,也要配置
axios.defaults.baseURL = 'http://127.0.0.1:3000'
axios.post('/login',{
username: 'Max',
password: '123'
},{
// 登录鉴权需要携带 token
headers:{
'Authorization': 'is token'
},
// 表示跨域请求时是否需要使用凭证 允许携带cookie
withCredentials: true
}).then(res => {
console.log(res);
}).catch(err => {
console.log(err);
})
默认后端也是不允许携带 token 和 cookie 的,需要各自添加一行代码
// 允许令牌通过
res.header('Access-Control-Allow-Headers', 'Content-Type, X-Token')
// 表示允许携带cookie
res.header('Access-Control-Allow-Credentials', 'true')
在请求标头可以看到token令牌,一般这个令牌是前端发起请求,后端返回的一串随机码,服务器会接收并验证这个 token 直接放行通过,完成登录操作
以上就是 CORS 解决跨域的办法,但是还是需要配置
CORS插件
有一个CORS插件来帮我们完成这些工作
expressjs/cors: Node.js CORS middleware (github.com)
npm i cors -s
然后只需要在服务器内引入模块并使用就可以了
const cors = require('cors')
app.use(cors())
就可以替代app.all('*',function(req,res,next){ })
cors() 默认是不支持携带cookie的,需要配置属性
app.use(cors({
origin: 'http://localhost:3000',
// 允许证书
credentials: true,
// 允许头 授权,这里token的key设为了 Authorization
// 上面的 X-Token 也要改成 Authorization,请求头数据也要以Authorization作为key读取
allowedHeaders: 'Content-Type,Authorization'
}))
下面是cors插件的其他配置选项(机翻加部分修正)
-
origin:配置Access-Control-Allow-Origin CORS header。可能的值:
-
Boolean - 如果设置为false,将设置为禁用CORS
-
String - 设置为特定请求源。例如,将其设置为仅来自"http://example.com"的请求
-
RegExp - 设置为常规表达模式,用于测试请求源。如果是匹配,请求源将进行反映。
例如,该模式将反映来自以"example.com"结尾的原点的任何请求 origin: /example.com$/
-
Array - 设置为一系列有效原产地。每个来源可以是一个或一个。例如,将接受来自"http://example1.com"或"example2.com"子域的任何请求。origin String RegExp : ["example1.com", /.example2.com$/]
-
Function - 设置为实现某些自定义逻辑的功能。该函数将请求源作为第一参数,将回调(称为选项的非功能值)作为第二个参数。
origin callback(err, origin)
-
-
methods: 配置 Access-Control-Allow-Methods CORS header。
允许一个逗号分隔的字符串 'GET,PUT,POST' ,或一个数组 ['GET', 'PUT', 'POST']
-
allowedHeaders: 配置 Access-Control-Allow-Headers CORS header。
允许一个逗号分隔的字符串 'Content-Type, Authorization' ,或一个数组 ['Content-Type', 'Authorization']。
如果不指定,则默认反映 Access-Control-Request-Headers header
-
exposedHeaders: 配置 Access-Control-Expose-Headers CORS header。
允许一个逗号分隔的字符串 'Content-Range,X-Content-Range',或一个数组 ['Content-Range', 'X-Content-Range'] 。如果不指定,则不会暴露自定义头。
-
credentials: 配置 Access-Control-Allow-Credentials CORS header。设置为 true 允许证书
-
maxAge: 配置缓存时间 Access-Control-Max-Age CORS header。设置为整数以传递给header,否则将省略。
-
preflightContinue: 将 CORS 飞行前响应传递给下一个处理程序。
-
optionsSuccessStatus: 提供一个状态代码用于成功的请求, 因为一些旧的浏览器 (IE11, 各种智能电视) 窒息.
OPTIONS``204
AXIOS配置
配置全局axios的三个默认值,可以指定将被用在各个请求的配置默认值
axios.defaults.baseURL = 'https://www.example.com'
axios.defaults.header.common['Ahtuorization'] = AUTH_TOKEN
axios.defaults.header.post['Content-Type'] = 'application/x-www-from-urlencoded'
有了中间这条我们就可以不用每个请求,都吧headers写在axios内部了
axios.defaults.baseURL = 'http://127.0.0.1:3000'
axios.defaults.header.common['Ahtuorization'] = 'drytfgh4rt43456tuf2rtjiorw'
axios.post('/login',{
username: 'Max',
password: '123'
},{
//headers:{
// 'Authorization': 'is token'
//},
withCredentials: true
}).then(res => {
console.log(res);
}).catch(err => {
console.log(err);
})
第三条则是在传递请求体给服务端接收的设置,服务器端通过req.body接收请求体
服务器端会接收到的请求体会是 键值对数据,当你用中间件时,默认得到的会是 undefined,这时需要用以下两种方法解析
- express.json() — 将数据解析为json
- express.urlencoded() — 将数据解析为urlencoded
使用方法是在服务器的上方写入
app.use(express.json())
这时通过打印请求体
app.post('/login', (req,res) => {
console.log(req.body)
res.json({status: 0, message: '登陆成功'})
})
就会获得请求体数据
{ username: 'Max', password: '123' }
但是,大部分的后端默认的接收格式都是urlencoded,因为这样的数据好统一管理
因为json字符串所管理的数据,有可能是数字、字符串、数组 或 对象,json不易于管理,所以需要解析为 urlencoded
结果则是 username=Max&password='123'
//客户端axios
axios.defaults.header.post['Content-Type'] = 'application/x-www-from-urlencoded'
//客户端引入qs库 (qs是一个url参数转化(parse和stringify)的js库)
<script src='https://cdn.bootcss.com/qs/6.9.1/qs.js'><script>
qs.js - 更好的处理url参数 - 簡書 (jianshu.com)
但是只是这样还不行,服务器端解析的结果会是一个空对象,这需要使用axios的拦截器,用函数对请求体做处理
axios.defaults.baseURL = 'http://127.0.0.1:3000'
axios.defaults.headers.common['Authorization'] = 'drytfgh4rt43456tuf2rtjiorw'
axios.defaults.headers.post['Content-Type'] = 'application/x-www-from-urlencoded'
//-------------------------------------------------------------------------
// 在返送请求前拦截器拦截请求
axios.interceptors.request.use(function (config) {
// 获取请求体数据
let data = config.data
// 调用Qs的stringify方法将请求体json数据格式转换为urlencoded
data = Qs.stringify(data)
console.log(data)
return config
}), function (err) {
// 对请求错误做些什么
return Promise.reject(err)
}
//--------------------------------------------------------------------------
axios.post('/login',{
username: 'Max',
password: '123'
},{
withCredentials: true
})
.then(res => {
console.log(res);
}).catch(err => {
console.log(err);
})
这时在客户端的请求体就被转换为 urlencoded 格式了
服务器端需要加上
app.use(express.urlencoded({ extended: true }))
req.body 并没有获取到 urlencoded,算了跳过...
NginX反向代理
实现原理类似于Node中间件代理,需要搭建一个中专nginx服务器,用于转发请求
使用nginx反向代理实现跨域,是最简单的跨域方式,主需要修改nginx的配置即可解决跨域问题,支持所有浏览器,支持session,不需要修改任何代码,并且不会影响服务器性能
实现思路:通过nginx配置一个代理服务器(域名与domain1相同,端口不同)做跳板机,反向代理访问domain2接口,别切可以顺便修改cookie中domain信息,方便当前域cookie写入,实现跨域登录