CORS
说了无数次,基本都是个别资源需要配置而已,在前面全站跨域的情况下还是出现了些小问题。
项目需要国内国外使用不同的api域来避免主域不能在国内备案的问题。所以在国内环境会启用新的备案域名做api接口请求域,不就是跨域嘛 简单…
原本以为只是需要配置下 Access-Control-Allow-xx
相关响应头,异步请求配置下 withCredentials
携带cookie,调整下相对路径,后面实际又陆续发现了新的问题,这个做下记录。
同源策略
首先还是说下同源策略,源文档和加载的资源需要 协议/主机/端口
完全一致,不完全相同的情况下会存在一定的限制,也就是跨域。
限制包含不限于如下:
- LocalStorage、SessionStorage、Cookie等存储读写操作
- 异步请求比如xhr、fetch 等
- iframe,无法跨域读写操作等
- script 无法捕获详细异常
- canvas 无法导出图片等
- …
但是也恰恰是因为了这些限制才保证了我们可以安全的浏览网页。常见的前端安全问题可以参见这里:常见前端安全问题
CORS
前面说了浏览器的同源策略和产生的一些限制,那对于解决异步请求限制最通用的方法就是使用CORS机制(Cross-Origin Resource Sharing),跨域资源共享。
常见CORS的场景
- xhr fetch 异步请求
- font-face跨域字体资源
- canvas导出跨域绘制图片
常见的 CORS
头配置
使用 CORS
需要服务端在允许跨域的资源配置相关的相应头,浏览器会根据相关响应做放行或者拦截处理。
1 | # 允许跨域来访问的源站,* 表示所有,多个站点通过,分割 |
另外根据请求的方法和请求头,会分为
简单请求
和非简单请求
。
非简单请求需要预检
,先发送OPTIONS
请求通过后才能发送正式请求,可以通过Access-Control-Max-Age
配置 OPTIONS 请求缓存,这里不细讲
遇到的问题
前面简单介绍了CORS的常用配置,下面主要记录下实际项目中遇到的一些常见而 不常见
问题。
异步请求下载文件失败
项目中有些导出文件采用 xhr
请求后端返回文件流的形式下载,同时文件名读取 content-disposition
响应头。
如下伪代码:
导出代码1
2
3
4
5
6
7
8async function exportXls() {
const res = await http(args);
const fileName = res.headers['content-disposition'].replace(/.*filename="(.*)?"/, '$1');
const blobUrl = window.URL.createObjectURL(res.body);
download(blobUrl, fileName);
window.URL.revokeObjectURL(blobUrl);
}
response头1
2
3...
content-disposition: attachment; filename="xxxRecord_2000_01_01.xls"
content-type: application/vnd.ms-excel;charset=utf-8
CORS后就出现了问题
content-disposition
头读取失败导致文件名获取失败。
查阅资料发现跨域时候默认 content-disposition
不会暴露给浏览器访问,需要配置 Access-Control-Expose-Headers
来指定。这个知识点真的有点偏…
默认情况下,只有六种 simple response headers (简单响应头)可以暴露给外部
- Cache-Control
- Content-Language
- Content-Type
- Expires
- Last-Modified
- Pragma
配置 Access-Control-Expose-Headers
格式如下:
1 | Access-Control-Expose-Headers: <header-name>, <header-name>, ... |
最终通过配置
access-control-expose-headers: Content-Disposition
解决了问题
SameSite 的兼容方案
SameSite
是Chrome为了加强浏览器安全性,防止 CSRF 攻击和用户追踪的cookie属性,在Chrome 70多的版本之后开始疯狂提示注意升级,并且在 80的版本将会在浏览器中默认设置为 Lax
。
比较烦人的是这个存在兼容性问题,如下这样一条 setCookie 语句,可能在ie或者其它不支持SameSite的浏览器下无法成功写入cookie。
1 | set-cookie: cookieName=hzhzhz; expires=Thu, 27-Aug-2020 07:08:16 GMT; Max-Age=7200; path=/; SameSite=None; secure |
最终通过写入两条同名cookie,一个旧的一个sameSite,关于 SameSite
属性这里单独介绍下 cookie-sameSite-兼容方案
Safari itp 协议导致三方cookie被限制
ITP协议是苹果为了加强用户隐私对站点存储的一些限制,最新的2.0的版本会限制三方cookie。目的是和SameSite类似,但比SameSite更加强硬。
不过目前暂时不影响 session cookie,所以到还好。
对于广告最终或用户统计类服务来说会有较大影响,更多关于Safari-itp协议应对策略这里单独介绍:Safari-itp协议
2020-09-01 网站在 Safari 下无法登录了,发现跨域 http response 的setCookie也无法写入和发送了。因为已经完全被阻止了三方cookie,即使是httponly,只能调整通过
token来做用户认证
使用相对路径的资源请求
这一点是和实际业务相关的,改起来挺多的也最容易遗漏。
比如:
- 异步请求所有都是以
/
开头,需要调整到新的域名 - 一些三方组件可能也继承了数据请求,比如图片上传组件,那配置的url也需要处理
- 通过a标签直接下载文件的场景
- 等等..
解决:
- 首先提供
getApiPath()
方法来获取到实际的接口地址 - 公共
http service
会处理异步请求接口地址,但是三方插件还是有漏网然后通过重写XMLHttpRequest.prototype.open
方法在过滤了一次/
开头地址 - a 标签这种只能统一排查修改
主要就是一不小心发现有漏改 …