跨域
什么是跨域
跨域是指一个域下的文档或脚本试图去请求另一个域下的资源,这里跨域是广义的。
广义的跨域:
1.资源跳转: A链接、重定向、表单提交
2.资源嵌入: <link>、<script>、<img>、<frame>等dom标签,还有样式中background:url()、@font-face()等文件外链
3.脚本请求: js发起的ajax请求、dom和js对象的跨域操作等
其实我们通常所说的跨域是狭义的,是由浏览器同源策略限制的一类请求场景。
什么是同源策略?
同源策略/SOP(Same origin policy)是一种约定,由Netscape公司1995年引入浏览器,它是浏览器最核心也最基本的安全功能,如果缺少了同源策略,浏览器很容易受到XSS、CSFR等攻击。所谓同源是指"协议+域名+端口"三者相同,即便两个不同的域名指向同一个ip地址,也非同源。
同源策略限制以下几种行为:
1.) Cookie、LocalStorage 和 IndexDB 无法读取
2.) DOM 和 Js对象无法获得
3.) AJAX 请求不能发送
常见跨域场景
URL 说明 是否允许通信
http://www.domain.com/a.js
http://www.domain.com/b.js 同一域名,不同文件或路径 允许
http://www.domain.com/lab/c.js
http://www.domain.com:8000/a.js
http://www.domain.com/b.js 同一域名,不同端口 不允许
http://www.domain.com/a.js
https://www.domain.com/b.js 同一域名,不同协议 不允许
http://www.domain.com/a.js
http://192.168.4.12/b.js 域名和域名对应相同ip 不允许
http://www.domain.com/a.js
http://x.domain.com/b.js 主域相同,子域不同 不允许
http://domain.com/c.js
http://www.domain1.com/a.js
http://www.domain2.com/b.js 不同域名 不允许
演示跨域
server.js
const http = require('http');
const fs = require('fs');
http.createServer((req, res) => {
const { url, method } = req;
if (url === '/' && method === 'GET') {
// 读取首页
fs.readFile('./index.html', (err, data) => {
if (err) {
res.statusCode = 500;//服务器内部错误
res.end('500- Interval Serval Error!');
}
res.statusCode = 200;//设置状态码
res.setHeader('Content-Type', 'text/html');
res.end(data);
})
} else if (url === '/user' && method === 'GET') {
res.statusCode = 200;//设置状态码
res.setHeader('Content-Type', 'application/json');
res.end(JSON.stringify([{ name: "小马哥" }]));
}
}).listen(3000);
axios发起请求
axios.get('http://127.0.0.1:3000/user').then(res => {
console.log(res.data);
}).catch(err => {
console.log(err);
})
跨域常用解决方案
- 通过jsonp跨域
- 跨域资源共享(CORS 最常用)
- nginx代理跨域
- nodejs中间件代理跨域
通过JSONP跨域
通常为了减轻web服务器的负载,我们把js、css,img等静态资源分离到另一台独立域名的服务器上,在html页面中再通过相应的标签从不同域名下加载静态资源,而被浏览器允许,基于此原理,我们可以通过动态创建script,再请求一个带参网址实现跨域通信
axios最新版本已经不支持jsonp方法了,不想因为一个jsonp请求就又去引一个依赖,所以决定自己封装一下
axios.jsonp = (url) => {
if (!url) {
console.error('Axios.JSONP 至少需要一个url参数!')
return;
}
return new Promise((resolve, reject) => {
window.jsonCallBack = (result) => {
resolve(result)
}
var JSONP = document.createElement("script");
JSONP.type = "text/javascript";
JSONP.src = `${url}callback=jsonCallBack`;
document.getElementsByTagName("head")[0].appendChild(JSONP);
setTimeout(() => {
document.getElementsByTagName("head")[0].removeChild(JSONP)
}, 500)
})
}
// 第一种 通过jsonp
axios.jsonp('http://127.0.0.1:3000/user?')
.then(res=>{
console.log(res);
}).catch(err=>{
console.log(err);
})
server.js
const express = require('express');
const fs = require('fs');
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 Serval Error!');
}
res.statusCode = 200;
res.setHeader('Content-Type','text/html');
res.end(data);
})
})
// app.set('jsonp callback name', 'cb')
app.get('/api/user',(req,res)=>{
console.log(req);
// http://127.0.0.1:3000/user?cb=jsonCallBack
// const cb = req.query.cb;
// cb({})
// res.end(`${cb}(${JSON.stringify({name:"小马哥"})})`)
res.jsonp({name:'小马哥'})
})
app.listen(3000);
jsonp缺点:只能实现get一种请求。
Nodejs中间件代理跨域
实现原理:同源策略是浏览器需要遵循的标准,而如果是服务器向服务器请求就无需遵循同源策略。 代理服务器,需要做以下几个步骤:
- 接受客户端请求 。
- 将请求 转发给服务器。
- 拿到服务器 响应 数据。
- 将 响应 转发给客户端。
index.html文件,通过代理服务器http://127.0.0.1:8080向目标服务器http://localhost:3000/user请求数据
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
</head>
<body>
<script src='/axios/dist/axios.js'></script>
<h2>中间件代理跨域</h2>
<script>
axios.defaults.baseURL = 'http://localhost:8080';
axios.get('/user')
.then(res => {
console.log(res);
})
.catch(err => {
console.log(err);
})
</script>
</body>
</html>
proxyServer.js代理服务器
const express = require('express');
const { createProxyMiddleware } = require('http-proxy-middleware');
const app = express();
// 代理服务器操作
//设置允许跨域访问该服务.
app.all('*', function (req, res, next) {
res.header('Access-Control-Allow-Origin', '*');
res.header('Access-Control-Allow-Headers', 'Content-Type');
res.header('Access-Control-Allow-Methods', '*');
res.header('Content-Type', 'application/json;charset=utf-8');
next();
});
// http-proxy-middleware
// 中间件 每个请求来之后 都会转发到 http://localhost:3001 后端服务器
app.use('/', createProxyMiddleware({ target: 'http://localhost:3001', changeOrigin: true }));
app.listen(8080);
业务服务器server.js
const express = require('express');
const fs = require('fs');
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 Serval Error!');
}
res.statusCode = 200;
res.setHeader('Content-Type','text/html');
res.end(data);
})
})
// app.set('jsonp callback name', 'cb')
app.get('/user',(req,res)=>{
res.json({name:'小马哥'})
})
app.listen(3001);
通过CORS跨域
//设置允许跨域访问该服务.
app.all('*', function (req, res, next) {
/// 允许跨域访问的域名:若有端口需写全(协议+域名+端口),若没有端口末尾不用加'/'
res.header('Access-Control-Allow-Origin', '*');
res.header('Access-Control-Allow-Headers', 'Content-Type');
res.header('Access-Control-Allow-Methods', '*');
res.header('Content-Type', 'application/json;charset=utf-8');
next();
});
具体实现:
-
响应简单请求:动词为get/post/head,如果没有自定义请求头,
Content-Type
是application/x-www-form-urlencoded
,multipar/form-data
或text/plagin
之一,通过添加以下解决res.header('Access-Control-Allow-Origin', '*');
-
响应prefight请求,需要响应浏览器发出的options请求(预检请求),并根据情况设置响应头
//设置允许跨域访问该服务. app.all('*', function (req, res, next) { res.header('Access-Control-Allow-Origin', 'http://localhost:3002'); //允许令牌通过 res.header('Access-Control-Allow-Headers', 'Content-Type,X-Token'); res.header('Access-Control-Allow-Methods', 'GET,POST,PUT'); //允许携带cookie res.header('Access-Control-Allow-Credentials', 'true'); res.header('Content-Type', 'application/json;charset=utf-8'); next(); });
前端
在这里给大家补充了axios相关用法,具体的看视频
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta http-equiv="X-UA-Compatible" content="ie=edge"> <title>Document</title> </head> <body> <script src='/axios/dist/axios.js'></script> <script src="https://cdn.bootcss.com/qs/6.9.1/qs.js"></script> <h2>CORS跨域</h2> <script> axios.defaults.baseURL = 'http://127.0.0.1:3002'; axios.defaults.headers.common['Authorization'] = 'xaxadadadadad'; axios.defaults.headers.post['Content-Type'] = 'application/x-www-form-urlencoded'; axios.interceptors.request.use(function (config) { let data = config.data; data = Qs.stringify(data); config.data = data; // 在发送请求之前做些什么 return config; }, function (error) { // 对请求错误做些什么 return Promise.reject(error); }); axios.post('/login', { username: 'xiaomage', password: 123 }, { // headers: { // 'Authorization': 'adjahdj1313131' // }, // 表示跨域请求时需要使用凭证 允许携带cookies withCredentials: true }) .then(res => { console.log(res); }).catch(err => { console.log(err); }) </script> </body> </html>
使用第三方插件cors
npm i cors -S
server.js
const cors = require('cors');
//简单使用
app.use(cors())
配置选项参考链接
ex:
app.use(cors({
origin:'http://localhost:3002', //设置原始地址
credentials:true, //允许携带cookie
methods:['GET','POST'], //跨域允许的请求方式
allowedHeaders:'Content-Type,Authorization' //允许请求头的携带信息
}))
nginx反向代理
实现原理类似于Node中间件代理,需要你搭建一个中转nginx服务器,用于转发请求。
使用nginx反向代理实现跨域,是最简单的跨域方式。只需要修改nginx的配置即可解决跨域问题,支持所有浏览器,支持session,不需要修改任何代码,并且不会影响服务器性能。
实现思路:通过nginx配置一个代理服务器(域名与domain1相同,端口不同)做跳板机,反向代理访问domain2接口,并且可以顺便修改cookie中domain信息,方便当前域cookie写入,实现跨域登录。
// proxy服务器
server {
listen 81;
server_name www.domain1.com;
location / {
proxy_pass http://www.domain2.com:8080; #反向代理
proxy_cookie_domain www.domain2.com www.domain1.com; #修改cookie里域名
index index.html index.htm;
# 当用webpack-dev-server等中间件代理接口访问nignx时,此时无浏览器参与,故没有同源限制,下面的跨域配置可不启用
add_header Access-Control-Allow-Origin http://www.domain1.com; #当前端只跨域不带cookie时,可为*
add_header Access-Control-Allow-Credentials true;
}
}
常见问题FAQ
- 免费下载或者VIP会员专享资源能否直接商用?
- 本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
- 提示下载完但解压或打开不了?
- 找不到素材资源介绍文章里的示例图片?
- 模板不会安装或需要功能定制以及二次开发?
发表评论
还没有评论,快来抢沙发吧!