跨域

什么是跨域 跨域的危险

官方解释:

出于浏览器的同源策略限制。同源策略(Sameoriginpolicy)是一种约定,它是浏览器最核心也最基本的安全功能。

同源策略限制了从同一个源加载的文档或脚本如何与来自另一个源的资源进行交互。这是一个用于隔离潜在恶意文件的重要安全机制。

同源的定义(官方)
如果两个页面的协议,端口(如果有指定)和主机都相同,则两个页面具有相同的源。

我们也可以把它称为“协议/主机/端口 tuple”,或简单地叫做“tuple". (“tuple” ,“元”,是指一些事物组合在一起形成一个整体,比如(1,2)叫二元,(1,2,3)叫三元)

没有同源策略限制的两大危险场景

据我了解,浏览器是从两个方面去做这个同源策略的,一是针对接口的请求,二是针对Dom访问。

接口请求:

这个很常见,在我们一个服务访问另一个服务时就会出现此问题

DOM访问(文件的源定义):
对于跨窗口DOM访问,每个文件都被视为一个单独的源文件,但有一个例外:
如果一个文件是从另一个文件中加载的,而另一个文件本来可以按照相同的源策略加载该文件,则认为它们具有相同的源。
没有同源策略限制的接口请求
cookie一般用来处理登录等场景,目的是让服务端知道谁发出的这次请求。如果你请求了接口进行登录,服务端验证通过后会在响应头加入Set-Cookie字段,然后下次再发请求的时候,浏览器会自动将cookie附加在HTTP请求的头字段Cookie中,服务端就能知道这个用户已经登录过了。

我们来看看很多写到跨域的这样一个场景:

1.你准备去清空你的购物车,于是打开了买买买网站www.maimaimai.com,然后登录成功,一看,购物车东西这么少,不行,还得买多点。
2.你在看有什么东西买的过程中,你的好友发给你一个链接www.piannide.com
3.你饶有兴致地浏览着www.piannide.com!由于没有同源策略的限制,它向www.maimaimai.com发起了请求!

聪明的你一定想到上面的话“服务端验证通过后会在响应头加入Set-Cookie字段,然后下次再发请求的时候,

浏览器会自动将cookie附加在HTTP请求的头字段Cookie中”,这样一来,这个不法网站就相当于登录了你的账号,可以为所欲为了!

没有同源策略限制的Dom查询

1.有一天你刚睡醒,收到一封邮件,说是你的银行账号有风险,赶紧点进www.yinghang.com改密码。

你吓尿了,赶紧点进去,还是熟悉的银行登录界面,你果断输入你的账号密码,登录进去看看钱有没有少了。
2.睡眼朦胧的你没看清楚,平时访问的银行网站是www.yinhang.com,而现在访问的是www.yinghang.com,这个钓鱼网站做了什么呢?

// HTML
<iframe name="yinhang" src="www.yinhang.com"></iframe>
// JS
// 由于没有同源策略的限制,钓鱼网站可以直接拿到别的网站的Dom
const iframe = window.frames['yinhang']
const node = iframe.document.getElementById('你输入账号密码的Input')
console.log(`拿到了这个${node},我还拿不到你刚刚输入的账号密码吗`)
限制

1.无法读取非同源网页的 Cookie、LocalStorage

2.无法接触非同源网页的 DOM

3.无法向非同源地址发送 AJAX 请求

跨域

简单点说:就是出现了不同源的情况会产生跨域。

Web是构建在同源策略基础之上的,浏览器只是针对同源策略的一种实现。同源策略会阻止一个域的javascript脚本和另外一个域的内容进行交互。所谓同源(即指在同一个域)就是两个页面具有相同的协议(protocol),主机(host)和端口号(port)

举个栗子:

当一个请求url的协议、域名、端口三者之间任意一个与当前页面url不同即为跨域

当前页面url被请求页面url是否跨域原因
http://www.test.com/http://www.test.com/index.html同源(协议、域名、端口号相同)
http://www.test.com/https://www.test.com/index.html跨域协议不同(http/https)
http://www.test.com/http://www.baidu.com/跨域主域名不同
http://www.test.com/http://blog.test.com/跨域子域名不同(www/blog)
http://www.test.com:8080/http://www.test.com:7001/跨域端口号不同(8080/7001)

跨域解决方法

部署在同一容器下
例如:

localhost:8080/one

localhost:8080/two

这两个项目之间就不会存在跨域的问题了
CORS解决跨域

Cross-Origin Resource Sharing(是一种允许当前域(domain)的资源(比如html/js/web service)被其他域(domain)的脚本请求访问的机制,通常由于同域安全策略(the same-origin security policy)浏览器会禁止这种跨域请求。)是一种前后端用于允许跨域通信的一种约定机制。
在这里插入图片描述
只要浏览器和后端做好相关的对接和支持工作,CORS就能跑通。

目前除IE10以下的IE浏览器,其余主流浏览器均支持CORS。

//允许milo.qq.com的请求跨域
header("Access-Control-Allow-Origin:milo.qq.com");

//设置通配符,允许所有请求跨域
header("Access-Control-Allow-Origin:*");

不建议后端配置Access-Control-Allow-Origin头为通配符*,因为为了安全起见,这种设置不可控。

建议后端以白名单的形式加header头,对于白名单内的请求,设置对应的跨域头,否则拒绝跨域。(使用上面那种)

简单得设置了Access-Control-Allow-Origin头并不能把cookie带过去,cookie作为前后端通信中常用的数据载体经常用于校验凭证等数据传输,非常重要。

下面简单介绍一下通过CORS实现跨域发送cookie。

跨域发送cookie只需要前端带上withCredentials相关头,并且后端加上Access-Control-Allow-Credentials:true即可。

//当前位于a.qq.com中,向c.qq.com/xx.php接口发送请求
$.ajax({
    type:'GET',
    url:'http://c.qq.com/xx.php',
    xhrFields:{
        withCredentials:true
    },
    success:function(res){
        console.log(res);
    },
    fail:function(){

    }
})
//当前为c.qq.com/xx.php
    //设置为制定的origin,不能设置为*
    header('Access-Control-Allow-Origin:http://a.qq.com');
    //允许携带cookie
    header('Access-Control-Allow-Credentials:true');

可以使用过滤器的方式解决跨域问题

<filter>
<filter-name>contextfilter</filter-name>
<filter-class>com.cf.hr.filter.WebContextFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>contextfilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
import java.io.IOException;
 
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletResponse;
 
public class WebContextFilter implements Filter {
 
    @Override
    public void destroy() {
    
         
    }
 
    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
            throws IOException, ServletException {
        HttpServletResponse  httpServletResponse = (HttpServletResponse) response;
		//如果允许多个域请求数据的话,可以直接用逗号隔开:"http://www.baidu.com,http://google.com"
        httpServletResponse.setHeader("Access-Control-Allow-Origin", "http://a.qq.com");
        httpServletResponse.setHeader("Access-Control-Allow-Headers", "accept,content-type"); 
		httpServletResponse.setHeader("Access-Control-Allow-Credentials", "true"); 
        httpServletResponse.setHeader("Access-Control-Allow-Methods", "OPTIONS,GET,POST,DELETE,PUT"); 
        chain.doFilter(request, httpServletResponse);
         
    }
 
    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
      
      
    }
 
}
jsonp解决跨域

JSONP(JSON with Padding)(json 数据填充)只支持GET请求

jsonp本质上是script请求,是前端页面中用于外链script的一种请求方式。

由于script标签有天然的跨域特性(拥有此特性的还用img标签等),而且其返回的内容为文本,且可以直接执行的特点。

故通过将请求返回的内容封装成js脚本的形式,在前端直接执行的方式可以得到后端返回内容。

$.ajax({
             url: "http://" + ipPort + "/user/getUser",
             type: 'GET',
             dataType: 'jsonp',
             jsonp: 'callback',                                
             success: function (data) {
                   alert('success:'+data);
             },
             fail: function () {
                   alert('fail');
             }
});   

前端调用默认会发出一个类似: http://c.qq.com/xx.php?callback=xxxxxx 的请求到后端,后端拿到callback参数值,后会将其作为回调方法,直接返回一段用callback调用responseData的方法即可。

@RequestMapping(value = "/json")
    public Object test(HttpServletRequest request) {

   Map<String, Object> obj = new HashMap<String, Object>();

        obj.put("name", "xiaoqiang");

        obj.put("age", 20);

        String callback = request.getParameter("jsonpCallback");

        System.out.println("请求参数:" + callback);

        return callback + "(" + com.alibaba.fastjson.JSONObject.toJSON(obj) + ")";

   }

使用jsonp方式跨域的优点很明显,就是兼容性强,所有浏览器均支持。而且后端改造的成本也低。 缺点就是jsonp本质上是script请求,只能支持GET请求,对于大数据量和传输文件等都不支持,而且也无法拿到相关的返回头,状态码等数据。

Nginx代理

如果我们请求的时候还是用前端的域名,然后有个东西帮我们把这个请求转发到真正的后端域名上,就避免跨域了

进入到nginx安装目录中的conf文件夹中

配置文件下面找到server节点

我们要修改几个参数就可以将项目定制到我们的项目中 


## demo listen 5017 proxy 5000 and 5001 ##
server {
#  nginx监听的端口
    listen 5017; 
# 前端项目ip,可以localhost
    server_name a.xxx.com;
    access_log /var/log/nginx/a.access.log;
    error_log /var/log/nginx/a.error.log;
    root html;
    index index.html index.htm index.php;

    ## send request back to flask ##
# 前端项目的端口
    location / {
        proxy_pass  http://127.0.0.1:5000/ ; 
 #Proxy Settings
        proxy_redirect off;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_next_upstream error timeout invalid_header http_500 http_502 http_503 http_504;
        proxy_max_temp_file_size 0;
        proxy_connect_timeout 90;
        proxy_send_timeout 90;
        proxy_read_timeout 90;
        proxy_buffer_size 4k;
        proxy_buffers 4 32k;
        proxy_busy_buffers_size 64k;

   }
    location /proxy {
        rewrite ^.+proxy/?(.*)$ /$1 break;
# 服务端的ip端口 
        proxy_pass  http://127.0.0.1:5001/ ; 
 #Proxy Settings
        proxy_redirect off;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_next_upstream error timeout invalid_header http_500 http_502 http_503 http_504;
        proxy_max_temp_file_size 0;
        proxy_connect_timeout 90;
        proxy_send_timeout 90;
        proxy_read_timeout 90;
        proxy_buffer_size 4k;
        proxy_buffers 4 32k;
        proxy_busy_buffers_size 64k;

   }

}

## End a.xxx.com ##
按照这个配置即可

这些命令使得localhost:5017代理了localhost:5000
使得localhost:5017/proxy代理了localhost:5001

使得localhost:5017/proxy/api/代理了localhost:5001/api/

如此以来,原本需要从5000端口请求5001端口的url,变成了从5017端口请求5017端口的/proxy。解决了同源策略带来的跨域问题。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值