18

跨站资源共享CORS原理深度解析

 3 years ago
source link: https://segmentfault.com/a/1190000037599314
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

bmIjaeF.png!mobile

我相信如果你写过前后端分离的web应用程序,或者写过一些ajax请求调用,你可能会遇到过CORS错误。

  • CORS是什么?
  • 它与安全性有关吗?
  • 为什么要有CORS?它解决了什么目的?
  • CORS是怎样运行的?

如果您有这些问题,那么这篇文章非常适合您。

一、什么是CORS?

要了解什么是CORS(Cross-Origin Resource Sharing:跨站资源共享),首先我们需要了解什么是同源策略 Same Origin Policy (SOP)。SOP是所有的现代浏览器都具备的安全措施,它不允许从一个加载的js脚本和资源的Origin域与另一个Origin域进行交互。换句话说,如果您的网站是 www.example.com ,则您无法向 www.test.com 发出XHR请求。

那么SOP有什么用?如果没有同源策略的限制,你想想会发生什么?比如:您已经登录到微博,并且不小心打开了一个恶意网站。该网站可以向微博发出请求,并从您微博登录的会话中提取个人信息。这显然是巨大的安全问题,为了防止这种情况,在浏览器中实施同源策略的限制。实际上,服务器并没有意识到在浏览器端发生的这一切,您仍然可以使用curl或postman发出相同的请求,并且一切响应正常,因为这些工具上没有SOP。

如果说SOP是限制跨源访问的一种方式,那么CORS是一种绕过SOP限制并允许您的前端向服务器提出合法请求的方法。如果您的服务端的确是存在跨域的情况(实际上对于现代分布式应用,这很常见),由于SOP限制您的客户端将无法向多节点跨域服务器发出xhr请求。救星就出现了,CORS使我们能够以安全且可管理的方式做到跨域请求,突破同源策略的限制。

二、同源策略的源(Same Origin Policy的Origin)

源由三部分组成:协议,hostip(域)和端口。例如

  • http://example.com/xxx/index.htmlhttp://example.com/yyy/index.html 是同源,
  • http://example.com:80http://example.com (对于http默认端口为80)是同源。
  • 由于协议不同, http://example.com/app1https://example.com/app2 是不同的源。
  • http://example.comhttp://www.example.com 由于域名不同,也是不同的源
  • 非常要注意的是 http://localhosthttp://127.0.0.1 是不同的源

同源策略就是:不允许不同的ip、端口、协议的应用在浏览器内进行互相资源共享、请求调用。

三、CORS如何运作?

CORS规范允许服务器向浏览器返回一些HTTP Headers,浏览器可以基于这些HTTP Headers来决定是否突破SOP的限制。最主要的一个HTTP Headers是Access-Control-Allow-Origin。

//目标服务允许所有的网站对其进行跨域访问
Access-Control-Allow-Origin: * 
//目标服务允许特定的网站对其进行跨域访问
Access-Control-Allow-Origin: https://example.com

CORS有两种类型的请求:“simple”简单请求和“preflight”预检请求,根据请求方法的不同由浏览器确定使用哪种请求。

simple简单请求:

如果符合以下所有条件,则API请求被视为简单请求:

  • API方法是以下方法之一:GET,POST或HEAD。
  • Content-Type 请求头包含: application/x-www-form-urlencodedmultipart/form-datatext/plain

这两个条件将构成大多数简单请求的用例,但是可以在 此处 找到更详细的简单请求条件列表。

如果您的API请求被视为 simple 简单请求,这个请求就可以直接被发送给服务器。服务器使用CORS HTTP Headers进行响应,浏览器将检查 Access-Control-Allow-Origin 后决定这个请求是否可以突破同源策略的限制,进行下一步的处理。

preflight预检请求:

如果您的API请求不满足成为简单请求的标准(最常见不满足简单请求标准的 Content-Type 值为 application/json ),则浏览器将在发送实际请求之前发出预检请求。

举一个例子,我们尝试使用 GET 请求 https://example.com/statusContent-Typeapplication/json ,所以浏览器认为它不符合一个简单请求的标准,因此浏览器会在发出实际请求之前发出预检请求,这个预检请求是使用HTTP的 OPTIONS方法发出的:

curl --location --request OPTIONS 'http://example.com/status' \
--header 'Access-Control-Request-Method: GET' \
--header 'Access-Control-Request-Headers: Content-Type, Accept' \
--header 'Origin: http://test.com'

上面的curl就是模拟预检请求,实际作用是:浏览器希望告诉服务器,我的实际请求将使用HTTP GET method进行调用, Content-TypeAccept 作为HTTP headers,这个请求是从 https://test.com 发起的。服务器响应此请求:

HTTP/1.1 204 No Content
Access-Control-Allow-Origin: *
Access-Control-Allow-Methods: OPTIONS, GET, HEAD, POST
Access-Control-Allow-Headers: Content-Type, Accept
  • Access-Control-Allow-Origin :允许发出请求的源,或者 * 可以从任何来源发出请求。(即允许跨域的源)
  • Access-Control-Allow-Methods :允许的以逗号分隔的HTTP方法列表。(即允许跨域的HTTP方法)
  • Access-Control-Allow-Headers :允许发送的HTTP headers列表。

浏览器收到服务端的预检请求响应之后,在我们的示例中服务器响应 * 可以从任何来源发出请求,因此现在浏览器将再次访问 https://example.com/status ,使用GET方法(不再是OPTIONS方法),浏览器将不再限制该请求的发出与响应数据的接收。

如果预检请求响应的Origin是特定的 Access-Control-Allow-Origin: http://domain.com ,浏览器将出现 Cross-Origin Request Blocked 错误。因为服务器端预检结果只允许 http://domain.com 发出跨域请求,不允许其他应用向我发出跨域请求。

四、如何处理CORS错误

我们现在知道什么是CORS及其工作原理,后面的事情其实就简单了。从上面的内容我们需要注意的是,对CORS的完全控制权在服务器,即服务器可以允许或禁止源的跨域访问。所以说跨域问题的处理一般都在服务端进行,不同的服务端的处理HTTP 请求头的代码是不一样的,当然也可以不用写代码,比如:nginx、haproxy设置。 但是万变不离其宗:最终都是对HTTP Headers进行重写

我就简单的举几个例子:

比如Servlet处理跨域

public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain)
            throws IOException, ServletException {
        HttpServletResponse response = (HttpServletResponse) resp; 
        response.setHeader("Access-Control-Allow-Origin", "*"); //解决跨域访问报错   
        response.setHeader("Access-Control-Allow-Methods", "POST, PUT, GET, OPTIONS, DELETE");   
        chain.doFilter(req, resp); 
}

比如Spring MVC配置

@Configuration
public class GlobalCorsConfig {
    @Bean
    public WebMvcConfigurer corsConfigurer() {
        return new WebMvcConfigurer() {
            @Override
            public void addCorsMappings(CorsRegistry registry) {
                registry.addMapping("/**")    //添加映射路径,“/**”表示对所有的路径实行全局跨域访问权限的设置
                        .allowedOrigins("*")    //开放哪些ip、端口、域名的访问权限
                        .allowCredentials(true)  //是否允许发送Cookie信息 
                        .allowedMethods("GET","POST", "PUT", "DELETE")     //开放哪些Http方法,允许跨域访问
                        .allowedHeaders("*")     //允许HTTP请求中的携带哪些Header信息
                        .exposedHeaders("*");   //暴露哪些头部信息(因为跨域访问默认不能获取全部头部信息)
            }
        };
    }
}

欢迎关注我的博客,里面有很多精品合集

本文转载注明出处(必须带连接,不能只转文字): 字母哥博客 - zimug.com

觉得对您有帮助的话,帮我点赞、分享!您的支持是我不竭的创作动力!。另外,笔者最近一段时间输出了如下的精品内容,期待您的关注。


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK