前端跨域总结(持续更新)

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 证书认证的问题

10、http-proxy-middleware+broswerSync+gulp

上一篇下一篇

猜你喜欢

热点阅读