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

我相信如果你写过前后端分离的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 GETmethod进行调用,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("*");   //暴露哪些头部信息(因为跨域访问默认不能获取全部头部信息)
            }
        };
    }
}

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

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

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

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 201,681评论 5 474
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 84,710评论 2 377
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 148,623评论 0 334
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,202评论 1 272
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,232评论 5 363
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,368评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,795评论 3 393
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,461评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,647评论 1 295
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,476评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,525评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,226评论 3 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,785评论 3 303
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,857评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,090评论 1 258
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,647评论 2 348
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,215评论 2 341