温馨提示×

温馨提示×

您好,登录后才能下订单哦!

密码登录×
登录注册×
其他方式登录
点击 登录注册 即表示同意《亿速云用户服务条款》

web后端怎么解决跨域问题

发布时间:2022-08-30 13:50:55 来源:亿速云 阅读:200 作者:iii 栏目:开发技术

本文小编为大家详细介绍“web后端怎么解决跨域问题”,内容详细,步骤清晰,细节处理妥当,希望这篇“web后端怎么解决跨域问题”文章能帮助大家解决疑惑,下面跟着小编的思路慢慢深入,一起来学习新知识吧。

    什么是跨域请求

    首先,我们要了解什么是跨域请求。简单来说,当一台服务器资源从另一台服务器(不同 的域名或者端口)请求一个资源或者接口,就会发起一个跨域 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中间件代理(两次跨域)

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

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

    • 接受客户端请求 。

    • 将请求 转发给服务器。

    • 拿到服务器 响应 数据。

    • 将 响应 转发给客户端。

    web后端怎么解决跨域问题

    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/*就可以了。

    读到这里,这篇“web后端怎么解决跨域问题”文章已经介绍完毕,想要掌握这篇文章的知识点还需要大家自己动手实践使用过才能领会,如果想了解更多相关内容的文章,欢迎关注亿速云行业资讯频道。

    向AI问一下细节

    免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:is@yisu.com进行举报,并提供相关证据,一经查实,将立刻删除涉嫌侵权内容。

    AI