关于后端如何解决跨域的问题说明

目录
  • 什么是跨域请求
  • 什么是同源策略
  • 同源策略限制内容有
    • 那什么又是同源?
  • 跨域解决方案
    • 1.jsonp
    • 2.cors
    • 3.@CrossOrigin注解
    • 4.使用SpringCloud网关
    • 5.Node中间件代理(两次跨域)
    • 6.nginx反向代理
  • 总结

对于跨域,相信同学们都有所了解。前端的跨域的若干种方式,大家也都知道,什么 JSONP,iframe+domain 等等。但是我们今天的主题,不是前端跨域,而是后端跨域。

一旦提及到跨域,就会想到同源策略,那我们就先来回顾跨域和同源策略。

什么是跨域请求

首先,我们要了解什么是跨域请求。简单来说,当一台服务器资源从另一台服务器(不同 的域名或者端口)请求一个资源或者接口,就会发起一个跨域 HTTP 请求。

举个简单的例子:

从http://aaa.com/index.html,发送一个 Ajax 请求,请求地址是 http://bbb.com/下面的一个接口,这就是发起了一个跨域请求。在不做任何处理的情况下,这个跨域请求是无法被成功请求的,因为浏览器基于同源策略 会对跨域请求做一定的限制。那什么又是同源策略呢?

什么是同源策略

首先大家要知道同源策略发生的场景——浏览器中,什么意思呢?如果不是浏览器的话, 就不会受到同源策略的影响。也就是说,两个服务器直接进行跨域请求是可以进行数据请求的。这也就为我们接下来的后端跨域埋下一下小伏笔。 同源策略的目的是什么呢?同源策略限制了从同一个源加载的文档或者脚本如何与来自另 一个源的资源进行交互。这是一个用于隔离潜在恶意文件的重要安全机制。

同源策略限制内容有

  • Cookie、LocalStorage、IndexedDB 等存储性内容
  • DOM 节点
  • AJAX 请求不能发送

那什么又是同源?

大家都知道,一个域名请求地址的组成是:协议+域名+端口号+请求资源地址 , 当协议、域名、端口号中任意一个不相同时 , 都算作不同源(必须是域名完全相同,比如说 a.example.com 和 b.example.com 这两个域名。

虽然它们的顶级域名和二级域名(均为 example.com)都相同,但是三级域名(a 和 b)不相同,所以也不能算作域名相同)。

如果不同时满足这上面三个条件,那就不符合浏览器的同源策略。 需要注意的是,不是所有的交互都会被同源策略拦截下来,下面两种交互就不会触发同源策略:

跨域写操作(Cross-origin writes)

  • 例如超链接、重定向以及表单的提交操 作,特定少数的 HTTP 请求需要添加预检请求(preflight)

跨域资源嵌入(Cross-origin embedding)

  • <script> 标签嵌入的跨域脚本; o <link> 标签嵌入的 CSS 文件; o <img> 标签嵌入图片;
  • <video> 和 <audio> 标签嵌入多媒体资源; o <object>, <embed>, <applet> 的插件;
  • @font-face 引入的字体,一些浏览器允许跨域字体(cross-origin fonts),一些需要同源字体(same-origin fonts);
  • <frame> 和 <iframe> 载入的任何资源,站点可以使用 X-FrameOptions 消息头来组织这种形式的跨域交互。

这里你或许有个疑问:请求跨域了,那么请求到底发出去没有?

跨域并不是请求发不出去,请求能发出去,服务端能收到请求并正常返回结果,只是结果被浏览器拦截了。

你可能会疑问明明通过表单的方式可以发起跨域请求,为什么 Ajax 就不会?因为归根结底,跨域是为了阻止用户读取到另一个域名下的内容,Ajax 可以获取响应,浏览器认为这不安全,所以拦截了响应。但是表单并不会获取新的内容,所以可以发起跨域请求。

同时也说明了跨域并不能完全阻止 CSRF,因为请求毕竟是发出去了。

跨域解决方案

跨域就是通过某些手段来绕过同源策略限制,实现不同服务器之间通信的效果。方法有很多 ,大致分为两类:

  • 服务端进行设置默认允许某些域名跨域访问
  • 从客户端入手想办法绕开同源安全策略

常见的解决方案有:

1.jsonp

利用 <script> 标签没有跨域限制的漏洞,网页可以得到从其他来源动态产生的 JSON 数据。JSONP请求一定需要对方的服务器做支持才可以。

JSONP和AJAX相同,都是客户端向服务器端发送请求,从服务器端获取数据的方式。但AJAX属于同源策略,JSONP属于非同源策略(跨域请求)

JSONP优点是简单兼容性好,可用于解决主流浏览器的跨域数据访问的问题。缺点是仅支持get方法具有局限性,不安全可能会遭受XSS攻击。

2.cors

CORS(Cross-origin resource sharing),跨域资源共享。CORS 其实是浏览器制定的一个规范,浏览器会自动进行 CORS 通信,它的实现则主要在服务端,它通过一些 HTTP Header 来限制可以访问的域,例如页面 A 需要访问 B 服务器上的数据,如果 B 服务器 上声明了允许 A 的域名访问,那么从 A 到 B 的跨域请求就可以完成。对于那些会对服务器数据产生副作用的 HTTP 请求,浏览器会使用 OPTIONS 方法发起 一个预检请求(preflight request),从而可以获知服务器端是否允许该跨域请求,服 务器端确认允许后,才会发起实际的请求。在预检请求的返回中,服务器端也可以告知客 户端是否需要身份认证信息。我们只需要设置响应头,即可进行跨域请求。

虽然设置 CORS 和前端没什么关系,但是通过这种方式解决跨域问题的话,会在发送请求时出现两种情况,分别为简单请求和复杂请求。

简单请求:

只要同时满足以下两大条件,就属于简单请求:

1)使用GET、HEAD、POST方法之一;

2)Content-Type 的值仅限于:text/plain、multipart/form-data、application/x-www-form-urlencoded,请求中的任意 XMLHttpRequestUpload 对象均没有注册任何事件监听器; XMLHttpRequestUpload 对象可以使用 XMLHttpRequest.upload 属性访问;

复杂请求:

不符合以上条件的请求就肯定是复杂请求了。 复杂请求的CORS请求,会在正式通信之前,增加一次HTTP查询请求,称为"预检"请求,该请求是 option 方法的,通过该请求来知道服务端是否允许跨域请求。

我们用PUT向后台请求时,属于复杂请求,后台需被请求的Servlet中添加Header设置,Access-Control-Allow-Origin这个Header在W3C标准里用来检查该跨域请求是否可以被通过,如果值为*则表明当前页面可以跨域访问。默认的情况下是不允许的。

一般我们可以写一个过滤器:

@WebFilter(filterName = "corsFilter", urlPatterns = "/*",
        initParams = {@WebInitParam(name = "allowOrigin", value = "*"),
                @WebInitParam(name = "allowMethods", value = "GET,POST,PUT,DELETE,OPTIONS"),
                @WebInitParam(name = "allowCredentials", value = "true"),
                @WebInitParam(name = "allowHeaders", value = "Content-Type,X-Token")})
public class CorsFilter implements Filter {

    private String allowOrigin;
    private String allowMethods;
    private String allowCredentials;
    private String allowHeaders;
    private String exposeHeaders;

    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
        allowOrigin = filterConfig.getInitParameter("allowOrigin");
        allowMethods = filterConfig.getInitParameter("allowMethods");
        allowCredentials = filterConfig.getInitParameter("allowCredentials");
        allowHeaders = filterConfig.getInitParameter("allowHeaders");
        exposeHeaders = filterConfig.getInitParameter("exposeHeaders");
    }

    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        HttpServletRequest request = (HttpServletRequest) servletRequest;
        HttpServletResponse response = (HttpServletResponse) servletResponse;
        if (!StringUtils.isEmpty(allowOrigin)) {
            if(allowOrigin.equals("*")){
                // 设置哪个源可以访问
                response.setHeader("Access-Control-Allow-Origin", allowOrigin);
            }else{
                List<String> allowOriginList = Arrays.asList(allowOrigin.split(","));
                if (allowOriginList != null && allowOriginList.size() > 0) {
                    String currentOrigin = request.getHeader("Origin");
                    if (allowOriginList.contains(currentOrigin)) {
                        response.setHeader("Access-Control-Allow-Origin", currentOrigin);
                    }
                }
            }
        }
        if (!StringUtils.isEmpty(allowMethods)) {
            //设置哪个方法可以访问
            response.setHeader("Access-Control-Allow-Methods", allowMethods);
        }
        if (!StringUtils.isEmpty(allowCredentials)) {
            // 允许携带cookie
            response.setHeader("Access-Control-Allow-Credentials", allowCredentials);
        }
        if (!StringUtils.isEmpty(allowHeaders)) {
            // 允许携带哪个头
            response.setHeader("Access-Control-Allow-Headers", allowHeaders);
        }
        if (!StringUtils.isEmpty(exposeHeaders)) {
            // 允许携带哪个头
            response.setHeader("Access-Control-Expose-Headers", exposeHeaders);
        }
        filterChain.doFilter(servletRequest, servletResponse);
    }

    @Override
    public void destroy() {

    }
}

大功告成,现在前端就可以跨域获取后台的数据了,正如我们上面所说的,后端是实现 CORS 通信的关键。

如果你的SpringBoot版本在2.0以上,以下代码配置即可完美解决你的前后端跨域请求问题:

@Configuration
public class CorsConfig {
    @Bean
    public CorsFilter corsFilter() {
        final UrlBasedCorsConfigurationSource urlBasedCorsConfigurationSource = new UrlBasedCorsConfigurationSource();
        final CorsConfiguration corsConfiguration = new CorsConfiguration();
        /*是否允许请求带有验证信息*/
        corsConfiguration.setAllowCredentials(true);
        /*允许访问的客户端域名*/
        corsConfiguration.addAllowedOrigin("*");
        /*允许服务端访问的客户端请求头*/
        corsConfiguration.addAllowedHeader("*");
        /*允许访问的方法名,GET POST等*/
        corsConfiguration.addAllowedMethod("*");
        urlBasedCorsConfigurationSource.registerCorsConfiguration("/**", corsConfiguration);
        return new CorsFilter(urlBasedCorsConfigurationSource);
    }
}

或者使用WebMvcConfigurationSupport,实现方式有很多,感兴趣的可以自行研究。

3.@CrossOrigin注解

这个方法仅对Java有用。springboot中,在Controller类上添加一个 @CrossOrigin(origins ="*") 注解就可以实现对当前controller 的跨域访问了,当然这个标签也可以加到方法上,或者直接加到入口类上对所有接口进行跨域处理,注意这个注解只在JDK1.8版本以上才起作用。

4.使用SpringCloud网关

服务网关(zuul)又称路由中心,用来统一访问所有api接口,维护服务。

Spring Cloud Zuul通过与Spring Cloud Eureka的整合,实现了对服务实例的自动化维护,所以在使用服务路由配置的时候,我们不需要向传统路由配置方式那样去指定具体的服务实例地址,只需要通过Ant模式配置文件参数即可

5.Node中间件代理(两次跨域)

实现原理:同源策略是浏览器需要遵循的标准,而如果是服务器向服务器请求就无需遵循同源策略。这样的话,我们可以让服务器替我们发送一个请求,请求其他服务器下面的数据。然后我们的页面访问当前服务器下的接口就没有跨域问题了。

代理服务器,需要做以下几个步骤:

  • 接受客户端请求 。
  • 将请求 转发给服务器。
  • 拿到服务器 响应 数据。
  • 将 响应 转发给客户端。

6.nginx反向代理

实现原理类似于Node中间件代理,需要你搭建一个中转nginx服务器,用于转发请求。

使用nginx反向代理实现跨域,是最简单的跨域方式。只需要修改nginx的配置即可解决跨域问题,支持所有浏览器,支持session,不需要修改任何代码,并且不会影响服务器性能。

实现思路:通过nginx配置一个代理服务器做跳板机,反向代理访问domain2接口,并且可以顺便修改cookie中domain信息,方便当前域cookie写入,实现跨域登录。

将nginx目录下的nginx.conf修改如下:

// 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;
        add_header Access-Control-Allow-Methods GET, POST, OPTIONS;
        add_header Access-Control-Allow-Headers *;
    }
}

这样我们的前端代理只要访问 http:www.domain1.com:81/*就可以了。

总结

  • CORS支持所有类型的HTTP请求,是跨域HTTP请求的根本解决方案
  • JSONP只支持GET请求,JSONP的优势在于支持老式浏览器,以及可以向不支持CORS的网站请求数据。
  • 不管是Node中间件代理还是nginx反向代理,主要是通过同源策略对服务器不加限制。
  • 日常工作中,用得比较多的跨域方案是cors和nginx反向代理

以上为个人经验,希望能给大家一个参考,也希望大家多多支持我们。

(0)

相关推荐

  • Spring Cloud项目前后端分离跨域的操作

    跨域问题,其实百度上面有一堆的解决方案 针对普通的情况其实百度上面的方案都是可行的. 我这里主要介绍2种情况. 当然我这里的配置都是基于网关的,而不是基于服务的. 1.没有增加权限验证. 2.增加了spring security的权限验证(我这里是基于keyCloak),增加了Authorization 首先我们介绍第一种情况的解决方法,这个很简单,只需要在启动类里面配置过滤器就可以解决. @Bean public CorsFilter corsFilter() { //1.添加CORS配置信息

  • SpringBoot前后端分离解决跨域问题的3种解决方案总结

    目录 什么是跨域 跨域问题的解决策略 三种解决方法 总结 什么是跨域 想要知道什么是跨域的话,我们可以通过一个小案例简单了解一下跨域的概念:在项目代码编写的时候,我们将前端项目代码和后端的项目代码相分离开,一个运行在本地的8080端口一个运行在本地的8888端口,这也就是我们常说的前后端分离项目.现在使用前端的请求去调用后端的接口,就会产生以下的错误 Access to XMLHttpRequest at 'http://localhost:8888/请求名' from origin ‘http

  • springboot解决前后端分离时的跨域问题

    随着分布式微服务的兴起,越来越多的公司在开发web项目的时候选择前后端分离的模式开发,前后端分开部署,使得分工更加明确,彻底解放了前端. 我们知道,http请求都是无状态,现在比较流行的都是jwt的形式处理无状态的请求,在请求头上带上认证参数(token等),前后端分离有好处,也有坏处,第一次开发前后端分离项目的人,肯定会遇到前端请求跨域的问题,这个怎么处理呢?在说处理方案前,有必要说明一下为什么会跨域和什么是跨域? 一.为什么会跨域? 出于浏览器的同源策略限制.同源策略(Sameoriginp

  • java后端解决跨域的几种问题解决

    1.java过滤器过滤 允许整个项目跨域访问,可通过filter来进行过虑: public class SimpleCORSFilter implements Filter{ @Override public void destroy() { } @Override public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletExceptio

  • 关于后端如何解决跨域的问题说明

    目录 什么是跨域请求 什么是同源策略 同源策略限制内容有 那什么又是同源? 跨域解决方案 1.jsonp 2.cors 3.@CrossOrigin注解 4.使用SpringCloud网关 5.Node中间件代理(两次跨域) 6.nginx反向代理 总结 对于跨域,相信同学们都有所了解.前端的跨域的若干种方式,大家也都知道,什么 JSONP,iframe+domain 等等.但是我们今天的主题,不是前端跨域,而是后端跨域. 一旦提及到跨域,就会想到同源策略,那我们就先来回顾跨域和同源策略. 什么

  • vue+Java后端进行调试时解决跨域问题的方式

    今天在开发过程中遇到一个问题,拿到了一套vue代码,计划对这套代码的部分样式进行调整,Java后端代码已经写好并且部署到了线上.这时命令行运行vue项目时访问会受限,取不下数据来,遇到了跨域访问失败的问题,这时可以怎么做呢? 首先,要了解什么叫跨域访问? 跨域,指的是浏览器不能执行其他网站的脚本.它是由浏览器的同源策略造成的,是浏览器对javascript施加的安全限制. 所谓同源是指,域名,协议,端口均相同,举个例子: http://www.123.com/index.html 调用 http

  • springboot后端解决跨域问题

    首先我门要知道什么是跨域: 跨域是指 不同域名之间相互访问.跨域,指的是浏览器不能执行其他网站的脚本.它是由浏览器的同源策略造成的,是浏览器对JavaScript施加的安全限制. 也就是如果在A网站中,我们希望使用Ajax来获得B网站中的特定内容 如果A网站与B网站不在同一个域中,那么就出现了跨域访问问题. 什么是同一个域? 同一协议,同一ip,同一端口,三同中有一不同就产生了跨域. 前端解决跨域: 前边也说了,跨域是浏览器不能执行其他网站的脚本.它是由浏览器的同源策略造成的,是浏览器对Java

  • 详解基于angular-cli配置代理解决跨域请求问题

    1.跨域请求产生 随着不同终端(Pad/Mobile/PC)的兴起,对开发人员的要求越来越高,纯浏览器端的响应式已经不能满足用户体验的高要求,我们往往需要针对不同的终端开发定制的版本.为了提升开发效率,前后端分离的需求越来越被重视,后端负责业务/数据接口,前端负责展现/交互逻辑,同一份数据接口,我们可以定制开发多个版本. 而前后端分离带来的一个问题就是前端web部署的服务器和后端提供服务的服务器大概率不在同一个域名下,进而会产生跨域问题. 2.通用解决方案 如果浏览器支持HTML5,那么就可以一

  • Angular4开发解决跨域问题详解

    1.跨域 浏览器对于javascript的同源策略的限制,例如a.cn下面的js不能调用b.cn中的js,对象或数据(因为a.cn和b.cn是不同域),所以跨域就出现了. 上面提到的,同域的概念又是什么呢??? 简单的解释就是相同域名,端口相同,协议相同 同源策略: 请求的url地址,必须与浏览器上的url地址处于同域上,也就是域名,端口,协议相同. 比如:我在本地上的域名是study.cn,请求另外一个域名一段数据,这个时候在浏览器上会报错,这个就是同源策略的保护,如果浏览器对javascri

  • 利用nginx解决跨域问题的方法(以flask为例)

    前言 我们单位的架构是在api和js之间架构一个中间层(python编写),以实现后端渲染,登录状态判定,跨域转发api等功能.但是这样一个中间会使前端工程师的工作量乘上两倍,原本js可以直接ajax请求api,但是我们不得不ajax请求中间层,中间层再请求api. 如图: 为了少敲代码,提高工作效率,我们当然希望将python中间层砍掉,但是如何解决以下三个问题,成为关键: 后端渲染 登录状态判定 跨域转发api 关于1,2我会在另外两篇博客中详细叙述,这篇文章主要解决3,也就是跨域问题.解决

  • 利用jsonp与代理服务器方案解决跨域问题

    前言 本文将从实践角度介绍如何使用jsonp和代理服务器方案解决跨域问题,分享出来供大家参考学习,下面话不多说了,来一起看看详细的介绍吧. 浏览器为了保护用户安全,引入了同源策略,即一个服务器页面无法访问另一个协议.域名.端口不同的服务器数据.当页面需要跨服务器访问另一个服务器的数据时,即产生跨域行为.以豆瓣的公开API(https://api.douban.com/v2/book/1220562)为例,当前我的服务器处于http://127.0.0.1:5000下,豆瓣的服务器很显然跟我的服务

  • vue解决跨域路由冲突问题思路解析

    vue 简介 Vue.js(读音 /vjuː/, 类似于 view) 是一套构建用户界面的渐进式框架. Vue 只关注视图层, 采用自底向上增量开发的设计. Vue 的目标是通过尽可能简单的 API 实现响应的数据绑定和组合的视图组件. Vue 学习起来非常简单,本教程基于 Vue 2.1.8 版本测试. 当我们在路由里面配置成以下代理可以解决跨域问题 proxyTable: { '/goods/*': { target: 'http://localhost:3000' }, '/users/*

随机推荐