前端跨域总结(持续更新)
2017-04-28 本文已影响0人
采姑娘的大白菜
同源策略(Same origin policy)是一种约定,它是浏览器最核心也最基本的安全功能,如果缺少了同源策略,则浏览器的正常功能可能都会受到影响。可以说Web是构建在同源策略基础之上的,浏览器只是针对同源策略的一种实现。现在所有支持JavaScript 的浏览器都会使用这个策略。所谓同源是指,域名,协议,端口相同。当一个浏览器的两个tab页中分别打开来 百度和谷歌的页面当浏览器的百度tab页执行一个脚本的时候会检查这个脚本是属于哪个页面的,即检查是否同源,只有和百度同源的脚本才会被执行。如果非同源,那么在请求数据时,浏览器会在控制台中报一个异常,提示拒绝访问。
源于同源策略,我们有时需要跨域处理。
一、jsonp解决方案
jsonp是利用script标签没有跨域限制的特性,通过在src的url的参数上附加回调函数名字,然后服务器接收回调函数名字并返回一个包含数据的回调函数
function doSomething(data) {
// 对data处理
}
var script = document.createElement("script");
script.src = "http://www.b.com/b.html?callback=doSomething";
document.body.appendChild(script);
// 1.生成一个script标签,将其append在body上,向服务器发出请求
// 2.服务器根据 callback 这个参数生成一个包含数据的函数 doSomething({"a", "1"})
// 3.页面事先已声明doSomething函数,此时执行 doSomething(data) 这个函数,获得数据
示例
前端以jq封装的jsonp为例
$.ajax({
url:'http://192.168.9.5/jsonp_test1.jsp',
dataType:"jsonp",
jsonp:"jsonpcallback",
success:function(data){
var $ul = $("<ul></ul>");
$.each(data,function(i,v){
$("<li/>").text(v["id"] + " " + v["name"]).appendTo($ul)
});
$("#res").append($ul);
}
});
服务器的处理
c#
[HttpGet]
public ActionResult getSurvey_Contact(String contact_id,string survey_id)
{
var callback = Request.QueryString["callback"]; //获取回调函数的名字
var contactColl = MyMongoDb.CurrentDB.GetCollection("Survey_Contact");
if (contact_id == null)
contact_id = String.Empty;
var doc = contactColl.FindAs<BsonDocument>(Query.And(Query.EQ("contact_id", contact_id), Query.EQ("Survey_Id", survey_id))).FirstOrDefault();
if (doc == null)
doc = new BsonDocument();
FormateJSON(new[] { doc });
var answerColl = MyMongoDb.CurrentDB.GetCollection("Survey_Answers");
var answerDocs = answerColl.FindAs<BsonDocument>(Query.And(Query.EQ("contact_id", contact_id), Query.EQ("survey_id", survey_id))).ToArray();
FormateJSON(answerDocs);
doc.Set("answers", new BsonArray(answerDocs));
var result = new ContentResult();
result.ContentEncoding = System.Text.Encoding.UTF8;
result.ContentType = "application/json";
//Regex reg = new Regex("ObjectId\\(([a-z0-9])\\)");
if (String.IsNullOrWhiteSpace(callback))
{
result.Content = doc.ToJson();
return result;
}
else
{
result.Content = callback + "(" + doc.ToJson() + ");"; //前端用js文件格式解析结果,通过callback(data)获取数据
return result;
}
}
//题外
jquery的$.Jsonp或者$.ajax的jsonp和ajax没有关系,只是jquery封装起来方便使用。
实际也是利用script标签没有跨域限制的特性实现的
JSONP的优点:它不像XMLHttpRequest对象实现的Ajax请求那样受到同源策略的限制;它的兼容性更好,在更加古老的浏览器中都可以运行,不需要XMLHttpRequest或ActiveX的支持;并且在请求完毕后可以通过调用callback的方式回传结果。
JSONP的缺点:它只支持GET请求而不支持POST等其它类型的HTTP请求;它只支持跨域HTTP请求这种情况,不能解决不同域的两个页面之间如何进行JavaScript调用的问题。
二、window.name+iframe
//window.name的原理是利用同一个窗口在不同的页面共用一个window.name,这个需要在a.com下建立一个代理文件c.html,使同源后a.html能获取c.html的window.name
//需要目标服务器响应window.name
三、window.location.hash+iframe
//需要目标服务器作处理
//b.html将数据以hash值的方式附加到c.html的url上,在c.html页面通过location.hash获取数据后传到a.html(这个例子是传到a.html的hash上,当然也可以传到其他地方)
四、document.domain
这种方式适用于主域相同,子域不同,比如http://www.a.com和http://b.a.com
假如这两个域名下各有a.html 和b.html,
a.html
document.domain = "a.com";
var iframe = document.createElement("iframe");
iframe.src = "http://b.a.com/b.html";
document.body.appendChild(iframe);
iframe.onload = function() {
console.log(iframe.contentWindow....); // 在这里操作b.html里的元素数据
}
b.html
document.domain = "a.com";
注意:document.domain需要设置成自身或更高一级的父域,且主域必须相同。
五、html5的 postMessage+ifrme
//这个需要目标服务器或者说是目标页面写一个postMessage,主要侧重于前端通讯。
假设在a.html里嵌套个<iframe src=""http://www.b.com/b.html" rel="nofollow">http://www.b.com/b.html" frameborder="0"></iframe>,在这两个页面里互相通信
a.html
window.onload = function() {
window.addEventListener("message", function(e) {
alert(e.data);
});
window.frames[0].postMessage("b data", "http://www.b.com/b.html");
}
b.html
window.onload = function() {
window.addEventListener("message", function(e) {
alert(e.data);
});
window.parent.postMessage("a data", "http://www.a.com/a.html");
}
这样打开a页面就先弹出 a data,再弹出 b data
六、CORS解决方案
CORS是XMLHttpRequest Level 2 里规定的一种跨域方式。在支持这个方式的浏览器里,javascript的写法和不跨域的ajax写法一模一样,只要服务器需要设置Access-Control-Allow-Origin: *
1、apache设置header : Access-Control-Allow-Origin
//httpd.conf
找到这行
#LoadModule headers_module modules/mod_headers.so
把#注释符去掉
LoadModule headers_module modules/mod_headers.so
目的是开启apache头信息自定义模块
<Directory />
AllowOverride none
Require all denied
</Directory>
改为下面代码
<Directory />
Require all denied
Header set Access-Control-Allow-Origin *
</Directory>
设置请求头
2、nginx设置header : Access-Control-Allow-Origin
在nginx的conf文件中加入以下内容:
location / {
add_header Access-Control-Allow-Origin *;
}
3、服务器设置header :Access-Control-Allow-Origin
//示例
php
//header("Access-Control-Allow-Origin:*");
//header("Access-Control-Allow-Origin:http://www.a.com");
c#
//在Web.config设置
<httpProtocol>
<customHeaders>
<add name="Access-Control-Allow-Origin" value="*" />
<add name="Access-Control-Allow-Headers" value="Content-Type" />
<add name="Access-Control-Allow-Methods" value="GET, POST, PUT, DELETE, OPTIONS" />
</customHeaders>
</httpProtocol>
nodejs
var express = require('express');
var app = express();
//设置跨域访问
app.all('*', function(req, res, next) {
res.header("Access-Control-Allow-Origin", "*");
res.header("Access-Control-Allow-Headers", "X-Requested-With");
res.header("Access-Control-Allow-Methods","PUT,POST,GET,DELETE,OPTIONS");
res.header("X-Powered-By",' 3.2.1')
res.header("Content-Type", "application/json;charset=utf-8");
next();
});
app.get('/getdata', function(req, res) {
res.send({id:req.params.id, name: req.params.password});
});
app.listen(3000);
console.log('Listening on port 3000...');
//题外
如果需要跨域设置cookie,要设置Access-Control-Allow-Credentials,例如
c#
x.Headers.Add("Access-Control-Allow-Origin", "http://localhost:8080");
x.Headers.Add("Access-Control-Allow-Credentials", "true");
//题外
cors缺点:
需要服务器配合
设置Access-Control-Allow-Origin 存在一定的风险
七、chrome插件跨域方案
//CORS Toggle
八、代理服务器解决方案
1、nignx反向代理
//搭建一个中转nginx服务器转发请求,如果是自己的主机就比较好操作。但是用了Nginx配置之后,webpack的hot reload会存在比较大的延迟
//nginx.conf
http {
include mime.types;
default_type application/octet-stream;
server {
listen 8080;
charset utf-8;
access_log on;
access_log logs/host.access.log ;
location / {
#8080端口号是开启的本地服务端口号
proxy_pass http://localhost:8080;
}
location /api {
#每有一个新的代理需求,就新增一个location
#反向代理,达到前后端分离开发的目的
proxy_pass http://192.168.60.225:7003;
}
}
}
/***********
关于proxy_pass
既是把请求代理到其他主机,其中 http://www.b.com/ 写法和 http://www.b.com写法的区别如下:
不带/
location /html/
{
proxy_pass http://b.com:8300;
}
带/
location /html/
{
proxy_pass http://b.com:8300/;
}
上面两种配置,区别只在于proxy_pass转发的路径后是否带 “/”。
针对情况1,如果访问url = http://server/html/test.jsp,则被nginx代理后,请求路径会便问http://proxy_pass/html/test.jsp,将test/ 作为根路径,请求test/路径下的资源。
针对情况2,如果访问url = http://server/html/test.jsp,则被nginx代理后,请求路径会变为 http://proxy_pass/test.jsp,直接访问server的根资源。
***********/
2、apache反向代理
用 apache 的 mod_proxy 模块开启反向代理功能来实现:
1 修改 apache 配置文件 httpd.conf ,去掉以下两行前面 # 号,加载反向代理模块
LoadModule proxy_module modules/mod_proxy.so
LoadModule proxy_http_module modules/mod_proxy_http.so
2 在站点目录中修改:
<VirtualHost *:80>
DocumentRoot "E:\study-environment\www"
#正向代理
#ProxyRequests On
#ProxyVia On
#反向代理时设置为Off
ProxyRequests Off
<Proxy "*">
Order deny,allow
#Deny from all
#Allow from 127.0.0.1 188.1.8.1
Allow from all
</Proxy>
#proxy setting
ProxyPass /api http://188.1.8.1/api
#ProxyPassReverse 浏览器的地址栏不会显示反向代理的地址
ProxyPassReverse /api http://188.1.8.1/api
#如果路径的名称/api代理后没有变化,session不会丢失,则可以不用ProxyPassReverseCookiePath
#ProxyPassReverseCookiePath /api /api
ProxyPass /log http://188.1.8.1/apilog
ProxyPassReverse /log http://188.1.8.1/apilog
ProxyPassReverseCookiePath /log /apilog
#另一种写法
<Location / >
ProxyPass http://188.1.8.1/home connectiontimeout=5 timeout=300
ProxyPassReverse http://188.1.8.1/home
</Location>
</VirtualHost>
重启 apache
//题外
正向代理(forward)是一个位于客户端【用户A】和原始服务器(origin server)【服务器B】之间的服务器【代理服务器Z】,为了从原始服务器取得内容,用户A向代理服务器Z发送一个请求并指定目标(服务器B),然后代理服务器Z向服务器B转交请求并将获得的内容返回给客户端。客户端必须要进行一些特别的设置才能使用正向代理。
反向代理正好与正向代理相反,对于客户端而言代理服务器就像是原始服务器,并且客户端不需要进行任何特别的设置。客户端向反向代理的命名空间(name-space)中的内容发送普通请求,接着反向代理将判断向何处(原始服务器)转交请求,并将获得的内容返回给客户端。
简单点说就是
正向代理:客户向代理服务器发请求,代理服务器再把请求到指定服务器
反向代理:客户向目标服务器发请求,代理服务器拦截请求,再把请求转发到指定的目标服务器
3、webpack+webpack-dev-server
//webpack.config.js
var path = require("path");
module.exports = {
entry: {
index: './src/index.entry.js',
authManage: './src/authManage.entry.js',
reports: './src/reports.entry.js'
},
output: {
path: path.join(__dirname, 'public'),
filename: '[name].bundle.js'
},
module: {
loaders: [
{
test: /\.jsx?$/,
loader: 'babel',
exclude: /node_modules/,
query: {
presets: ['es2015', 'react', "stage-2"]
}
},
{ test: /\.css$/, loader: 'style!css' },
{ test: /\.(png|jpg|jpeg|gif|woff)$/, loader: 'url-loader?limit=8192' }
]
},
devServer: {
//代理
proxy: {
'/api/*': 'http://localhost:7003/'
//'/api': {
// target: 'http://localhost:7003/'
//}
}
}
};
//在cmd敲入命令运行webpack-dev-server,例如
>>webpack-dev-server --inline --hot --progress --colors --port 8082
//也可以在package.json的scripts配置命令方便启动服务或者配置new WebpackDevServer来启动服务
//在页面请求本地接口即可,devServer会拦截/api/的请求
//action.js
fetch(`/api/map?exhibition=${data._id}`)
.then(res => res.json())
.then(data => {
let sourceId = data[0]._id;
return fetch(`/api/exhibitor/${sourceId}`);
})
.then(res => res.json())
.then(data => {
return dispatch(addexhibition(data)) ;
})
.catch(error => {
console.log(error);
})
4、node+express+webpack+webpack-dev-middleware+http-proxy-middleware
//webpack-dev-server是一个小型的Node.js Express服务器,它使用webpack-dev-middleware来服务于webpack的包,除此自外,它还有一个通过Sock.js来连接到服务器的微型运行时.所以我们也可以不用webpack-dev-server,自己来搭一个
//server.js
var path = require('path')
var express = require('express')
var webpack = require('webpack')
var proxyMiddleware = require('http-proxy-middleware')
var webpackConfig = require('./webpack.dev.conf')
var port = 7003
// 定义HTTP代理到自定义的API后端
var proxyTable = {
'/api': {
target: 'http://localhost:7003/',
changeOrigin: true,
// pathRewrite: {
// '^/api': '/api'
// }
},
'/Uploads': {
target: 'http://localhost:7003/',
changeOrigin: true
}
}
var app = express()
var compiler = webpack(webpackConfig)
var devMiddleware = require('webpack-dev-middleware')(compiler, {
publicPath: webpackConfig.output.publicPath,
stats: {
colors: true,
chunks: false
}
})
var hotMiddleware = require('webpack-hot-middleware')(compiler)
// force page reload when html-webpack-plugin template changes
compiler.plugin('compilation', function (compilation) {
compilation.plugin('html-webpack-plugin-after-emit', function (data, cb) {
hotMiddleware.publish({ action: 'reload' })
cb()
})
})
// proxy api requests
Object.keys(proxyTable).forEach(function (context) {
var options = proxyTable[context]
if (typeof options === 'string') {
options = { target: options }
}
app.use(proxyMiddleware(context, options)) //使用代理中间件
})
// handle fallback for HTML5 history API
app.use(require('connect-history-api-fallback')())
// serve webpack bundle output
app.use(devMiddleware)
// enable hot-reload and state-preserving
// compilation error display
app.use(hotMiddleware)
// serve pure static assets
var staticPath = path.posix.join(config.dev.assetsPublicPath, config.dev.assetsSubDirectory)
app.use(staticPath, express.static('./static'))
module.exports = app.listen(port, function (err) {
if (err) {
console.log(err)
return
}
var uri = 'http://localhost:' + port
console.log('Listening at ' + uri + '\n')
// when env is testing, don't need open it
if (process.env.NODE_ENV !== 'testing') {
opn(uri)
}
})
5、node+proxy-middleware
//主要针对采用https协议的服务器
var connect = require('connect');
var url = require('url');
var proxy = require('proxy-middleware');
var app = connect();
app.use('/api', proxy(url.parse('https://example.com/endpoint')));
// now requests to '/api/x/y/z' are proxied to 'https://example.com/endpoint/x/y/z'
//same as example above but also uses a short hand string only parameter
app.use('/api-string-only', proxy('https://example.com/endpoint'));
6、node+cors
7、node+node-http-proxy
8、node+node-reverse-proxy
9、node+reverse-proxy
//通过pem和SNI 解决了 HTTPS 证书认证的问题