HTTP访问控制(CORS)——预检请求问题的解决

1:CORS介绍

CORS (Cross-Origin Resource Sharing,跨域资源共享)是一个系统,它由一系列传输的HTTP头组成,这些HTTP头决定浏览器是否阻止前端 JavaScript 代码获取跨域请求的响应。
同源安全策略 默认阻止“跨域”获取资源。但是 CORS 给了web服务器这样的权限,即服务器可以选择,允许跨域请求访问到它们的资源。

CORS 头
Access-Control-Allow-Origin
指示请求的资源能共享给哪些域。
Access-Control-Allow-Credentials
指示当请求的凭证标记为 true 时,是否响应该请求。
Access-Control-Allow-Headers
用在对预请求的响应中,指示实际的请求中可以使用哪些 HTTP 头。
Access-Control-Allow-Methods
指定对预请求的响应中,哪些 HTTP 方法允许访问请求的资源。
Access-Control-Expose-Headers
指示哪些 HTTP 头的名称能在响应中列出。
Access-Control-Max-Age
指示预请求的结果能被缓存多久。
Access-Control-Request-Headers
用于发起一个预请求,告知服务器正式请求会使用那些 HTTP 头。
Access-Control-Request-Method
用于发起一个预请求,告知服务器正式请求会使用哪一种 HTTP 请求方法。
Origin
指示获取资源的请求是从什么域发起的。
  • 跨域资源共享(CORS) 是一种机制,它使用额外的 HTTP 头来告诉浏览器 让运行在一个 origin (domain) 上的Web应用被准许访问来自不同源服务器上的指定的资源。当一个资源从与该资源本身所在的服务器不同的域、协议或端口请求一个资源时,资源会发起一个跨域 HTTP 请求。
    在这里插入图片描述

比如,站点 http://domain-a.com 的某 HTML 页面通过 的 src 请求 http://domain-b.com/image.jpg。网络上的许多页面都会加载来自不同域的CSS样式表,图像和脚本等资源。

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

并不一定是浏览器限制了发起跨站请求,也可能是跨站请求可以正常发起,但是返回结果被浏览器拦截了。)

在这里插入图片描述

跨域资源共享( CORS )机制允许 Web 应用服务器进行跨域访问控制,从而使跨域数据传输得以安全进行。现代浏览器支持在 API 容器中(例如 XMLHttpRequest 或 Fetch )使用 CORS,以降低跨域 HTTP 请求所带来的风险。

2:什么情况下需要 CORS

跨域资源共享标准( cross-origin sharing standard )允许在下列场景中使用跨域 HTTP 请求:

  • 前文提到的由 XMLHttpRequest 或 Fetch 发起的跨域 HTTP 请求。
  • Web 字体 (CSS 中通过 @font-face 使用跨域字体资源), 因此,网站就可以发布 TrueType 字体资源,并只允许已授权网站进行跨站调用。
  • WebGL 贴图
  • 使用 drawImage 将 Images/video 画面绘制到 canvas

3:跨域资源共享机制功能概述

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

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

接下来的内容将讨论相关场景,并剖析该机制所涉及的 HTTP 首部字段。

3.1 若干访问控制场景案例演示

这里,有三个场景来解释跨域资源共享机制的工作原理。这些例子都使用 XMLHttpRequest 对象。分别为简单请求,预检请求,我们在这里只讲预检请求(小编只学到这个目前,哈哈哈)

3.1.1 预检请求

“需预检的请求”要求必须首先使用 OPTIONS 方法发起一个预检请求到服务器,以获知服务器是否允许该实际请求。"预检请求“的使用,可以避免跨域请求对服务器的用户数据产生未预期的影响
如下是一个需要执行预检请求的 HTTP 请求:

var invocation = new XMLHttpRequest();
var url = 'http://bar.other/resources/post-here/';
var body = '<?xml version="1.0"?><person><name>Arun</name></person>';
    
function callOtherDomain(){
    
    
  if(invocation)
    {
    
    
      invocation.open('POST', url, true);
      invocation.setRequestHeader('X-PINGOTHER', 'pingpong');
      invocation.setRequestHeader('Content-Type', 'application/xml');
      invocation.onreadystatechange = handler;
      invocation.send(body); 
    }
}
......

上面的代码使用 POST 请求发送一个 XML 文档,该请求包含了一个自定义的请求首部字段(X-PINGOTHER: pingpong)。另外,该请求的 Content-Type 为 application/xml。因此,该请求需要首先发起“预检请求”。

若请求满足所有下述条件,则该请求可视为“简单请求”:否则就是预检请求,如上Content-Type为application/xml不满足下面的条件,则就是预检请求

  • 使用下列方法之一:
    GET
    HEAD
    POST
  • 除了被用户代理自动设置的首部字段(例如 Connection ,User-Agent)和在 Fetch 规范中定义为 禁用首部名称 的其他首部,允许人为设置的字段为 Fetch 规范定义的 对 CORS 安全的首部字段集合。该集合为:
    Accept
    Accept-Language
    Content-Language
    Content-Type (需要注意额外的限制)
    DPR
    Downlink
    Save-Data
    Viewport-Width
    Width
  • Content-Type 的值仅限于下列三者之一:
    text/plain
    multipart/form-data
    application/x-www-form-urlencoded
  • 请求中的任意XMLHttpRequestUpload 对象均没有注册任何事件监听器;XMLHttpRequestUpload 对象可以使用 XMLHttpRequest.upload 属性访问。
  • 请求中没有使用 ReadableStream 对象。

在这里插入图片描述
如下对预检请求的发送,响应一系列过程进行讲解

1:OPTIONS /resources/post-here/ HTTP/1.1
Host: bar.other
User-Agent: Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10.5; en-US; rv:1.9.1b3pre) Gecko/20081130 Minefield/3.1b3pre
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: en-us,en;q=0.5
Accept-Encoding: gzip,deflate
Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7
Connection: keep-alive
Origin: http://foo.example
Access-Control-Request-Method: POST:
12:Access-Control-Request-Headers: X-PINGOTHER, Content-Type


14: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://foo.example
Access-Control-Allow-Methods: POST, GET, OPTIONS
Access-Control-Allow-Headers: X-PINGOTHER, Content-Type
Access-Control-Max-Age: 86400
Vary: Accept-Encoding, Origin
Content-Encoding: gzip
Content-Length: 0
Keep-Alive: timeout=2, max=100
Connection: Keep-Alive:
26:Content-Type: text/plain

预检请求完成之后,发送实际请求:

POST /resources/post-here/ HTTP/1.1
Host: bar.other
User-Agent: Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10.5; en-US; rv:1.9.1b3pre) Gecko/20081130 Minefield/3.1b3pre
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: en-us,en;q=0.5
Accept-Encoding: gzip,deflate
Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7
Connection: keep-alive
X-PINGOTHER: pingpong
Content-Type: text/xml; charset=UTF-8
Referer: http://foo.example/examples/preflightInvocation.html
Content-Length: 55
Origin: http://foo.example
Pragma: no-cache
Cache-Control: no-cache

<?xml version="1.0"?><person><name>Arun</name></person>


HTTP/1.1 200 OK
Date: Mon, 01 Dec 2008 01:15:40 GMT
Server: Apache/2.0.61 (Unix)
Access-Control-Allow-Origin: http://foo.example
Vary: Accept-Encoding, Origin
Content-Encoding: gzip
Content-Length: 235
Keep-Alive: timeout=2, max=99
Connection: Keep-Alive
Content-Type: text/plain

[Some GZIP'd payload]

浏览器检测到,从 JavaScript 中发起的请求需要被预检。从上面的报文中,我们看到,第 1~12 行发送了一个使用 OPTIONS 方法的“预检请求”。 OPTIONS 是 HTTP/1.1 协议中定义的方法,用以从服务器获取更多信息。该方法不会对服务器资源产生影响。 预检请求中同时携带了下面两个首部字段:

Access-Control-Request-Method: POST
Access-Control-Request-Headers: X-PINGOTHER, Content-Type

首部字段 Access-Control-Request-Method 告知服务器,实际请求将使用 POST 方法。首部字段 Access-Control-Request-Headers 告知服务器,实际请求将携带两个自定义请求首部字段:X-PINGOTHER 与 Content-Type。服务器据此决定,该实际请求是否被允许。

第14~26 行为预检请求的响应,表明服务器将接受后续的实际请求。重点看第 17~20 行:

Access-Control-Allow-Origin: http://foo.example
Access-Control-Allow-Methods: POST, GET, OPTIONS
Access-Control-Allow-Headers: X-PINGOTHER, Content-Type
Access-Control-Max-Age: 86400

首部字段 Access-Control-Allow-Methods 表明服务器允许客户端使用 POST, GET 和 OPTIONS 方法发起请求。 该字段与 HTTP/1.1 Allow: response header 类似,但仅限于在需要访问控制的场景中使用。

首部字段 Access-Control-Allow-Headers 表明服务器允许请求中携带字段 X-PINGOTHER 与 Content-Type。与 Access-Control-Allow-Methods 一样,Access-Control-Allow-Headers 的值为逗号分割的列表。

最后,首部字段 Access-Control-Max-Age 表明该响应的有效时间为 86400 秒,也就是 24 小时。在有效时间内,浏览器无须为同一请求再次发起预检请求。请注意,浏览器自身维护了一个最大有效时间,如果该首部字段的值超过了最大有效时间,将不会生效。

预检请求与重定向
大多数浏览器不支持针对于预检请求的重定向。如果一个预检请求发生了重定向,浏览器将报告错误:

在这里插入图片描述

4:实际问题中解决预检请求案例演示

我们在演示人人开源管理系统时,我们还用到了网关,这时会报如下错误,当点击登录人人开源后台会报如下错误,这就是不可以进行跨域请求,因为域名,端口,路径都不一样
在这里插入图片描述

我们检查网页,点击network,点击登录按钮,会出现两个报错的login路径(红色按钮)
如下可以按着上面预检请求的篇章进行分析
跨域请求出错的两个login路径如下
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
根据上面预检请求篇的分析得到下面这张图
在这里插入图片描述
那我们可以怎么解决呢?有两种方式,推荐使用第二种(我们直接在响应运行跨域的时候,在网关里配置文件,告述请求端可以发送真实请求)
在这里插入图片描述
在这里插入图片描述
我们只需在网关中写一个配置文件如下即可

@Configuration
public class GulimallCorsConfiguration {
    
    

    @Bean
    public CorsWebFilter corsWebFilter(){
    
    
        UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();

        CorsConfiguration corsConfiguration = new CorsConfiguration();

        //1、配置跨域
        corsConfiguration.addAllowedHeader("*");
        corsConfiguration.addAllowedMethod("*");
        corsConfiguration.addAllowedOrigin("*");
        corsConfiguration.setAllowCredentials(true);

        source.registerCorsConfiguration("/**",corsConfiguration);
        return new CorsWebFilter(source);
    }
}

再次重新登录人人开源管理后台,再次检查连个login出现如下,说明成功解决跨域请求问题
在这里插入图片描述
在这里插入图片描述

猜你喜欢

转载自blog.csdn.net/qq_44891295/article/details/107189738