crossorigin 属性:为什么它是避免 tainted canvases 的关键?
本文为 HTML 标准解读系列文章,其他文章详见这里。
你是否曾经遇到过这样的问题?
使用
canvas
的getImageData()
方法,却报错:The canvas has been tainted by cross-origin data
;使用
canvas
的toDataURL()
方法,却报错:Tainted canvases may not be exported
;使用
document.styleSheets[0].cssRules
访问样式表的 css 规则,却报错:Failed to read the 'cssRules' property from 'CSSStyleSheet': Cannot access rules
。
如果你上网找解决方案,会发现他们都跟一个 HTML 属性密切关联:crossorigin
。crossorigin
是 CORS 在 HTML 标签上的应用。 很多的元素,包括img
、video
、audio
、link
、script
都有这个属性:
幸运的是,在不同的元素上,crossorigin
发挥作用的机理几乎是一样的,也就是说,只要明白它在一个元素上是如何发挥作用的,其他元素上的使用也就明白了。
本文我将会基于HTML标准2.5.4 CORS settings attribute,力求给你讲清楚:
crossorigin
属性有哪些值?分别是干什么用的?crossorigin
是如何解决开篇的三个问题的?如何使用 js 的
fetch API
,来模拟不同crossorigin
的值所发起的请求?
一些必要的前置知识
同源策略 是浏览器执行的一种安全机制,这个策略的一个重要方面就是限制使用 js 脚本访问不同源的资源。而 CORS(Cross-Origin Resource Sharing/跨域资源共享)就是同源策略开的一道口子,只要客户端与服务端(通过 HTTP 头)按照一定的规则进行协商,那么客户端就能正常使用 js 访问不同源的服务端资源。
我在这里不会讲他们是如何进行协商的,因为像这样的文章满天飞。我为你准备的是一段服务端代码,这段代码使用expressJS实现了服务端资源的跨域共享,以便你在阅读本文的时候可以方便的在本地进行测试:
请求的三种状态
我们都知道,使用img
、video
、audio
、link
、script
等 HTML 元素可以请求外部的资源。
根据标准,这些元素所发出的请求可以根据是否支持 CORS 划分为 3 种状态:
No CORS
Anonymous
Use Credentials
状态
No CORS
表示不支持 CORS 的请求。 在这种情况下,不管服务端对应的资源是否也支持 CORS, 虽然浏览器依然会拿到资源,这个资源也会正常加载和显示,但使用 js 访问其资源的能力会受到限制,具体表现为:对于
img
、audio
、video
元素:当这些元素的资源放在canvas
上使用的时候,这些资源会被标记为tainted
,于是getImageData()
和toDataURL()
方法就会被禁用;对于
script
元素:使用window.onerror
访问其错误信息的的能力会被限制,比如在 Chrome 上只是统一报一个Script error.
的错误文本;对于
link
元素:资源无法被 js 访问,如样式表中的cssRule
。状态
Anonymous
表示这是一个支持 CORS 的请求:如果服务端对应的资源不支持 CORS,就会报诸如
Access to XXX from origin 'YYY' has been blocked by CORS policy
的错误。如果服务端对应的资源支持 CORS,那么你就能拿到这个资源完整的访问权,也就是可以通过 js 脚本操作这个资源。
状态
Use Credentials
表示这是一个支持 CORS 的请求,并且会在请求头中附带相关的身份凭证(如Cookie
信息),其他地方与状态Anonymous
一样。 Anonymous 在英文中是匿名的意思,而 Use Credentials 则表明你要像服务端表明你的身份。
注意:以上说的都是针对不同源的请求而言的。如果是同源的请求,资源不会受到同源策略的限制,默认就是有完整的访问权限。
使用crossorigin
改变请求的状态
大部分情况下,No CORS
是 HTML 元素发起请求的默认状态。这也就是为什么在不做任何配置的情况下,在canvas
使用上不同源的img
会报tainted canvases
的错误,访问不同源的cssRule
会报错。只有一种例外情况,当link
和script
与module
一起使用的时候,默认请求状态会设置为Anonymous
。
而你可以通过crossorigin
属性改变默认的请求状态:
如果
crossorigin="anonymous"
,就会创建一个状态为Anonymous
的 CORS 请求。如果
crossorigin="use-credentials"
,就会创建一个状态为Use Credentials
的 CORS 请求。其他任何属性值,包括空字符串
""
,都会创建一个状态为Anonymous
的 CORS 请求。
于是,对于文章开头的几个问题,解法已经很清晰了:你必须使用crossorigin
属性修改请求的状态,使其支持跨域。这样如果服务端也做了跨域支持,那么你就可以拿到资源完整的访问权。
也就是说,在服务端已经支持跨域的情况下,为了避免tainted canvases
,你需要给img
赋予一个crossorigin
属性,如:
为了访问cssRule
,你需要给link
赋予一个crossorigin
属性,如:
值得一提的是,虽然object
、embed
、iframe
等元素虽然也可以发起资源请求,但是他们都不支持crossorigin
属性,所以他们的请求状态都是No CORS
。
使用 fetch api 模拟三种请求状态
fetch(resource,options)
的options
参数有两个属性可以用来模拟不同状态:
mode
属性,可以用来设置是否支持 CORS,具体的值在这里列出了。credentials
属性,可以控制是否发送身份认证信息,具体的值在这里列出了。
于是,你可以在 JS 中使用fetch
模拟 HTML 元素请求三种状态:
与 HTML 元素不同的是,一般情况下mode
的默认值是cors
,也就是说一般情况下 fetch 请求默认支持 CORS。
当你发送的是一个状态为No CORS
的跨域请求,你会发现返回的response
里的body
为 null,也就是说,虽然请求成功,但你仍然无法访问返回的资源。
当你发送的是一个状态为Anonymous
的跨域请求,你可以从开发者工具看到你的请求头并没有Cookies
这一项。
当你发送的是一个状态为Use Credentials
的跨域请求,你可以从开发者工具看到你的请求头附上了Cookies
。
总结
我在HTML常见微语句提到过,有一种 HTML 属性叫「可枚举属性」。这种属性有有限的状态,并且定义了一个关键词/状态映射的集合,一个关键词对应一个状态。两个特殊的状态是missing value default
以及 invalid value default
,分别表示属性没有出现时使用的状态以及属性值非法(不匹配任一关键词)时的使用的状态。与「可枚举属性」相反的是像class
这种属性,有无限的状态。
crossorigin
就是一种可枚举属性。下面我用一张表为你总结了这个属性:
版权声明: 本文为 InfoQ 作者【水鱼兄】的原创文章。
原文链接:【http://xie.infoq.cn/article/730a345c35c051aa8363bf73a】。文章转载请联系作者。
评论