您当前的位置:首页 > 电脑百科 > 程序开发 > 语言 > JAVA

跨域问题是怎样造成的?

时间:2020-04-28 09:27:55  来源:  作者:

跨域问题的由来

相信很多人都或多或少了解过跨域问题,尤其在现如今前后端分离大行其道的时候。

你在本地开发一个前端项目,这个项目是通过 node 运行的,端口是9528,而服务端是通过 spring boot 提供的,端口号是7001。

当你调用一个服务端接口时,很可能得到类似下面这样的一个错误:

跨域问题是怎样造成的?

 

然后你在发送请求的地方debug,在出现异常的地方你将得到这样的结果:

跨域问题是怎样造成的?

 

异常对象很诡异,返回的 response 是 undefined 的,并且 message 消息中只有一个"Network Error"。

看到这里你应该要知道,你遇到跨域问题了。

但是你需要明确的一点是,这个请求已经发出去了,服务端也接收到并处理了,但是返回的响应结果不是浏览器想要的结果,所以浏览器将这个响应的结果给拦截了,这就是为什么你看到的response是undefined。

浏览器的同源策略

那浏览器为什么会将服务端返回的结果拦截掉呢?

这就需要我们了解浏览器基于安全方面的考虑,而引入的 同源策略(same-origin policy) 了。

早在1995年,Netscape 公司就在浏览器中引入了“同源策略”。

最初的 “同源策略”,主要是限制Cookie的访问,A网页设置的 Cookie,B网页无法访问,除非B网页和A网页是“同源”的。

那么怎么确定两个网页是不是“同源”呢,所谓“同源”就是指"协议+域名+端口"三者相同,即便两个不同的域名指向同一个ip地址,也非同源。

跨域问题是怎样造成的?

 

没有同源策略的保护

那么为什么要做这个同源的限制呢?因为如果没有同源策略的保护,浏览器将没有任何安全可言。

老李是一个钓鱼爱好者,经常在 我要买(51mai.com) 的网站上买各种钓鱼的工具,并且通过 银行(yinhang.com) 以账号密码的方式直接支付。

这天老李又在 51mai.com 上买了一根鱼竿,输入银行账号密码支付成功后,在支付成功页看到一个叫 钓鱼(diaoyu.com) 的网站投放的一个"免费领取鱼饵"的广告。

老李什么都没想就点击了这个广告,跳转到了钓鱼的网站,殊不知这真是一个 “钓鱼” 网站,老李银行账户里面钱全部被转走了。

跨域问题是怎样造成的?

 

以上就是老李的钱被盗走的过程:

1.老李购买鱼竿,并登录了银行的网站输入账号密码进行了支付,浏览器在本地缓存了银行的Cookie

2.老李点击钓鱼网站,钓鱼网站使用老李登录银行之后的Cookie,伪造成自己是老李进行了转账操作。

这个过程就是著名的CSRF(Cross Site Request Forgery),跨站请求伪造,正是由于可能存在的伪造请求,导致了浏览器的不安全。

那么如何防止CSRF攻击呢,可以参考这篇文章:如何防止CSRF攻击?

同源策略限制哪些行为

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

随着互联网的发展,"同源策略"越来越严格,不仅限于Cookie的读取。目前,如果非同源,共有三种行为受到限制。

(1) Cookie、LocalStorage 和 IndexDB 无法读取。

(2) DOM 无法获得。

(3) 请求的响应被拦截。

虽然这些限制是必要的,但是有时很不方便,合理的用途也会受到影响,所以为了能够获取非“同源”的资源,就有了跨域资源共享。

跨域资源共享

看到这里你应该明白,为什么文章开头的请求会被拦截了,原因就是请求的源和服务端的源不是“同源”,而服务端又没有设置允许的跨域资源共享,所以请求的响应被浏览器给拦截掉了。

CORS 是一个 W3C 标准,全称是"跨域资源共享"(Cross Origin Resource Sharing),它允许浏览器向跨源服务器,发出 XMLHttpRequest 请求,从而克服了只能发送同源请求的限制。

CORS实现机制

那跨域资源共享机制是怎样实现的呢?

当一个资源(origin)通过脚本向另一个资源(host)发起请求,而被请求的资源(host)和请求源(origin)是不同的源时(协议、域名、端口不全部相同),浏览器就会发起一个 跨域 HTTP 请求 ,并且浏览器会自动将当前资源的域添加在请求头中一个叫 Origin 的 Header 中。

当然了,有三个标签本身就是允许跨域加载资源的:

  • <img src=XXX>
  • <link href=XXX>
  • <script src=XXX>

比如某个网站的首页 http://domain-a.com/index.html 通过 <img src="http://domain-b.com/image.jpg" /> 来加载其他域上的图片,除此之外还有诸如通过 CDN 节点引入css和js文件的方式。

出于安全原因,浏览器限制从脚本内发起的跨域 HTTP 请求。 例如,XMLHttpRequest 和 Fetch API 遵循同源策略。 也就是说使用这些 API 的 Web 应用程序只能从加载应用程序的同一个域请求 HTTP 资源,除非响应报文中包含了正确 CORS 响应头。

通过在响应报文中设置额外的 HTTP 响应头来告诉浏览器,运行在某个 origin 上的 Web 应用被准许访问来自不同源服务器上的资源,此时浏览器就不会将该响应拦截掉了。

那这些额外的 HTTP 响应头是什么呢?

响应头是否必须含义Access-Control-Allow-Origin是该字段表示,服务端接收哪些来源的域的请求Access-Control-Allow-Credentials否是否可以向服务端发送Cookie,默认是 falseAccess-Control-Expose-Headers否可以向请求额外暴露的响应头

其中只有 Access-Control-Allow-Origin 是必须的,该响应头的值可以是请求的 Origin 的值,也可以是 * ,表示服务端接收所有来源的请求。

当浏览器发起 CORS 请求时,默认只能获得6个响应头的值:

Cache-Control、Content-Language、Content-Type、Expires、Last-Modified、Pragma

如果还需要返回其他的响应头给前端,则可以通过在 Access-Control-Expose-Headers 中指定。

CORS的两种请求类型

CORS有两种类型的请求,分别是:简单请求(simple request)和非简单请求(not-so-simple request)

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

(1) 请求方法是以下三种方法之一:

HEADGETPOST

(2) HTTP的头信息不超出以下几种字段:

AcceptAccept-LanguageContent-LanguageLast-Event-IDContent-Type:只限于三个值 Application/x-www-form-urlencoded 、multipart/form-datatext/plain

凡是不同时满足上面两个条件,就属于非简单请求,浏览器对这两种请求的处理,是不一样的。

为什么会有两种不同类型的请求呢?

CORS 规范要求,对那些可能对服务器数据产生副作用的 HTTP 请求方法(特别是 GET 以外的 HTTP 请求,或者搭配某些 MIME 类型的 POST 请求),浏览器必须首先使用 OPTIONS 方法发起一个预检请求(preflight request),从而获知服务端是否允许该跨域请求。

服务器确认允许之后,浏览器才能发起实际的 HTTP 请求。在预检请求的返回中,服务器端也可以通知客户端,是否需要携带身份凭证(包括 Cookies 和 HTTP 认证相关的数据)。

非简单请求就要求浏览器先发送一个预检请求,预检通过后再发送实际的请求。

怎样实现CORS

知道了CORS的实现机制之后,我们就可以解决遇到的CORS的问题了。

1.通过JSONP

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

JSONP 和 AJAX 相同,都是客户端向服务器端发送请求,从服务器端获取数据的方式。但 AJAX 属于同源策略,JSONP 属于非同源策略(支持跨域请求)。JSONP优点是简单兼容性好,可用于解决主流浏览器的跨域数据访问的问题。缺点是仅支持 GET 方法具有局限性,不安全可能会遭受XSS攻击。

2.利用反向代理服务器

同源策略是浏览器需要遵循的标准,而如果是服务器向服务器请求就无需遵循同源策略

所以通过反向代理服务器可以有效的解决跨域问题,代理服务器需要做以下几个步骤:

1.接受客户端的请求

2.将请求转发给实际的服务器

3.将服务器的响应结果返回给客户端

Nginx就是类似的反向代理服务器,可以通过配置Nginx代理来解决跨域问题。

3.服务端支持CORS

最安全的还是服务端来设置允许哪些来源的请求,即服务端在接收到请求之后,对允许的请求源设置 Access-Control-Allow-Origin 的响应头。

通过@CrossOrigin注解

这里以 Spring Boot 为例,可以通过 @CrossOrigin 注解来指定哪些类或者方法支持跨越,如下列代码所示:

/**
 * 在类上加注解
 */
@CrossOrigin({"http://127.0.0.1:9528", "http://localhost:9528"})
@RestController
public class UserController {
    
}
@RestController
public class UserController {
    @Resource
    private UserFacade userFacade;
    /**
     * 在方法上加注解
     */
    @GetMapping(ApiConstant.Urls.GET_USER_INFO)
    @CrossOrigin({"http://127.0.0.1:9528", "http://localhost:9528"})
    public PojoResult<UserDTO> getUserInfo() {
        return userFacade.getUserInfo();
    }
}

通过CorsRegistry设置全局跨域配置

@Configuration
@EnableWebMvc
public class WebConfig extends WebMvcConfigurerAdapter {
	@Override
	public void addCorsMappings(CorsRegistry registry) {
		registry.addMapping("/**")
            .allowedOrigins("http://127.0.0.1:9528", "http://localhost:9528");
	}
}

如果你使用的是 Spring Boot,推荐的做法是只定义一个 WebMvcConfigurer 的Bean:

@Configuration
public class MyConfiguration {
    @Bean
    public WebMvcConfigurer corsConfigurer() {
        return new WebMvcConfigurerAdapter() {
            @Override
            public void addCorsMappings(CorsRegistry registry) {
                registry.addMapping("/**")
                    .allowedOrigins("http://127.0.0.1:9528", "http://localhost:9528");
            }
        };
    }
}

以上两种方式在没有定义拦截器(Interceptor)的时候,使用一切正常,但是如果你有一个全局的拦截器用来检测用户的登录态,例如下面的简易代码:

public class AuthenticationInterceptor implements HandlerInterceptor {
    @Override
    public boolean preHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object object) throws Exception {
        // 从 http 请求头中取出 token
        String token = httpServletRequest.getHeader("token");
        // 检查是否登录
        if (token == null) {
            throw new InvalidTokenException(ResultCode.INVALID_TOKEN.getCode(), "登录态失效,请重新登录");
        }
        return true;
    }
}

当自定义拦截器返回true时,一切正常,但是当拦截器抛出异常(或者返回false)时,后续的CORS设置将不会生效。

为什么拦截器抛出异常时,CORS不生效呢?可以看下这个issue:

when interceptor preHandler throw exception, the cors is broken

有个人提交了一个issue,说明如果在自定义拦截器的preHandler方法中抛出异常的话,通过 CorsRegistry 设置的全局 CORS 配置就失效了,但是Spring Boot 的成员不认为这是一个Bug。

然后提交者举了个具体的例子:

他先定义了CorsRegistry,并添加了一个自定义的拦截器,拦截器中抛出了异常

跨域问题是怎样造成的?

 

然后他发现AbstractHandlerMapping在添加CorsInterceptor的时候,是将 Cors 的拦截器加在拦截器链的最后:

跨域问题是怎样造成的?

 

那就会造成上面说的问题,在自定义拦截器中抛出异常之后,CorsInterceptor 拦截器就没有机会执行向 response 中设置 CORS 相关响应头了。

issue的提交者也给出了解决的方案,就是将用来处理 Cors 的拦截器 CorsInterceptor 加在拦截器链的第一个位置:

跨域问题是怎样造成的?

 

这样的话请求来了之后,第一个就会为 response 设置相应的 CORS 响应头,后续如果其他自定义拦截器抛出异常,也不会有影响了。

感觉是一个可行的解决方案,但是 Spring Boot 的成员认为这不是 Spring Boot 的Bug,而是 Spring Framework 的 Bug,所以将这个issue关闭了。

通过CorsFilter设置全局跨域配置

既然通过拦截器设置全局跨域配置会有问题,那我们还有另外一种方案,通过过滤器 CorsFilter 的方式来设置,代码如下:

@Configuration
public class MyConfiguration {
	@Bean
	public FilterRegistrationBean corsFilter() {
		UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
		CorsConfiguration config = new CorsConfiguration();
		config.setAllowCredentials(true);
		config.addAllowedOrigin("http://127.0.0.1:9528");
        config.addAllowedOrigin("http://localhost:9528");
		config.addAllowedHeader("*");
		config.addAllowedMethod("*");
		source.registerCorsConfiguration("/**", config);
		FilterRegistrationBean bean = new FilterRegistrationBean(new CorsFilter(source));
		bean.setOrder(0);
		return bean;
	}
}

为什么过滤器可以而拦截器不行呢?

因为过滤器依赖于 Servlet 容器,基于函数回调,它可以对几乎所有请求进行过滤。而拦截器是依赖于 Web 框架(如Spring MVC框架),基于反射通过AOP的方式实现的。

在触发顺序上如下图所示:

跨域问题是怎样造成的?

 

因为过滤器在触发上是先于拦截器的,但是如果有多个过滤器的话,也需要将 CorsFilter 设置为第一个过滤器才行。

原文:https://my.oschina.net/u/3216837/blog/3196454



Tags:跨域   点击:()  评论:()
声明:本站部分内容及图片来自互联网,转载是出于传递更多信息之目的,内容观点仅代表作者本人,如有任何标注错误或版权侵犯请与我们联系(Email:2595517585@qq.com),我们将及时更正、删除,谢谢。
▌相关推荐
问题后端已经配置好了跨域,前端本地搭建vue-cli测试环境的时候如何解决跨域?(前提进行了基本的axios封装)分析这个时候后端API是一个明确的外网环境(使用外网IP或固定域名访问),...【详细内容】
2021-11-03  Tags: 跨域  点击:(55)  评论:(0)  加入收藏
浏览器的同源策略跨域的根本原因就是因为浏览器的同源策略,这是浏览器出于安全性考虑做出的限制,所谓同源是指:域名、协议、端口相同。比如在互联网上有两个资源(网页或者请求等...【详细内容】
2021-09-17  Tags: 跨域  点击:(101)  评论:(0)  加入收藏
前言难以置信,我居然被跨域问题折磨了一上午。相信很多程序员都遇到过跨域问题,当然,解决方案也有很多种。但我今天尝试了无数种办法,依旧没有解决。直到最后我媳妇儿给了我个提...【详细内容】
2021-09-16  Tags: 跨域  点击:(108)  评论:(0)  加入收藏
“前端如何解决跨域问题?” 这个是前段在知乎看到的一个提问,这几乎是做前端都会遇到的一个问题,产生的情况可能会很多,解决一个问题还是要先了解下为什么会产生这样问题,学习最好的方法就是结合一些实际的案例来学习,理解...【详细内容】
2021-06-09  Tags: 跨域  点击:(171)  评论:(0)  加入收藏
首先说一下,跨域它不是一个问题,它是浏览器的一种安全策略,叫同源策略。拿前后端分离项目来说,前端调试或者部署的地址,与api的地址,必须拥有相同的协议、域名、端口号。否则,前端...【详细内容】
2021-02-19  Tags: 跨域  点击:(264)  评论:(0)  加入收藏
什么是CSRF攻击?了解CSRF攻击的最佳方法是看一个具体示例。 例子假设您银行的网站提供了一种表格,该表格允许将资金从当前登录的用户转移到另一个银行帐户。例如,转账表单可能...【详细内容】
2021-01-15  Tags: 跨域  点击:(153)  评论:(0)  加入收藏
1、背景随着MPLS技术的成熟,其应用越来越广泛,尤其是在VPN方面。电信运营商提供给用户的MPLSVPN服务主要有MPLSLayer2 VPN和 MPLS Layer 3 VPN两类。MPLS Layer 2 VPN因标准化...【详细内容】
2021-01-04  Tags: 跨域  点击:(160)  评论:(0)  加入收藏
这篇文章我们聊一聊CORS跨域,它的全称是"跨域资源共享"(Cross-origin resource sharing)。在之前的文章中我们已经详细介绍了如何使用JSONP进行接口跨域请求,如果不了解的可以参...【详细内容】
2020-12-28  Tags: 跨域  点击:(165)  评论:(0)  加入收藏
内容简介MPLS VPN跨域互联有三种方式,分别是Option-A、Option-B、Option-C。这三种方式各自有各自的优缺点,你不能说某种跨域方式就能完全替代另外一种。但有时候,遇到两个ASBR...【详细内容】
2020-12-15  Tags: 跨域  点击:(307)  评论:(0)  加入收藏
前言:Axios是一个来自于vue官方推荐的一个用于与后端(Java、go、Python、PHP)进行数据交互的JavaScript库,你可以通过axios库快速高效的与后端进行数据交互,是现文件上传等复杂功...【详细内容】
2020-10-09  Tags: 跨域  点击:(163)  评论:(0)  加入收藏
▌简易百科推荐
面向对象的特征之一封装 面向对象的特征之二继承 方法重写(override/overWrite) 方法的重载(overload)和重写(override)的区别: 面向对象特征之三:多态 Instanceof关键字...【详细内容】
2021-12-28  顶顶架构师    Tags:面向对象   点击:(2)  评论:(0)  加入收藏
一、Redis使用过程中一些小的注意点1、不要把Redis当成数据库来使用二、Arrays.asList常见失误需求:把数组转成list集合去处理。方法:Arrays.asList 或者 Java8的stream流式处...【详细内容】
2021-12-27  CF07    Tags:Java   点击:(3)  评论:(0)  加入收藏
文章目录 如何理解面向对象编程? JDK 和 JRE 有什么区别? 如何理解Java中封装,继承、多态特性? 如何理解Java中的字节码对象? 你是如何理解Java中的泛型的? 说说泛型应用...【详细内容】
2021-12-24  Java架构师之路    Tags:JAVA   点击:(5)  评论:(0)  加入收藏
大家好!我是老码农,一个喜欢技术、爱分享的同学,从今天开始和大家持续分享JVM调优方面的经验。JVM调优是个大话题,涉及的知识点很庞大 Java内存模型 垃圾回收机制 各种工具使用 ...【详细内容】
2021-12-23  小码匠和老码农    Tags:JVM调优   点击:(11)  评论:(0)  加入收藏
前言JDBC访问Postgresql的jsonb类型字段当然可以使用Postgresql jdbc驱动中提供的PGobject,但是这样在需要兼容多种数据库的系统开发中显得不那么通用,需要特殊处理。本文介绍...【详细内容】
2021-12-23  dingle    Tags:JDBC   点击:(13)  评论:(0)  加入收藏
Java与Lua相互调用案例比较少,因此项目使用需要做详细的性能测试,本内容只做粗略测试。目前已完成初版Lua-Java调用框架开发,后期有时间准备把框架进行抽象,并开源出来,感兴趣的...【详细内容】
2021-12-23  JAVA小白    Tags:Java   点击:(11)  评论:(0)  加入收藏
Java从版本5开始,在 java.util.concurrent.locks包内给我们提供了除了synchronized关键字以外的几个新的锁功能的实现,ReentrantLock就是其中的一个。但是这并不意味着我们可...【详细内容】
2021-12-17  小西学JAVA    Tags:JAVA并发   点击:(11)  评论:(0)  加入收藏
一、概述final是Java关键字中最常见之一,表示“最终的,不可更改”之意,在Java中也正是这个意思。有final修饰的内容,就会变得与众不同,它们会变成终极存在,其内容成为固定的存在。...【详细内容】
2021-12-15  唯一浩哥    Tags:Java基础   点击:(17)  评论:(0)  加入收藏
1、问题描述关于java中的日志管理logback,去年写过关于logback介绍的文章,这次项目中又优化了下,记录下,希望能帮到需要的朋友。2、解决方案这次其实是碰到了一个问题,一般的情况...【详细内容】
2021-12-15  软件老王    Tags:logback   点击:(19)  评论:(0)  加入收藏
本篇文章我们以AtomicInteger为例子,主要讲解下CAS(Compare And Swap)功能是如何在AtomicInteger中使用的,以及提供CAS功能的Unsafe对象。我们先从一个例子开始吧。假设现在我们...【详细内容】
2021-12-14  小西学JAVA    Tags:JAVA   点击:(21)  评论:(0)  加入收藏
最新更新
栏目热门
栏目头条