敞开成长之旅!这是我参与「日新计划 12 月更文应战」的第30天,点击查看活动概况

前语

本文将为我们对 【SpringMVC教程】跨域问题 的相关内容进行介绍。当一个恳求url的协议、域名、端口三者之间恣意一个与当时页面url不一同,就会产生跨域。那么终究什么是跨域,跨域问题该怎么处理,本文详细将对同源战略什么是跨域简略恳求与非简略恳求跨域问题处理等进行翔实介绍~

Java全栈学习道路可参阅: 【Java全栈学习道路】最全的Java学习道路及常识清单,Java自学方向指引,内含最全Java全栈学习技能清单~

算法刷题道路可参阅: 算法刷题道路总结与相关材料共享,内含最翔实的算法刷题道路攻略及相关材料共享~

↩️本文上接:最全面的SpringMVC教程——视图解析器详解,大局异常捕获,处理资源,拦截器,大局装备类


一、同源战略

同源战略(Sameoriginpolicy)是一种约定,它是浏览器最中心也最根本的安全功能。同源战略会阻止一个域的javascript脚本和别的一个域的内容进行交互。所谓同源(即指在同一个域)便是两个页面具有相同的协议(protocol),主机(host)和端口号(port)。

二、什么是跨域

当一个恳求url的协议、域名、端口三者之间恣意一个与当时页面url不一同,就会产生跨域。

最全面的SpringMVC教程——跨域问题

举一个比如:从127.0.0.1:5000拜访的页面中,有Javascript运用ajax拜访127.0.0.1:8888的接口就会产生跨域。

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

非同源限制:

  • 无法读取非同源网页的 Cookie、LocalStorage 和 IndexedDB
  • 无法接触非同源网页的 DOM
  • 无法向非同源地址发送 AJAX 恳求

三、简略恳求与非简略恳求

CORS全称是”跨域资源共享“(Cross-origin resource sharing);浏览器将CORS恳求分红两类: 简略恳求(simple request)和非简略恳求(not-so-simple request)。

只需一同满足以下两大条件,就归于简略恳求:

  • (1) 恳求办法是以下三种办法之一:HEAD;GET;POST
  • (2)HTTP的头信息不超出以下几种字段:Accept;Accept-Language;Content-Language;Last-Event-ID;Content-Type(只限于三个值application/x-www-form-urlencoded、multipart/form-data、text/plain)

这是为了兼容表单(form),由于历史上表单一向能够宣布跨域恳求。Ajax 的跨域规划便是,只需表单能够发,Ajax就能够直接发。

但凡不一同满足上面两个条件,就归于非简略恳求。

浏览器对这两种恳求的处理,是不一样的。

(1)简略恳求

根本流程

对于简略恳求,浏览器直接宣布CORS恳求。详细来说,便是在头信息之中,添加一个Origin字段。

下面是一个比如,浏览器发现这次跨源AJAX恳求是简略恳求,就自动在头信息之中,添加一个Origin字段。

GET /cors HTTP/1.1
Origin: http://api.bob.com
Host: api.ydlclass.com
Accept-Language: en-US
Connection: keep-alive
User-Agent: Mozilla/5.0...

上面的头信息中,Origin字段用来说明,本次恳求来自哪个源(协议 + 域名 + 端口)。服务器依据这个值,决定是否赞同这次恳求。

假如Origin指定的源,不在答应范围内,服务器会回来一个正常的HTTP回应。浏览器发现,这个回应的头信息没有包含Access-Control-Allow-Origin字段(详见下文),就知道出错了,然后抛出一个过错,被XMLHttpRequeston error回调函数捕获。留意,这种过错无法通过状况码辨认,由于HTTP回应的状况码有可能是200。

假如Origin指定的域名在答应范围内,服务器回来的呼应,会多出几个头信息字段。

Access-Control-Allow-Origin: http://api.bob.com
Access-Control-Allow-Credentials: true
Access-Control-Expose-Headers: FooBar
Content-Type: text/html; charset=utf-8

上面的头信息之中,有三个与CORS恳求相关的字段,都以Access-Control-最初。

(1)Access-Control-Allow-Origin

该字段是有必要的。它的值要么是恳求时Origin字段的值,要么是一个*,标明承受恣意域名的恳求。

(2)Access-Control-Allow-Credentials

该字段可选。它的值是一个布尔值,标明是否答应发送Cookie。默许情况下,Cookie不包含在CORS恳求之中。设为true,即标明服务器明确答应,Cookie能够包含在恳求中,一同发给服务器。这个值也只能设为true,假如服务器不要浏览器发送Cookie,删去该字段即可。

(3)Access-Control-Expose-Headers

该字段可选。CORS恳求时,XMLHttpRequest目标的getResponseHeader()办法只能拿到6个根本字段:Cache-ControlContent-LanguageContent-TypeExpiresLast-ModifiedPragma。假如想拿到其他字段,就有必要在Access-Control-Expose-Headers里边指定。上面的比如指定,getResponseHeader('FooBar')能够回来FooBar字段的值。

(4)withCredentials 特点

上面说到,CORS恳求默许不发送Cookie和HTTP认证信息。假如要把Cookie发到服务器,一方面要服务器赞同,指定Access-Control-Allow-Credentials字段。

Access-Control-Allow-Credentials: true

另一方面,开发者有必要在Ajax恳求中打开withCredentials特点。

var xhr = new XMLHttpRequest();
xhr.withCredentials = true;

否则,即使服务器赞同发送Cookie,浏览器也不会发送。或许,服务器要求设置Cookie,浏览器也不会处理。

可是,假如省略withCredentials设置,有的浏览器还是会一同发送Cookie。这时,能够显式封闭withCredentials

xhr.withCredentials = false;

需求留意的是,假如要发送Cookie,Access-Control-Allow-Origin就不能设为星号,有必要指定明确的、与恳求网页共同的域名。一同,Cookie仍然遵循同源方针,只要用服务器域名设置的Cookie才会上传,其他域名的Cookie并不会上传,且(跨源)原网页代码中的document.cookie也无法读取服务器域名下的Cookie。

(2)非简略恳求

预检恳求

非简略恳求是那种对服务器有特殊要求的恳求,比如恳求办法是PUT或DELETE,或许Content-Type字段的类型是application/json

非简略恳求的CORS恳求,会在正式通讯之前,添加一次HTTP查询恳求,称为”预检”恳求(preflight)。

浏览器先询问服务器,当时网页所在的域名是否在服务器的答应名单之中,以及能够运用哪些HTTP动词和头信息字段。只要得到必定答复,浏览器才会宣布正式的XMLHttpRequest恳求,否则就报错。

下面是一段浏览器的JavaScript脚本。

var url = 'http://api.wang.com/cors';
var xhr = new XMLHttpRequest();
xhr.open('PUT', url, true);
xhr.setRequestHeader('X-Custom-Header', 'value');
xhr.send();

上面代码中,HTTP恳求的办法是PUT,并且发送一个自定义头信息X-Custom-Header

浏览器发现,这是一个非简略恳求,就【自动】宣布一个”预检”恳求,要求服务器承认能够这样恳求。下面是这个”预检”恳求的HTTP头信息。

OPTIONS /cors HTTP/1.1
Origin: http://api.bob.com
Access-Control-Request-Method: PUT
Access-Control-Request-Headers: X-Custom-Header
Host: api.ydlclass.com
Accept-Language: en-US
Connection: keep-alive
User-Agent: Mozilla/5.0...

“预检”恳求用的恳求办法是OPTIONS,标明这个恳求是用来询问的。头信息里边,关键字段是Origin,标明恳求来自哪个源。

除了Origin字段,”预检”恳求的头信息包含两个特殊字段:

  • (1)Access-Control-Request-Method。该字段是有必要的,用来列出浏览器的CORS恳求会用到哪些HTTP办法,上例是PUT
  • (2)Access-Control-Request-Headers。该字段是一个逗号分隔的字符串,指定浏览器CORS恳求会额外发送的头信息字段,上例是X-Custom-Header

预检恳求的呼应

服务器收到”预检”恳求今后,检查了OriginAccess-Control-Request-MethodAccess-Control-Request-Headers字段今后,承认答应跨源恳求,就能够做出回应。

 HTTP/1.1 200 OK
Date: Mon, 01 Dec 2008 01:15:39 GMT
Server: Apache/2.0.61 (Unix)
Access-Control-Allow-Origin: http://api.bob.com
Access-Control-Allow-Methods: GET, POST, PUT
Access-Control-Allow-Headers: X-Custom-Header
Content-Type: text/html; charset=utf-8
Content-Encoding: gzip
Content-Length: 0
Keep-Alive: timeout=2, max=100
Connection: Keep-Alive
Content-Type: text/plain

上面的HTTP回应中,关键的是Access-Control-Allow-Origin字段,标明http://api.bob.com能够恳求数据。该字段也能够设为’*‘,标明赞同恣意跨源恳求。

Access-Control-Allow-Origin: *

假如服务器否定了”预检”恳求,会回来一个正常的HTTP回应,可是没有任何CORS相关的头信息字段。这时,浏览器就会认定,服务器不赞同预检恳求,因此触发一个过错,被XMLHttpRequest目标的onerror回调函数捕获。控制台会打印出如下的报错信息。

XMLHttpRequest cannot load http://api.wang.com.
Origin http://api.bob.com is not allowed by Access-Control-Allow-Origin.

服务器回应的其他CORS相关字段如下:

Access-Control-Allow-Methods: GET, POST, PUT
Access-Control-Allow-Headers: X-Custom-Header
Access-Control-Allow-Credentials: true
Access-Control-Max-Age: 1728000

(1)Access-Control-Allow-Methods

该字段必需,它的值是逗号分隔的一个字符串,标明服务器支撑的一切跨域恳求的办法。留意,回来的是一切支撑的办法,而不单是浏览器恳求的那个办法。这是为了避免多次”预检”恳求。

(2)Access-Control-Allow-Headers

假如浏览器恳求包含Access-Control-Request-Headers字段,则Access-Control-Allow-Headers字段是必需的。它也是一个逗号分隔的字符串,标明服务器支撑的一切头信息字段,不限于浏览器在”预检”中恳求的字段。

(3)Access-Control-Allow-Credentials

该字段与简略恳求时的含义相同。

(4)Access-Control-Max-Age

该字段可选,用来指定本次预检恳求的有效期,单位为秒。上面成果中,有效期是20天(1728000秒),即答应缓存该条回应1728000秒(即20天),在此期间,不用宣布另一条预检恳求。

四、跨域问题处理

首先想到的便是运用过滤器进行统一的处理,当然在单个的servlet或许controller中也能够单独处理,根本的逻辑便是在呼应的首部信息中加入需求的首部信息字段,处理方案如下:

public class CORSFilter implements Filter{
    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
    }
    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        HttpServletResponse response = (HttpServletResponse) servletResponse;
        response.setHeader("Access-Control-Allow-Origin", "*");
        response.setHeader("Access-Control-Allow-Methods", "POST, GET");
        response.setHeader("Access-Control-Max-Age", "3600");
        response.setHeader("Access-Control-Allow-Headers", "Content-Type, Access-Control-Allow-Headers, Authorization, X-Requested-With");
        filterChain.doFilter(servletRequest, servletResponse);
    }
    @Override
    public void destroy() {
    }
}

对api为前缀的恳求都进行处理:

<!-- CORS Filter -->
<filter>
    <filter-name>CORSFilter</filter-name>
    <filter-class>com.ydlclass.filter.CORSFilter</filter-class>
</filter>
<filter-mapping>
    <filter-name>CORSFilter</filter-name>
    <url-pattern>/api/*</url-pattern>
</filter-mapping>

到这里,就能够简略的完成 CORS 跨域恳求了,上面的过滤器将会为一切恳求的呼应加上Access-Control-Allow-*首部,换言之便是答应来自恣意源的恳求来拜访该服务器上的资源。而在实践开发中能够依据需求敞开跨域恳求权限以及控制呼应头部等等。

springmvc提供了更加简略的处理方案

在Controller 上运用 @CrossOrigin 注解就能够完成跨域,这个注解是一个类级别也是办法级别的注解:

@CrossOrigin(maxAge = 3600)
@RestController 
@RequestMapping("goods")
public class GoodsController{
}

假如一同在 Controller 和办法上都有运用@CrossOrigin 注解,那么在详细某个办法上的 CORS 特点将是两个注解特点兼并的成果,假如特点的设置发生冲突,那么Controller 上的主机特点将被掩盖。

也能够运用装备类进行大局的装备:

@Configuration
@EnableWebMvc
public class WebConfig extends WebMvcConfigurerAdapter {
    @Override
    public void addCorsMappings(CorsRegistry registry) {
        registry.addMapping("/api/**")
            .allowedOrigins("*")
            .allowedMethods("PUT", "DELETE")
            .allowedHeaders("header1", "header2", "header3")
            .exposedHeaders("header1", "header2")
            .allowCredentials(false).maxAge(3600);
    }
}

基于 XML 装备文件与上等效:

<mvc:cors>
  <mvc:mapping path="/api/**"
        allowed-origins="*"
        allowed-methods="GET, PUT"
        allowed-headers="header1, header2, header3"
        exposed-headers="header1, header2" allow-credentials="false"
        max-age="123" />
    <mvc:mapping path="/resources/**"
        allowed-origins="http://domain1.com" />
</mvc:cors>

跋文

本文以上呢为我们对 【SpringMVC教程】跨域问题 的相关内容进行了介绍。当一个恳求url的协议、域名、端口三者之间恣意一个与当时页面url不一同,就会产生跨域。那么终究什么是跨域,跨域问题该怎么处理,本文详细对同源战略什么是跨域简略恳求与非简略恳求跨域问题处理等进行了翔实介绍~

Java全栈学习道路可参阅: 【Java全栈学习道路】最全的Java学习道路及常识清单,Java自学方向指引,内含最全Java全栈学习技能清单~

算法刷题道路可参阅: 算法刷题道路总结与相关材料共享,内含最翔实的算法刷题道路攻略及相关材料共享~

最全面的SpringMVC教程——跨域问题