精华内容
下载资源
问答
  • 2020-08-27 06:55:19

    跨域

    当一个资源向与本身所在服务器不同的域或者端口发起请求时,会发起一个跨域HTTP请求。

    CORS

    CORS全称Cross-Origin Resource Sharing,也就是我们常说的跨域资源共享,CORS是通过新增一组HTTP头部字段,允许服务器声明那些源站有权限访问哪些资源。

    CORS的标准规范要求可能对服务器数据产生副作用的HTTP请求方法,浏览器必须首先使用OPTIONS方法发起预检请求,如果服务器允许该跨域请求后,才可以发起HTTP请求。再预检请求的返回中,服务器可以要求客户端携带身份凭证信息。

    简单请求

    简单请求,顾名思义是相对简单的请求,满足一下条件的请求被称为简单请求。

    • 请求方法
      • GET
      • POST
      • HEATD
    • 字段
      • Accept
      • Accept-Language
      • Content-Language
      • Content-Type
    • Content-Type 的值
      • text/plain
      • multipart/form-data
      • application/x-www-form-urlencoded
        这些跨域请求和其他跨域请求没有区别,如果服务器没有返回正确的相应头,请求方不会收到任何数据,因此,那些不允许跨域请求的网站无需为这一新的HTTP控制特效担心。

    预检请求

    预检请求要求必须首先使用 OPTONS 方法发起一个预检请求到服务器,以获取服务器是否允许该实际请求。同样的他也需要满足一定的要求:

    • 请求方法
      • PUT
      • DELETE
      • CONNECT
      • OPTIONS
      • TRACE
      • PATH
    • 首部字段
      • Accept
      • Accept-Language
      • Content-Language
      • Content-Type
    • Content-Type 的值
      • text/plain
      • multipart/form-data
      • application/x-www-form-urlencoded

    预检请求可以避免跨域请求对服务器的用户数据产生未预期的影响。

    认证请求

    XMLHttpRequest 与 CORS 的一个有趣的特性是,可以基于 HTTP cookies 和 HTTP 认证信息发送身份凭证。一般而言,对于跨域 XMLHttpRequest 请求,浏览器不会发送身份凭证信息。如果要发送凭证信息,需要设置 XMLHttpRequest 的某个特殊标志位。

    xmlHttpRequest.withCredentials = true
    

    withCredentials 标志设置为 true,从而向服务器发送 Cookies。因为这是一个简单 GET 请求,所以浏览器不会对其发起 “预检请求”。但是,如果服务器端的响应中未携带 Access-Control-Allow-Credentials: true ,浏览器将不会把响应内容返回给请求的发送者。

    服务端

    只在客户端下足功夫也不一定可以解决跨域问题,我们也需要在服务端上设置Access-Control-Allow-Origin响应头,当然我们也可以设置如Access-Control-Allow- Methods,Access-Control-Allow-Headers来告诉客户端同不同意他的请求。

    更多相关内容
  • Golang解决跨域问题

    千次阅读 2021-02-24 20:21:05
    使用CORS解决跨域问题2.1 简单请求2.2 非简单请求2.3 配置CORS以解决跨域问题3.Golang解决跨域拦截 1.什么是跨域 由于浏览器的同源策略限制,进而产生跨域拦截问题。同源策略是浏览器最核心也最基本的安全功能;所谓...

    1.什么是跨域

    由于浏览器的同源策略限制,进而产生跨域拦截问题。同源策略是浏览器最核心也最基本的安全功能;所谓同源(即指在同一个域)就是两个页面具有相同的协议(protocol),主机(host)和端口号(port)。

    同源策略(Same origin policy)是一种约定,它是浏览器最核心也最基本的安全功能,如果缺少了同源策略,则浏览器的正常功能可能都会受到影响。可以说Web是构建在同源策略基础之上的,浏览器只是针对同源策略的一种实现。

    同源策略分为两种:

    • DOM 同源策略:禁止对不同源页面 DOM 进行操作。这里主要场景是 iframe 跨域的情况,不同域名的 iframe 是限制互相访问的。
    • XMLHttpRequest 同源策略:禁止使用 XHR 对象向不同源的服务器地址发起 HTTP 请求。

    同源策略在解决浏览器访问安全的同时,也带来了跨域问题,当一个请求url的协议域名端口三者之间任意一个与当前页面url不同即为跨域。

    2.使用CORS解决跨域问题

    CORS(Cross-origin resource sharing,跨域资源共享)是一个 W3C 标准,定义了在必须访问跨域资源时,浏览器与服务器应该如何沟通。CORS 背后的基本思想,就是使用自定义的 HTTP 头部让浏览器与服务器进行沟通,从而决定请求或响应是应该成功,还是应该失败。CORS 需要浏览器和服务器同时支持。
     
    整个CORS通信过程,都是浏览器自动完成,不需要用户参与。对于开发者来说,CORS通信与同源的AJAX通信没有差别,代码完全一样。浏览器一旦发现AJAX请求跨源,就会自动添加一些附加的头信息,有时还会多出一次附加的请求,但用户不会有感觉。

    浏览器将CORS请求分成两类:简单请求(simple request)和非简单请求(not-so-simple request)。

    2.1 简单请求

    简单请求是指满足下面两大条件的请求:

    • 请求方法为 HEAD、GET、POST中的一种。
    • HTTP头信息不超过一下几种:
      • Accept
      • Accept-Language
      • Content-Language
      • Last-Event-ID
      • Content-Type(只限于三个值application/x-www-form-urlencoded、multipart/form-data、text/plain)

    对于简单请求,浏览器回自动在请求的头部添加一个 Origin 字段来说明本次请求来自哪个源(协议 + 域名 + 端口),服务端则通过这个值判断是否接收本次请求。如果 Origin 在许可范围内,则服务器返回的响应会多出几个头信息:

    Access-Control-Allow-Credentials: true
    Access-Control-Allow-Headers: Content-Type, Content-Length
    Access-Control-Allow-Origin: *
    Content-Type: text/html; charset=utf-8
    

    实际上后续我们就是通过配置这些参数来处理跨域请求的。

    2.2 非简单请求

    非简单请求是那种对服务器有特殊要求的请求,比如请求方法是 PUTDELETE ,或者 Content-Type 字段的类型是 application/json

    非简单请求的CORS请求,会在正式通信之前,增加一次HTTP查询请求,称为"预检"请求(preflight),预检请求其实就是我们常说的 OPTIONS 请求,表示这个请求是用来询问的。头信息里面,关键字段 Origin ,表示请求来自哪个源,除 Origin 字段,"预检"请求的头信息包括两个特殊字段:

    //该字段是必须的,用来列出浏览器的CORS请求会用到哪些HTTP方法
    Access-Control-Request-Method
    //该字段是一个逗号分隔的字符串,指定浏览器CORS请求会额外发送的头信息字段.
    Access-Control-Request-Headers
    

    在这里插入图片描述
    浏览器先询问服务器,当前网页所在的域名是否在服务器的许可名单之中,以及可以使用哪些HTTP动词和头信息字段。只有得到肯定答复,浏览器才会发出正式的 XMLHttpRequest 请求,否则就报错。

    2.3 配置CORS以解决跨域问题

    上述介绍了两种跨域请求,其中出现了几种特殊的 Header 字段,CORS 就是通过配置这些字段来解决跨域问题的:

    Access-Control-Allow-Origin
    

    该字段是必须的。它的值要么是请求时Origin字段的值,要么是一个*,表示接受任意域名的请求

    Access-Control-Allow-Methods
    

    该字段必需,它的值是逗号分隔的一个字符串,表明服务器支持的所有跨域请求的方法。注意,返回的是所有支持的方法,而不单是浏览器请求的那个方法。这是为了避免多次"预检"请求。

    Access-Control-Allow-Headers
    

    如果浏览器请求包括Access-Control-Request-Headers字段,则Access-Control-Allow-Headers字段是必需的。它也是一个逗号分隔的字符串,表明服务器支持的所有头信息字段,不限于浏览器在"预检"中请求的字段。

    Access-Control-Expose-Headers
    

    该字段可选。CORS请求时,XMLHttpRequest对象的response只能拿到6个基本字段:Cache-ControlContent-LanguageContent-TypeExpiresLast-ModifiedPragma。如果想拿到其他字段,就必须在Access-Control-Expose-Headers里面指定。

    Access-Control-Allow-Credentials
    

    该字段可选。它的值是一个布尔值,表示是否允许发送Cookie。默认情况下,Cookie不包括在CORS请求之中。设为true,即表示服务器明确许可,Cookie可以包含在请求中,一起发给服务器。这个值也只能设为 true,如果服务器不要浏览器发送Cookie,删除该字段即可。

    Access-Control-Max-Age
    

    该字段可选,用来指定本次预检请求的有效期,单位为秒,在此期间,不用发出另一条预检请求。

    3.Golang解决跨域拦截

    Gin框架为例,配置处理跨域的中间件:

    func Cors(context *gin.Context) {
    	method := context.Request.Method
    	// 必须,接受指定域的请求,可以使用*不加以限制,但不安全
    	//context.Header("Access-Control-Allow-Origin", "*")
    	context.Header("Access-Control-Allow-Origin", context.GetHeader("Origin"))
    	fmt.Println(context.GetHeader("Origin"))
    	// 必须,设置服务器支持的所有跨域请求的方法
    	context.Header("Access-Control-Allow-Methods", "POST, GET, PUT, DELETE, OPTIONS")
    	// 服务器支持的所有头信息字段,不限于浏览器在"预检"中请求的字段
    	context.Header("Access-Control-Allow-Headers", "Content-Type, Content-Length, Token")
    	// 可选,设置XMLHttpRequest的响应对象能拿到的额外字段
    	context.Header("Access-Control-Expose-Headers", "Access-Control-Allow-Headers, Token")
    	// 可选,是否允许后续请求携带认证信息Cookir,该值只能是true,不需要则不设置
    	context.Header("Access-Control-Allow-Credentials", "true")
    	// 放行所有OPTIONS方法
    	if method == "OPTIONS" {
    		context.AbortWithStatus(http.StatusNoContent)
    		return
    	}
    	context.Next()
    }
    

    注意:上述context.Header("Access-Control-Allow-Origin", "*")如果将Access-Control-Allow-Origin设置为*时存在一个问题是不允许XMLHttpRequest携带Cookie,所以要实现通配的话可以采用动态获取Origin,即context.GetHeader("Origin")的方式。

    原生的HTTP包也是类似操作响应头即可。

    展开全文
  • 一、jsonp解决跨域 jsonp解决跨域问题的原理是:script不受同源策略的影响。 //前端代码: <!DOCTYPE html> <html lang="cn"> <head> <meta charset="UTF-8"> <meta ...
    跨域是前端再常见不过的问题了,下面主要针对跨域做一次总结,一次理清楚。
    

    一、jsonp解决跨域

    jsonp解决跨域问题的原理是:script不受同源策略的影响。
    
    //前端代码:
    <!DOCTYPE html>
    <html lang="cn">
    <head>
      <meta charset="UTF-8">
      <meta http-equiv="X-UA-Compatible" content="IE=edge">
      <meta name="viewport" content="width=device-width, initial-scale=1.0">
      <title>Document</title>
    </head>
    <body>
    
      <script>
        function callback(res) {
          console.log(res)
        }
      </script>
      <script src="http://127.0.0.1:3000?fn=callback"></script>
      
    </body>
    </html>
    
    //服务器代码
    var http = require('http');
    var server = http.createServer();
    var qs = require("querystring")
    
    server.on("request", (req, res) => {
      let params = qs.parse(req.url.split("?")[1])
      let obj = {name:"张三", age :20}
      res.end(params.fn + "(" + JSON.stringify(obj) +")")
    })
    
    server.listen(3000, () => {
      console.log("服务器已打开")
    })
    
    jsonp实现跨域:前端通过script标签将函数以参数的形式传递给后台,后台通过解
    析请求的字符串,拿到该函数,然后让该函数以执行的状态返回给前端,并且在其中
    传入数据。
    不过jsonp请求只能支持get请求方式。
    

    二、document.domain + iframe实现跨域

    此方案只适用于主域相同,子域不同的页面实现跨域。
    
    父窗口的地址为 http://www.fordmc.com/a.html
    <body>
    	<iframe src="http://ioc.fordmc.com/b.html"></iframe>
    	<script>
    		document.domain = "fordmc.com"  
    		var name = "哈哈哈"
    	</script>
    </body>
    
    子窗口的地址是 http://ioc.fordmc.com/b.html
    <body>
    	<script>
    		document.domain = "fordmc.com"
    		console.log(window.parent.name)   //哈哈哈
    	</script>
    </body>
    
    总结:document.domain + iframe实现跨域原理是,当子域名不一致的情况下,
    需要在父级和子级都需要使用document.domain强制设置基础主域名。然后在父级
    中使用iframe将子集引入,这样在子集就能拿到父级的数据了。
    

    三、location.hash + iframe实现跨域

    存在两个页面(a, b),如果a,b页面同域,则可以直接进行数据通信。如果a, b不同
    域时,可以通过location.hash + iframe来实现跨域数据传输。
    
    a页面所在的域为:http://www.domain1.com/a.html
    a页面的代码为:
    <body>
    	<iframe src="http://www.domain2.com/b.html"></iframe>
    	<script>
    		let iframe = document.querySelect("iframe")
    		setTimeout(() => {
    			iframe.src = iframe.src + "#user=admin"
    		})
    	</script>
    </body>
    
    <body>
    	<script>
    		window.onhashchange = function(){
    			console.log(location.hash)  //#user=admin
    		}	
    	</script>
    </body>
    
    总结:上面两个页面处于不同的域中,在a页面中使用iframe引入了b页面,通过改
    变b页面的hash值,来实现将数据传递给b页面的目的。同样b页面使用
    onhashchange监听函数,监听hash值的改变。本例中hash值为user=admin,这样
    就能拿到这个字符串,然后最终在b页面中解析,最终实现数据跨域传输。
    

    四、window.name + iframe实现跨域

    window.name存在这样一个特点就是在不同的页面下,或者是在不同的域下面其值都
    是存在的,并且name的长度非常长(2MB)
    
    a页面的地址是:http://www.domain1.com/a.html
    <body>
    	<script>
          var proxy = function (url, callback) {
          var state = 0;
          var iframe = document.createElement('iframe');
    
          // 加载跨域页面
          iframe.src = url;
          // onload事件会触发2次,第1次加载跨域页,并留存数据于window.name
          iframe.onload = function () {
            if (state === 1) {
              // 第2次onload(同域proxy页)成功后,读取同域window.name中数据
              callback(iframe.contentWindow.name);
              destoryFrame();
    
            } else if (state === 0) {
              // 第1次onload(跨域页)成功后,切换到同域代理页面
              iframe.contentWindow.location = 'http://www.domain1.com/proxy.html';
              state = 1;
            }
          };
    
          document.body.appendChild(iframe);
    
          // 获取数据以后销毁这个iframe,释放内存;这也保证了安全(不被其他域frame js访问)
          function destoryFrame() {
            iframe.contentWindow.document.write('');
            iframe.contentWindow.close();
            document.body.removeChild(iframe);
          }
        };
    
        // 请求跨域b页面数据
        proxy('http://www.domain2.com/b.html', function (data) {
          console.log(data)
        });
    	</script>
    </body>
    
    b页面的地址为:http://www.domain1.com/b.html
    中间代理页,与a.html同域,内容为空即可。
    
    c页面的地址是:http://www.domain2.com/c.html
    
    <script>
    	window.name = "张三"
    </script>
    
    总结:window.name + iframe实现的思路:本例中需要在a页面的中访问到c页面
    的数据,但是由于跨域,无法进行访问。此时设置一个代理b页面,起到桥梁的作
    用。首先在a页面中设置iframe并且首先将其的src值设置为页面c的地址。由于
    iframe需要执行两次onload,此时我们可以在iframe第一次加载之后,改变其src
    为页面b的地址。这样iframe上的window.name了,当改变src时,window.name也
    不会改变。这样a和b就是同源的了,访问数据就不受限制了。最后需要销毁iframe。
    

    五、postMessage实现跨域

    postMessage是html5中新增的api,是window属性中位数不多的可以实现跨域的。
    其存在两个参数,一个是发送的数据(使用JSON.stringify()来格式化一下)
    第二个参数是:origin,表示为主机 + 协议 + 端口,"*"表示向全部的端口发送,
    "/"表示向当前窗口同源的窗口发送。
    
    a页面的地址为:http://www.domain1.com/a.html
    
    <body>
    	<iframe src="http://www.domain2.com/b.html">
    	<script>
    		let iframe = document.querySelector("iframe")
    		iframe.onload = function(){
    			let obj = { name:"张三",age:20 }
    			
    				iframe.contentWindow.postMessage(JSON.stringify(obj),"http://www.domain2.com")
    				//向domain2发送数据
    		}
    		 //监听domain2发送来的数据
    		window.addEventListener("message", function(data) {
    			console.log(data)   //"data"
    		})
    	</script>
    </body>
    
    b页面的地址是:http://www.domain2.com/b.html
    	<script>
    		window.addEventListener("message", function(data){
    			console.log(data)  //{name:"张三",age:20}
    			window.parent.postMessage("data", "http://www.domain1.com")
    		})	
    	</script>
    

    六、跨域资源共享(CORS)

    目前跨域使用CORS比较多,如果不需要携带cookie,则只需要在服务端设置Access-control-Allow-Origin即可,如果需要携带cookie,则见下文。
    
    前端设置
    1、原生的ajax
    // 前端设置是否带cookie
    xhr.withCredentials = true;
    
    2、实例代码
    var xhr = new XMLHttpRequest(); // IE8/9需用window.XDomainRequest兼容
    
    // 前端设置是否带cookie
    xhr.withCredentials = true;
    
    xhr.open('post', 'http://www.domain2.com:8080/login', true);
    xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
    xhr.send('user=admin');
    
    xhr.onreadystatechange = function() {
        if (xhr.readyState == 4 && xhr.status == 200) {
            alert(xhr.responseText);
        }
    };
    
    node后台进行设置
    var http = require('http');
    var server = http.createServer();
    var qs = require('querystring');
    
    server.on('request', function(req, res) {
        var postData = '';
    
        // 数据块接收中
        req.addListener('data', function(chunk) {
            postData += chunk;
        });
    
        // 数据接收完毕
        req.addListener('end', function() {
            postData = qs.parse(postData);
    
            // 跨域后台设置
            res.writeHead(200, {
                'Access-Control-Allow-Credentials': 'true',     // 后端允许发送Cookie
                'Access-Control-Allow-Origin': 'http://www.domain1.com',    // 允许访问的域(协议+域名+端口)
                /* 
                 * 此处设置的cookie还是domain2的而非domain1,因为后端也不能跨域写cookie(nginx反向代理可以实现),
                 * 但只要domain2中写入一次cookie认证,后面的跨域接口都能从domain2中获取cookie,从而实现所有的接口都能跨域访问
                 */
                'Set-Cookie': 'l=a123456;Path=/;Domain=www.domain2.com;HttpOnly'  // HttpOnly的作用是让js无法读取cookie
            });
    
            res.write(JSON.stringify(postData));
            res.end();
        });
    });
    
    server.listen('8080');
    console.log('Server is running at port 8080...');
    
    展开全文
  • 基于最新Spring 5.x,详细介绍了跨域的概念,以及Spring项目如何通过CORS的方法来解决跨域问题。

    基于最新Spring 5.x,详细介绍了跨域的概念,以及Spring项目中如何通过CORS的方法来解决跨域问题。

    本次我们来学习跨域的概念,以及Spring项目中如何通过CORS的方法来解决跨域问题,这是一种非常常见且简单的解决跨域问题的方法。

    Spring MVC学习 系列文章

    Spring MVC学习(1)—MVC的介绍以及Spring MVC的入门案例

    Spring MVC学习(2)—Spring MVC中容器的层次结构以及父子容器的概念

    Spring MVC学习(3)—Spring MVC中的核心组件以及请求的执行流程

    Spring MVC学习(4)—ViewSolvsolver视图解析器的详细介绍与使用案例

    Spring MVC学习(5)—基于注解的Controller控制器的配置全解【一万字】

    Spring MVC学习(6)—Spring数据类型转换机制全解【一万字】

    Spring MVC学习(7)—Validation基于注解的声明式数据校验机制全解【一万字】

    Spring MVC学习(8)—HandlerInterceptor处理器拦截器机制全解

    Spring MVC学习(9)—项目统一异常处理机制详解与使用案例

    Spring MVC学习(10)—文件上传配置、DispatcherServlet的路径配置、请求和响应内容编码

    Spring MVC学习(11)—跨域的介绍以及使用CORS解决跨域问题

    1 同源和跨域

    什么是同源?请求的“协议+ip(域名)+端口”被称之为“源”,如果当前页面的url和在当前页面中要访问的url具有相同的源,那么我们称这两个请求“同源”,即它们来自或者去往同一个服务器。

    什么是跨域?如果当前页面的url和请求的url非同源,那么我们就可以称之为跨域,也称之为跨源!或者说,当请求url的协议、域名、端口三者之间任意一个与当前页面url不同即为跨域!非同源和跨域,可以说是一种事务的不同说法!

    假设某个页面的URL为http://store.company.com/dir/page.html,下面是常见的跨域(非同源)请求的类型:

    请求的URL是否跨域(非同源)原因
    http://store.company.com/dir2/other.html只有访问的资源路径不同
    http://store.company.com/dir/inner/another.html只有访问的资源路径不同
    https://store.company.com/dir/page.html协议不同
    http://store.company.com:81/dir/page.html端口不同 ( http:// 默认端口是80)
    http://news.company.com/dir/page.html主机(域名)不同

    什么是同源策略?同源策略(Same origin policy)是一种约定,它是浏览器最核心也最基本的安全功能,它的目的,是为了保证用户信息的安全,防止恶意的网站窃取数据。如果缺少了同源策略,则浏览器的正常功能可能都会受到影响,很容易受到 XSS、 CSFR 等攻击。

    同源策略对跨域(非同源)请求进行了如下限制:

    1. 无法读取非同源网页的 Cookie、LocalStorage 和IndexedDB;当然如果两个网页一级域名相同,只是次级域名不同,那么Cookie可以通过设置相同的domainName来实现共享Cookie。
    2. 无法获取非同源网页的 DOM树和Js对象。
    3. 无法获取非同源地址返回的响应,例如通过XMLHttpRequest发送的AJAX请求,以及<img>标签请求的非同源图片资源,获取想要加载的各种网络字体、样式资源。

    同源策略的限制通常是浏览器自己来实现的,当同源策略触发时:

    1. 对于某些浏览器,可能已经正常发送了跨域请求,甚至服务器已经正常处理了请求并且返回了响应,只不过在处理结果时浏览器做了限制,导致无法获取服务器响应的结果!
    2. 对于另一些浏览器以及某些请求方式,它会首先发送一个OPTIONS 预检请求到服务器,以获知服务器是否允许该实际请求,当服务器响应允许此次跨域时,才会发送真正的请求并且正确的处理响应结果。"预检请求“的使用,可以避免跨域请求对服务器的用户数据产生未预期的影响。

    2 模拟跨域

    下面是一个没有解决跨域问题的web项目的Controller:

    @RestController
    public class AccessControlController {
        
        @GetMapping("/accessControl/{id}")
        public User accessControl(@PathVariable Long id, String name) {
            System.out.println(id);
            System.out.println(name);
            return new User(id, name);
        }
    }
    

    在Chrome浏览器中,当我们直接在浏览器中输入URL访问/accessControl/123?name=test时,是没问题的:

    在这里插入图片描述

    如果我们将此url放到另一个非同源的url页面中通过AJAX来访问时,就会出现跨域问题!

    我们的js脚本如下:

    var xhr = new XMLHttpRequest();
    xhr.open('GET', 'http://localhost:8081/mvc/accessControl/123?name=test',true);
    xhr.send();
    xhr.onreadystatechange=function() {
        if(xhr.readyState == 4) {
            if(xhr.status == 200) {
              console.log(JSON.parse(xhr.responseText));
            }
        }
    }
    

    想要调试,非常简单,对于在Chrome浏览器中来说,我们首先随便打开一个非同源的网页,比如说百度首页https://www.baidu.com/,然后F12 检查,切换到Console,即可在这里在线执行js脚本!

    输入js脚本并执行,结果如下:

    在这里插入图片描述

    可以看到,控制台出现了如下报错:

    Access to XMLHttpRequest at 'http://localhost:8081/mvc/accessControl/123?name=test' from origin 'https://www.baidu.com' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource.
    

    这个异常描述为响应缺少Access-Control-Allow-Origin头字段,这个字段我们下面会介绍!但是我们在后端服务器控制台能够看到正常输出:

    在这里插入图片描述

    这说明,实际上请求已经被发送了,并且服务器正常处理了,但是响应结果被浏览器的同源策略拦截了!通常,当前端页面的控制台输出上面的异常信息时,就是说明遇到跨域问题了!

    3 解决跨域

    同源策略虽然带来一定的安全性,但是这往往给一些正规的跨域需求带来不便,特别是前后端分离以及服务化的项目,通常不同的服务之间是不同源但,但是它们之间需要互相调用那个。为此,我们可以采取一些方式来绕过同源策略!

    解决的方案有很多,比如JSONP、CORS、WebSocket、Nginx反向代理等。

    1. JSONP是一种只需要前端处理的比较简单的方法,但是只能支持get请求,不支持其他类型的请求,因此用的不多,这里不赘述。但是JSONP的优势在于支持某些老式浏览器,以及可以向不支持CORS的网站请求数据。
    2. WebSocket则是另一种基于HTTP升级而来的协议,它不实行同源策略,只要服务器支持,就可以通过它进行跨域通信。但是WebSocket协议的前后端代码开发和普通HTTP开发有很大区别,通常是被用于即时通信或者要求服务器实时推送数据的项目中,普通web项目为了解决跨域问题而使用WebSocket是得不偿失的!
    3. Nginx反向代理,也是一种非常有效的解决跨域问题的方法,用的也非常多,但是需要Nginx服务器的相关知识,我们后面会专门讲解!

    本次我们要学习的就是Spring MVC基于CORS来解决跨域问题,这也是Spring官网推荐的方式,值得学习:https://docs.spring.io/spring-framework/docs/5.2.8.RELEASE/spring-framework-reference/web.html#mvc-cors

    4 CORS概述

    CORS(Cross-origin resource sharing),通俗地译为跨域资源共享,但是我认为更准确的叫法是跨源资源共享,CORS是一个W3C标准,是一种基于HTTP头的机制,该机制通过允许服务器标示除了它自己以外的其它origin(源,包括域、协议和端口),这样浏览器可以访问加载这些资源

    CORS需要浏览器和服务器同时支持。目前,所有浏览器都支持该功能,IE浏览器不能低于IE10。整个CORS通信过程,都是浏览器自动完成,不需要用户参与。对于开发者来说,CORS通信与同源的AJAX通信没有差别,代码完全一样。浏览器一旦发现AJAX请求跨源,就会自动添加一些附加的头信息,有时还会多出一次附加的OPTIONS“预检”请求,但用户不会有感觉。因此,实现CORS通信的关键是服务器。

    CORS可以看作是跨源AJAX请求的一种根本解决方法。相比JSONP只能发GET请求,CORS允许任何类型的请求。

    5 CORS标准

    WordC为CORS标准新增了一组 HTTP 首部字段,以允许服务器在响应头中通过这些字段声明和控制哪些源站通过浏览器有权限访问哪些资源。另外,规范要求,对那些可能对服务器数据产生副作用的 HTTP 请求方法(特别是 GET 以外的 HTTP 请求,或者搭配某些 MIME 类型的 POST 请求),浏览器必须首先使用 OPTIONS 方法发起一个预检请求(preflight request),从而获知服务端是否允许该跨源请求。服务器确认允许之后,才发起实际的 HTTP 请求。在预检请求的返回中,服务器端也可以通知客户端,是否需要携带身份凭证(包括 Cookies 和 HTTP 认证相关数据)。

    CORS请求失败会产生错误,但是为了安全,在JavaScript代码层面是无法获知到底具体是哪里出了问题。你只能查看浏览器的控制台以得知具体是哪里出现了错误。

    不同的请求场景下,CORS有不同的处理规范。CORS将请求分成两类:简单请求(simple request)和非简单请求(not-so-simple request)

    若请求满足所有下述条件,则该请求可视为“简单请求”:

    1. 请求方法: GET、HEAD、POST三者之一;
    2. 首部字段:除了被用户代理自动设置的首部字段(例如host、connection、user-agent等)之外,其他请求首部字段只能是如下字段:Accept、Accept-Language、Content-Language、Last-Event-ID、Content-Type。
    3. Content-Type的值只限于application/x-www-form-urlencoded、multipart/form-data、text/plain。

    不同浏览器的实现可能有细微的区别!

    5.1 简单请求处理

    对于简单非同源请求,浏览器不会先发出预检请求,而是直接发出CORS请求

    我们最初始的模拟跨域的测试案例中,发出的AJAX请求并没有定义其他头部信息,这就属于简单请求,我们切换到Chrome浏览器的Network页面,查看请求头信息如下:

    GET /mvc/accessControl/123?name=test HTTP/1.1
    Host: localhost:8081
    Connection: keep-alive
    User-Agent: Mozilla/5.0 (Linux; Android 6.0; Nexus 5 Build/MRA58N) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/88.0.4324.104 Mobile Safari/537.36
    Accept: */*
    Origin: https://www.baidu.com
    Sec-Fetch-Site: cross-site
    Sec-Fetch-Mode: cors
    Sec-Fetch-Dest: empty
    Referer: https://www.baidu.com/
    Accept-Encoding: gzip, deflate, br
    Accept-Language: zh-CN,zh;q=0.9
    

    简单CORS请求和普通请求的区别就是会在请求首部添加了一个origin字段,该字段用来说明,本次请求来自哪个源(协议 + 域名 + 端口)。在上面头的信息中Origin为https://www.baidu.com,说明本次请求来源于百度的页面!

    服务器根据请求头信息中的Origin值决定是否同意这次请求。如果Origin指定的源,不在许可范围内,服务器会直接返回一个正常的HTTP响应。浏览器接受响应之后,会判断如果响应头信息中没有包含一个名为Access-Control-Allow-Origin字段,就直接抛出一个异常,被XMLHttpRequest的onerror回调函数捕获,并且请求发起方无法获取正常的响应结果。注意,这种错误无法通过状态码识别,因为HTTP回应的状态码有可能是200,通常这种错误在浏览器控制台查看!

    在这里插入图片描述

    在模拟跨域的案例中,服务器没有进行CORS处理,因此返回的响应头中没有包含Access-Control-Allow-Origin字段:

    在这里插入图片描述

    Access-Control-Allow-Origin头部字段是服务器返回的,它的值用于指示服务器允许的域,返回表示允许所有外域访问!如果响应存在该字段,但是值不是“”也不是当前的页面源,那么请求方同样无法收到响应结果。

    如果服务器仅允许来自http://localhost:8081的源访问,首部字段的内容如下:

    Access-Control-Allow-Origin: http://localhost:8081
    

    使用 Origin 和 Access-Control-Allow-Origin 字段就能完成最简单的自定义跨域访问控制,这也是为什么说CORS是基于HTTP头的机制!

    如果服务器允许外部来源访问,那么响应头信息可能如下:

    HTTP/1.1 200 OK
    Server: Apache-Coyote/1.1
    Vary: Origin
    Vary: Access-Control-Request-Method
    Vary: Access-Control-Request-Headers
    Access-Control-Allow-Origin: https://www.baidu.com
    Access-Control-Allow-Credentials: true
    Content-Type: application/json;charset=UTF-8
    Transfer-Encoding: chunked
    Date: Tue, 02 Feb 2021 07:57:22 GMT
    

    另一个响应头Access-Control-Allow-Credentials是可选的,它的值是一个布尔值,表示是否允许发送Cookie。默认情况下,Cookie不包括在CORS请求之中。设为true,即表示服务器明确许可,Cookie可以包含在请求中,一起发给服务器。这个值也只能设为true,如果服务器不要浏览器发送Cookie,那么该响应中就没有该字段。

    5.1.1 附带身份凭证信息

    对于跨源的XMLHttpRequest 或 Fetch 请求,浏览器默认不包含Cookie信息以及HTTP认证信息等身份凭证信息。如果需要发送凭证信息(比如Cookie),首先需要服务器统一,指定响应头包括Access-Control-Allow-Credentials字段。

    另外,前端开发者还需要在AJAX中打开withCredentials属性:

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

    否则,即使服务器同意发送Cookie,浏览器也不会发送。或者,服务器要求设置Cookie,浏览器也不会处理。但是,如果省略withCredentials设置,有的浏览器还是会一起发送Cookie。这时,可以将withCredentials设置为false,以显式关闭。

    需要注意的是,如果要发送附带身份凭证的请求,响应中的Access-Control-Allow-Origin字段就不能设为“*”,必须指定明确的与请求源网页一致的域名(如上案例,Allow-Origin只能是https://www.baidu.com)。

    同时,Cookie依然遵循同源政策,只有用服务器域名设置的Cookie才会上传,其他域名的Cookie并不会上传,且(跨源)原网页代码中的document.cookie也无法读取服务器域名下的Cookie。

    5.2 非简单请求处理

    对于非简单非同源请求,比如PUT、DELETE请求,或者Content-Type字段的类型是application/json等请求,浏览器不会直接发出真实请求,而是先发出一次OPTIONS预检(preflight)请求,该请求是浏览器控制的,对于用户和前端开发人员来说是无感的!也就是说,对于非简单请求,将会分为两个请求发送,第一个是预检请求,通过之后才会发送真实请求

    浏览器先询问服务器,当前网页所在的域名是否在服务器的许可名单之中,以及可以使用哪些HTTP方法和请求头字段。只有得到肯定答复,并且和当前请求匹配,浏览器才会发出正式的XMLHttpRequest请求,否则同样报错,并且不会发出真实请求。

    要测试预检请求,也很简单,我们在AJAX中随便加一个请求头即可:

    var xhr = new XMLHttpRequest();
    xhr.open('GET', 'http://localhost:8081/mvc/accessControl/123?name=test',true); 
    xhr.setRequestHeader('xx', 'yy');     //添加一个请求头
    xhr.send();
    xhr.onreadystatechange=function() {
        if(xhr.readyState == 4) {
            if(xhr.status == 200) {
              console.log(JSON.parse(xhr.responseText));
            }
        }
    }
    

    Chrome浏览器中再次发送,请求预检请求的头信息如下:

    OPTIONS /mvc/accessControl/123?name=test HTTP/1.1
    Host: localhost:8081
    Connection: keep-alive
    Accept: */*
    Access-Control-Request-Method: GET
    Access-Control-Request-Headers: xx
    Origin: https://www.baidu.com
    User-Agent: Mozilla/5.0 (Linux; Android 6.0; Nexus 5 Build/MRA58N) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/88.0.4324.104 Mobile Safari/537.36
    Sec-Fetch-Mode: cors
    Sec-Fetch-Site: cross-site
    Sec-Fetch-Dest: empty
    Referer: https://www.baidu.com/
    Accept-Encoding: gzip, deflate, br
    Accept-Language: zh-CN,zh;q=0.9
    

    可以看到,预检请求和正式请求时统一路径,但是请求方法是OPTIONS,OPTIONS 是 HTTP/1.1 协议中定义的方法,用以从服务器获取更多信息。OPTIONS方法不会对服务器资源产生影响,不会执行控制器方法中的业务代码!

    请求头中同样有一个origin字段表示请求来源!除此之外,新携带了两个首部字段:

    access-control-request-method: GET
    access-control-request-headers: xx
    
    1. Access-Control-Request-Method:必须的字段,用于告知服务器,实际CORS请求将使用的GET方法。
    2. Access-Control-Request-Headers非必须的字段,用于告知服务器,实际请求将携带一个自定义请求首部字段名:xx。如果存在多个字段,则使用“,”分隔。

    服务器据此决定该实际请求是否被允许!服务器在收到预检请求之后,如果没有进行特殊CORS处理,那么便不会返回特殊的响应头,浏览器收到响应之后边抛出异常,不再发送后续真实的请求。

    如果服务器进行了CORS处理并且通过了校验,那么返回的响应头信息可能如下:

    HTTP/1.1 200 OK
    Server: Apache-Coyote/1.1
    Vary: Origin
    Vary: Access-Control-Request-Method
    Vary: Access-Control-Request-Headers
    Access-Control-Allow-Origin: https://www.baidu.com
    Access-Control-Allow-Methods: GET
    Access-Control-Allow-Headers: xx
    Access-Control-Allow-Credentials: true
    Access-Control-Max-Age: 1800
    Allow: GET, HEAD, POST, PUT, DELETE, TRACE, OPTIONS, PATCH
    Content-Length: 0
    Date: Tue, 02 Feb 2021 07:41:46 GMT
    

    其中包括几个特殊字段:

    Access-Control-Allow-Origin: https://www.baidu.com
    Access-Control-Allow-Methods: GET
    Access-Control-Allow-Headers: xx
    Access-Control-Allow-Credentials: true
    Access-Control-Max-Age: 1800
    
    1. Access-Control-Allow-Origin:必须的字段,服务器允许的域,允许所有域设置为 *。
    2. Access-Control-Allow-Methods: 服务器允许的请求方法,允许所有方法设置为*。
    3. Access-Control-Allow-Headers:如果请求包括Access-Control-Request-Headers字段,则响应的Access-Control-Allow-Headers字段是必需的。表明服务器允许添加的请求头字段,采用“,”分隔,不限于浏览器在预检请求中传递的字段。
    4. Access-Control-Allow-Credentials:处理方式与简单请求时一致。
    5. Access-Control-Max-Age: 该预检请求响应的有效时间,单位秒时。在有效时间内,浏览器无须为同一请求再次发起预检请求。浏览器自身同样维护了一个最大有效时间,如果该首部字段的值超过了最大有效时间,将不会生效。

    如果通过了预检请求,那么继续发送真实请求,并且在预检响应的有效时间内,浏览器无须为同一请求再次发起预检请求。后续的请求就是简单请求一样,只需要Origin和Access-Control-Allow-Origin字段即可!

    5.3 总结首部信息

    根据上面的介绍,我们总结一下CORS的解决过程中的关键首部字段。

    请求首部

    字段名描述
    Origin表明发送预检请求或发送实际请求的源地址。所有的CORS请求中Origin 首部字段总是被发送。
    Access-Control-Request-Method用于预检请求,用于将实际请求所使用的HTTP方法告诉服务器。
    Access-Control-Request-Headers用于预检请求,用于将实际请求所携带的自定义首部字段告诉服务器。

    响应首部

    字段名描述
    Access-Control-Allow-Origin必定返回的字段。指定允许访问该资源的外域 URI。对于不需要携带身份凭证的请求,服务器可以指定该字段的值为通配符“”,表示允许来自所有域的请求。如果服务端指定了具体的域名而非“”,那么响应首部中的 Vary 字段的值必须包含 Origin。这将告诉客户端:服务器对不同的源站返回不同的内容。
    Access-Control-Max-Age指定了预检(preflight)请求的结果能够被缓存多久,单位秒。
    Access-Control-Allow-Credentials是否允许请求中携带身份认证信息,比如Cookie。该功能还需要XMLHttpRequest的credentials设置为true。
    Access-Control-Allow-Methods用于预检请求的响应,指明了实际请求所允许使用的 HTTP 方法。
    Access-Control-Allow-Headers用于预检请求的响应,指明了实际请求所允许携带的自定义首部字段。
    Access-Control-Expose-Headers让服务器把允许浏览器访问的头放入白名单,多个头采用“,”分隔。在跨源访问时,XMLHttpRequest对象的getResponseHeader()方法只能拿到一些最基本的响应头,Cache-Control、Content-Language、Content-Type、Expires、Last-Modified、Pragma,如果要访问其他头,则需要服务器设置本响应头。

    6 CORS实现

    根据上面的CORS标准,我们知道,除了携带身份认证信息的请求之外,其他简单或者复杂请求的跨源控制都是由后端来实现的。下面我们来看看基于Spring MVC的Web项目是如果通过CORS来实现跨源访问控制的!

    Spring MVC的HandlerMapping的实现为CORS提供了内置支持。在成功将request映射到handler处理器后,HandlerMaping 实现将检查给定request和handler的 CORS 配置信息,随后,将会直接处理预检请求,同时拦截、验证简单和实际 CORS 请求,并设置所需的(配置的)CORS响应头信息。

    为了启用跨源请求(如果存在Origin请求并且与当前请求的主机不一致,就被认为是跨源请求),我们需要具有一些显式声明的 CORS 配置。如果未找到匹配的 CORS 配置,则预检请求将直接被拒绝,并且不会向简单和实际 CORS 请求的响应中添加 CORS 头,因此在浏览器收到响应会拒绝响应结果,并在控制台抛出异常。

    每个HandlerMapping都可以使用基于URL模式的 CorsConfiguration映射单独配置。在大多数情况下,web程序使用 MVC JavaConfig或 XML文件来声明这些映射规则,这会导致单个全局映射传递将给所有 HandlerMaping 实例。

    我们可以将HandlerMapping级别的全局CORS配置与更细粒度的handler级别的CORS配置相结合使用。例如,基于注解的Controller控制器可以使用类或方法级的@CrossOrigin注解来为某些或者某个接口单独定义CORS配置(其他类型的handler可以实现 CorsConfigurationSource接口来配置)。

    注意,如果我们具有全局和本地的CORS配置,那么对于只能接受单个值的属性(如allowCredentials和 maxAge),本地值将覆盖全局值,对于其他接受多个值的属性,比如origins,则全局和本地的CORS配置值会组合在一起(java中是list集合)!

    6.1 @CrossOrigin注解配置

    @CrossOrigin注解用于在基于注解的控制器方法上启用跨源请求,如下例所示:

    /**
     * @author lx
     */
    @RestController
    public class AccessControlController {
    
        @CrossOrigin
        @GetMapping("/accessControl/{id}")
        public User accessControl(@PathVariable Long id, String name) {
            System.out.println(id);
            System.out.println(name);
            return new User(id, name);
        }
    }
    

    启动项目再次测试,无论是发送简单跨源请求还是复杂跨源请求,都可以成功获得响应:

    在这里插入图片描述

    默认情况下,@CrossOrigin注解表示允许:所有的origins(源)、所有的headers(请求头字段)、控制器方法映射到的所有 HTTP 方法。

    默认情况下,allowedCredentials属性为false,也就是不允许附带身份凭证信息,比如Cookie和CSRF token,通常手动设置为true。

    默认情况下,maxAge被设置为30分钟。

    @CrossOrigin还支持标注在类上,并且由所有控制器方法将继承该配置,并且同样遵循优先级以及复合规则:对于单个字段的配置,方法上的@CrossOrigin注解优先级更高,方法和类上的@CrossOrigin注解的值将组合(java中是list集合),如下所示:

    /**
     * @author lx
     */
    @RestController
    public class AccessControlController {
    
        @CrossOrigin
        @GetMapping("/accessControl/{id}")
        public User accessControl(@PathVariable Long id, String name, HttpEntity httpEntity) {
            System.out.println(httpEntity.getHeaders());
            System.out.println(id);
            System.out.println(name);
            return new User(id, name);
        }
    }
    

    6.1.1 简单原理

    类以及方法上的@CrossOrigin注解,在RequestMappingHandlerMapping被实例化之后的afterPropertiesSet回调方法中(RequestMappingHandlerMapping实现了InitializingBean接口),随着处理器方法HandlerMethod的解析被一并解析。

    每解析到一个处理器方法,会尝试为当前HandlerMethod初始化一个CorsConfiguration,其内部保存了当前方法的CORS配置:

    public class CorsConfiguration {
        //……
    
        /**
         * Access-Control-Allow-Origin
         */
        private List<String> allowedOrigins;
        /**
         * Access-Control-Allow-Methods
         */
        private List<String> allowedMethods;
        /**
         * Access-Control-Allow-Headers
         */
        private List<String> allowedHeaders;
        /**
         * Access-Control-Expose-Headers
         */
        private List<String> exposedHeaders;
        /**
         * Access-Control-Allow-Credentials
         */
        private Boolean allowCredentials;
        /**
         * Access-Control-Max-Age
         */
        private Long maxAge;
    
        //……
    }
    

    配置CorsConfiguration时(RequestMappingHandlerMapping#initCorsConfiguration方法),会尝试获取当前方法以及方法所属的类上的@CrossOrigin注解,随后会对他们进行合并:

    在这里插入图片描述

    随后会将控制器方法的HandlerMethod与对应的CorsConfiguration缓存起来(AbstractHandlerMethodMapping#register方法):

    在这里插入图片描述

    这样,在后续请求到来的时候,就可以根据CorsConfiguration的配置进行CORS控制了!

    当请求到来时,Spring 中对 CORS 规则的校验委托给DefaultCorsProcessor的processRequest方法,而DefaultCorsProcessor在RequestMappingHandlerMapping被实例化时一起实例化的(位于AbstractHandlerMapping中)。

    在这里插入图片描述

    那么DefaultCorsProcessor具体是怎么调用的呢,对于预检请求实际上使用PreFlightHandler作为handler来调用的,对于其他请求则是通过HandlerInterceptor 拦截器CorsInterceptor来进行调用的,并且它位于拦截器链的首位:

    在这里插入图片描述

    对于预检请求,PreFlightHandler的handler仅仅调用DefaultCorsProcessor的processRequest方法而没有处理器方法的调用,这样就避免了业务代码的执行,而对于其他请求,在handler方法调用之前则会执行拦截器首位的CorsInterceptor,拦截器内部的preHandle方法中同样会调用DefaultCorsProcessor的processRequest方法来判断CORS,如果不通过,那么preHandle返回false,后续handler方法也就不再执行,同样避免了由于CORS不通过但是业务代码执行的问题!

    注意,如果没有配置CorsConfiguration(无论是全局的还是局部的),或者请求不是预检请求,那么即使是CORS请求到来时,业务代码会按照正常请求流程来执行,也就不会有CORS校验,虽然浏览器不会呈现最终结果!

    DefaultCorsProcessor的processRequest方法逻辑为:

    1. 判断是否是有效的CORS请求,如果request已经存在Origin请求头并且和当前服务器非同源,那么是有效的CORS请求,继续该方法的后续CORS判断,否则算作CORS校验通过,继续后续的方法处理!
    2. 如果response存在Access-Control-Allow-Origin响应头,那么表示该请求已被处理过了,算作CORS校验通过,继续后续的方法处理!否则,继续后续CORS判断。
    3. 如果CorsConfiguration为null
    4. 如果是预检请求,那么设置403响应码并返回,表示预检失败,服务器拒绝该请求;如果不是预检请求,算作CORS校验通过,继续后续的方法处理!
    5. 如果CorsConfiguration不为null,那么通过配置的CORS校验该请求头中的CORS信息,主要是校验origin 、method header是否合法(受支持)。如果全部合法,则在 response中添加响应头字段,并继续后面的处理(比如handler方法调用),如果不合法,则设置403响应码并返回,表示预检失败,服务器拒绝该请求。

    6.2 全局配置

    除了细粒度、控制器方法级别配置外,我们更多的可能还希望定义一些全局CORS配置。我们可以在任何处HandlerMapping上单独设置基于URL的CorsConfiguration映射。但是,大多数应用程序使用 MVC JavaConfig或 MVC XML配置来做到这一点。

    默认情况下,全局配置表示允许:所有的origins(源),所有的headers(请求头字段),GET、HEAD和POST方法。

    默认情况下,allowedCredentials属性为false,也就是不允许附带身份凭证信息,比如Cookie和CSRF token,通常手动设置为true。

    默认情况下,maxAge被设置为30分钟。

    6.2.1 Java配置

    采用JavaConfig的配置方式非常简单,我们只需要实现WebMvcConfigurer接口并且,实现addCorsMappings回调方法即可,在方法中即可通过参数配置CORS信息!

    /**
     * @author lx
     */
    @Configuration
    @EnableWebMvc
    public class WebConfig implements WebMvcConfigurer {
    
        /**
         * 配置跨源请求处理。
         */
        @Override
        public void addCorsMappings(CorsRegistry registry) {
            //为指定的路径(模式)启用跨源请求处理。
            //默认情况下,此映射的CORS配置采用CorsConfiguration.applyPermitDefaultValue()的配置
            //即允许所有的origins(源),所有的headers(请求头字段),所有的GET、HEAD和POST方法。
            registry.addMapping("/api/**")
                    //设置允许访问该资源的外域源,这里是所有
                    .allowedOrigins("*")
                    //设置实际请求所允许使用的 HTTP 方法。这里是所有
                    .allowedMethods("*")
                    //设置实际请求所允许携带的自定义首部字段。这里是所有
                    .allowedHeaders("*")
                    //设置是否允许请求中携带身份认证信息,比如Cookie。这里是允许
                    .allowCredentials(true);
        }
    
    }
    

    6.2.2 XML配置

    若要在Spring的XML配置文件中启用CORS,可以使用<mvc:cors>标签:

    <mvc:cors>
        <!--配置第一个CORS映射处理规则-->
        <mvc:mapping path="/api/**"
                     allow-credentials="true"
                     allowed-headers="*"
                     allowed-methods="*"
                     allowed-origins="*"
                     max-age="1800"/>
        <!--配置第二个CORS映射处理规则-->
        <mvc:mapping path="/service/**"/>
        <!--  ……………… -->
    </mvc:cors>
    

    6.3 CORS Filter

    我们也可以通过Servlet中的Filter来实现CORS的支持,此前的老项目都是自己实现CorsFilter。Spring MVC 4.2之后,已经内置了CorsFilter的实现,我们无需自己定义,只需要配置即可。

    首先,我们需要配置CorsFilter的bean:

    @Bean
    public CorsFilter corsFilter() {
        CorsConfiguration config = new CorsConfiguration();
        // 使用默认CORS参数
        // config.applyPermitDefaultValues()
        config.setAllowCredentials(true);
        config.addAllowedOrigin("*");
        config.addAllowedHeader("*");
        config.addAllowedMethod("*");
        config.setMaxAge(1800L);
        UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
        source.registerCorsConfiguration("/**", config);
        return new CorsFilter(source);
    }
    

    随后我们需要在web.xml中配置DelegatingFilterProxy来代理配置的CorsFilter的调用,不能直接在web.xml中配置Spring MVC的CorsFilter!

    <!-- 通过DelegatingFilterProxy来代理Spring管理的CorsFilter bean-->
    <filter>
        <filter-name>corsFilter</filter-name>
        <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
        <!--设置是否调用代理目标bean上的 Filter.init 和 Filter.destroy的生命周期方法。-->
        <init-param>
            <param-name>targetFilterLifecycle</param-name>
            <param-value>true</param-value>
        </init-param>
        <!--DelegatingFilterProxy的filter-name应该和Spring管理的Filter的beanName一致-->
        <!--如果不一致,需要配置targetBeanName参数指向Spring管理的Filter的beanName-->
        <init-param>
            <param-name>targetBeanName</param-name>
            <param-value>corsFilter</param-value>
        </init-param>
    </filter>
    <filter-mapping>
        <filter-name>corsFilter</filter-name>
        <url-pattern>/*</url-pattern>
    </filter-mapping>
    

    CorsFilter只是 Spring MVC Java 配置CORS和 XML配置CORS的替代方案,仅适用于仅依赖于spring-web(不依赖spring-webmvc上)的应用程序,或者对于要求在Filter级别执行 CORS 检查的安全约束。实际用的并不多!

    另外,如果需要使用Spring Security,那么Spring Security内置了CorsFilter的支持!

    相关文章:

    1. https://spring.io/
    2. Spring Framework 5.x 学习
    3. Spring Framework 5.x 源码

    如有需要交流,或者文章有误,请直接留言。另外希望点赞、收藏、关注,我将不间断更新各种Java学习博客!

    展开全文
  • 后端项目部署到服务器,并设置允许跨域访问后,本地前端项目使用服务器上后端项目接口时,问题来了:首先,使用postman测试获取图片验证码接口和验证图片验证码接口,正常。然后,在html使用获取图片验证码接口,...
  •   在前端领域跨域是指浏览器允许向服务器发送跨域请求,从而克服Ajax只能同源使用的限制。 什么是同源策略?   同源策略是一种约定,由Netscape公司1995年引入浏览器,它是浏览器最核心也最基本的安全...
  • 解决这个问题,可以从两方面入手,一种方案是在微服务各自的业务模块实现,即在SpringBoot层实现,另外一种方案就是在Gateway层实现。 首先讲一下在SpringBoot层实现的三种方案。 解决方案一:在Controller上添加
  • 什么是跨域跨域解决方法

    万次阅读 多人点赞 2018-12-14 19:22:02
    一、为什么会出现跨域问题 出于浏览器的同源策略限制。同源策略(Sameoriginpolicy)是一种约定,它是浏览器最核心也最基本的安全功能,如果缺少了同源策略,则浏览器的正常功能可能都会受到影响。可以说Web是构建...
  • 前端本地开发如何靠自己解决跨域

    千次阅读 2021-12-20 12:24:52
    哈哈 但是作者有自己的态度,基本都是自己解决这个问题,俗话说自己动手 丰衣足食呀。废话就不多说了,让我们来先了解跨域是如何产生的。 1.什么是跨域跨域:指的是浏览器不能执行其他网站的脚本。它是由浏览器的...
  • 什么是跨域问题?平时遇到跨域问题应该怎么解决,希望对大家有帮助
  • 什么是跨域跨域是指一个域下的文档或脚本试图去请求另一个域下的资源,这里跨域是广义的。 广义的跨域: 1、资源跳转:A链接、重定向、表单提交 2、资源嵌入:<link>、<script>、<img>、&...
  • Java文件上传实例并解决跨域问题

    千次阅读 多人点赞 2021-09-03 20:06:44
    本文内容为Java文件上传实例并解决跨域问题,其中重点讲解了文件上传,MultipartFile接口的使用,配置nginx,以及解决文件上传跨域的问题
  • 在前端领域跨域是指浏览器允许向服务器发送跨域请求,从而克服Ajax只能同源使用的限制。 什么是同源策略? 同源策略是一种约定,由Netscape公司1995年引入浏览器,它是浏览器最核心也最基本的安全功能,如果...
  • 可以说web时构建在同源策略的基础上的,浏览器只是针对同源策略的一种实现。 同源策略会阻止一个域的javascript脚本和另一个域的内容进行交互。 所谓同源(即指在同一个域),就是两个页面具有相同的协议(protool)...
  • springcloud gatway网关解决跨域问题,开发时,遇到application.yml,设置如下,但我在实际开发不生效: spring: cloud: gateway: globalcors: corsConfigurations: '[/**]': allowedOrigins: "*" ...
  • 跨域解决方法

    2021-05-09 14:09:04
    可以说Web是构建在同源策略基础之上的,浏览器只是针对同源策略的一种实现。同源策略会阻止一个域的javascript脚本和另外一个域的内容进行交互。所谓同源(即指在同一个域)就是两个页面具有相同的协议(protocol)...
  • jsonp解决跨域

    2020-12-29 11:13:17
    前后端交互 — jsonp 知识要点 跨域解决 jsonp原理及封装 jsonp服务器搭建 jsonp实际运用 ajax问题 ...同源策略是浏览器的一个...可以让网页从别的域名(网站)那获取资料,即跨域读取数据。 jsonp原理 通过script
  • 在项目载入axios 项目根目录下 : npm install axios 1、创建api文件夹 2、http.js写入 import axios from 'axios' import router from '../router' axios.defaults.timeout = 5000 // 超时时间5秒 ...
  • vueaxios解决跨域问题和拦截器使用

    万次阅读 多人点赞 2017-11-23 10:41:57
    所以有两种方法可以解决这点:第一种: 在main.js引入axios,然后将其设置为vue原型链上的属性,这样在组件就可以直接 this.axios使用了 import axios from 'axios'; Vue.prototype.axios=axios;components:...
  • 什么叫跨域 一、跨域: 指浏览器不能执行其他网站的脚本,也就是说需要保持同源策略,那么问题又来了啥又叫 同源策略 呢??? 二、同源策略: 同源策略是指 域名,协议,端口号必须相同 ,如有一个不同那么就会跨域...
  • 就比如跨域,新人或者刚接触的人对它并不是那么熟悉,所以说列出一些自己积累的方案,以及一些常用的场景来给他人带来一些解决问题的思路,这件事是有意义的。(写完之后还发现真香。以后忘了还能回来看..
  • 10 种跨域解决方案(附终极方案)

    千次阅读 2021-08-11 00:22:17
    写在前面嗯。又来了,又说到跨域了,这是一个老生常谈的话题,以前我觉得这种基础文章没有什么好写的,会想着你去了解底层啊,不是很简单吗。但是最近在开发一个 「vscode 插件」 发现,当你刚...
  • 一、什么是跨域跨域是指一个域下的文档或脚本试图去请求另一个域下的资源,这里跨域是广义的。 广义的跨域: 1.) 资源跳转: A链接、重定向、表单提交 2.) 资源嵌入: < link >、< script >、< ...
  • 解决跨域问题的n种办法

    千次阅读 2021-03-17 14:12:25
    今天我们来聊聊跨域的事情,那我们先从浏览器的报错开始吧! 见到这个报错信息,那么就要恭喜你,遇到了跨域这个磨人的小妖精了。...当协议、域名、端口任意一个不相同时,都算作跨域。需注意即使两个
  • 单点登录是要求多域名 下共用一套登录的逻辑和数据,这个时候肯定会出现跨域问题 刚开始解决这个问题的方法是 用window.open打开的新窗口进行等待用户登录操作,原窗口进行 ajax轮询服务端接口 判断用户是否已登录...
  • 跨域问题及解决方法

    2021-03-02 20:08:02
    跨域问题及解决方法 13、跨域问题:什么是跨域解决方案 1)跨域简介 跨域是指跨域名的访问,以下情况都属于跨域跨域原因说明 示例 域名不同 www.jd.com 与 www.taobao.com 域名相同,端口不同 ...
  • 文件上传在项目是必不可少的一个环节。一般而言,只要有一个上传页面就可以了 图片直接上传到网站下面。但是当项目较多,或者项目太大 有好多需要上传文件的需求时,一个通用的文件上传组件就很有必要了。而且我...
  • 资源嵌入: link、script、img、frame等dom标签,还有样式background:url()、@font-face()等文件外链 脚本请求: js发起的ajax请求、dom和js对象的跨域操作等 其实我们通常所说的跨域是狭义的,是...
  • 1,cors,服务器在响应头添加access control allow origin字段,浏览器在收到请求之后就会认为本次请求时允许跨域的。 2,JSONP,浏览器使用创建script标签的形式发送请求,将一个函数名作为请求参数发给服务器,...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 10,640
精华内容 4,256
关键字:

form中的path可以解决跨域么