|
| 1 | +--- |
| 2 | +article: true |
| 3 | +date: 2025-07-19 |
| 4 | +category: |
| 5 | + - dev |
| 6 | + - cors |
| 7 | +tag: |
| 8 | + - dev |
| 9 | + - 开发 |
| 10 | + |
| 11 | +head: |
| 12 | + - - meta |
| 13 | + - name: description |
| 14 | + content: coep coop corp cors 跨域资源共享 |
| 15 | +--- |
| 16 | + |
| 17 | +# 跨源隔离和跨域资源共享 |
| 18 | + |
| 19 | +在开发 [Python Playground](https://play-py.zhaobc.site) 时遇到了跨域资源共享的问题,尤其是把它嵌入到本站时,导致了一些功能无法正常工作。 |
| 20 | + |
| 21 | +## 相关术语 |
| 22 | + |
| 23 | +在解决这些过程中,遇到了一些专业术语,这里简单记录一下。 |
| 24 | + |
| 25 | +- [`Cross-Origin-Opener-Policy` (_COOP_)](https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/Cross-Origin-Opener-Policy) |
| 26 | +- [`Cross-Origin-Embedder-Policy` (_COEP_)](https://developer.mozilla.org/zh-CN/docs/Web/HTTP/Reference/Headers/Cross-Origin-Embedder-Policy) |
| 27 | +- [`Cross-Origin-Resource-Policy` (_CORP_)](https://developer.mozilla.org/zh-CN/docs/Web/HTTP/Reference/Headers/Cross-Origin-Resource-Policy) |
| 28 | +- [Cross-Origin Resource Sharing (_CORS_)](https://developer.mozilla.org/zh-CN/docs/Web/HTTP/Guides/CORS) |
| 29 | +- [`Content-Security-Policy` (_CSP_)](https://developer.mozilla.org/zh-CN/docs/Web/HTTP/Guides/CSP) |
| 30 | + |
| 31 | +## SharedArrayBuffer 相关 |
| 32 | + |
| 33 | +Python Playground 中用到了 [`SharedArrayBuffer`](https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/SharedArrayBuffer),它需要一定的[安全需求](https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/SharedArrayBuffer#%E5%AE%89%E5%85%A8%E9%9C%80%E6%B1%82)。 |
| 34 | + |
| 35 | +MDN 中对 [`SharedArrayBuffer`](https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/SharedArrayBuffer) 有详细的说明。 |
| 36 | + |
| 37 | +::: tip |
| 38 | +对于顶级文档,需要设置两个标头来实现你网站的跨源隔离: |
| 39 | + |
| 40 | +`Cross-Origin-Opener-Policy` 设置为 `same-origin`(来保护你的源站点免受攻击) |
| 41 | +`Cross-Origin-Embedder-Policy` 设置为 `require-corp` 或 `credentialless`(保护受害者免受你的源站点的影响) |
| 42 | + |
| 43 | +为了验证跨源隔离是否生效,你可以测试窗口和 worker 上下文中的 `Window.crossOriginIsolated` 或 `WorkerGlobalScope.crossOriginIsolated` 属性。 |
| 44 | + |
| 45 | +嵌套文档和专用 worker 线程也需要将 `Cross-Origin-Embedder-Policy` 标头设置为同样的值。 |
| 46 | +对于同源嵌套文档和子资源,不需要进行任何其他更改。 |
| 47 | +同站(但跨源)嵌套文档和子资源需要将 `Cross-Origin-Resource-Policy` 标头设置为 `same-site`。 |
| 48 | +而它们的跨源(和跨站点)的对应部分也需要将同样的标头设置为 `cross-origin`。 |
| 49 | + |
| 50 | +请注意,将 `Cross-Origin-Resource-Policy` 标头设置为除 `same-origin` 之外的任何值,都会使资源暴露于潜在的攻击中,比如幽灵漏洞。 |
| 51 | +::: |
| 52 | + |
| 53 | +::: warning |
| 54 | +`credentialless` 目前还有[浏览器兼容性问题](https://developer.mozilla.org/zh-CN/docs/Web/HTTP/Reference/Headers/Cross-Origin-Embedder-Policy#%E6%B5%8F%E8%A7%88%E5%99%A8%E5%85%BC%E5%AE%B9%E6%80%A7)。 |
| 55 | + |
| 56 | +Safari 和多数移动端浏览器目前不支持 `credentialless`。 |
| 57 | +::: |
| 58 | + |
| 59 | +## 相关设置 |
| 60 | + |
| 61 | +基于以上信息,将主站(也就是本站)的 headers 做了一下设置。 |
| 62 | + |
| 63 | +- 正常 PC 浏览器 |
| 64 | + |
| 65 | + ```txt |
| 66 | + Cross-Origin-Opener-Policy: same-origin |
| 67 | + Cross-Origin-Embedder-Policy: credentialless |
| 68 | + Cross-Origin-Resource-Policy: cross-origin |
| 69 | + ``` |
| 70 | + |
| 71 | +- 移动端浏览器 |
| 72 | + |
| 73 | + ```txt |
| 74 | + Cross-Origin-Opener-Policy: same-origin |
| 75 | + Cross-Origin-Embedder-Policy: require-corp |
| 76 | + Cross-Origin-Resource-Policy: cross-origin |
| 77 | + ``` |
| 78 | + |
| 79 | +- Safari |
| 80 | + |
| 81 | + ```txt |
| 82 | + Cross-Origin-Opener-Policy: same-origin |
| 83 | + Cross-Origin-Embedder-Policy: require-corp |
| 84 | + Cross-Origin-Resource-Policy: cross-origin |
| 85 | + ``` |
| 86 | + |
| 87 | +`vite` 中设置如下 : |
| 88 | + |
| 89 | +```js |
| 90 | +export default defineConfig({ |
| 91 | + plugins: [ |
| 92 | + vue(), |
| 93 | + // 在 server.headers 中设置貌似不起作用,在 plugins 中可以起作用。 |
| 94 | + // Support Cross-Origin-Opener-Policy and Cross-Origin-Embedder-Policy on dev server |
| 95 | + // https://github.com/vitejs/vite/issues/3909#issuecomment-934044912 |
| 96 | + { |
| 97 | + name: 'configure-response-headers', |
| 98 | + configureServer: server => { |
| 99 | + server.middlewares.use((_req, res, next) => { |
| 100 | + // Cross-Origin-Embedder-Policy 浏览器兼容性 |
| 101 | + // https://developer.mozilla.org/zh-CN/docs/Web/HTTP/Reference/Headers/Cross-Origin-Embedder-Policy#%E6%B5%8F%E8%A7%88%E5%99%A8%E5%85%BC%E5%AE%B9%E6%80%A7 |
| 102 | + const userAgent = _req.headers['user-agent']?.toLowerCase() || '' |
| 103 | + if (/safari/i.test(userAgent) && !/chrome/i.test(userAgent)) { |
| 104 | + // safari |
| 105 | + res.setHeader('Cross-Origin-Embedder-Policy', 'require-corp') |
| 106 | + } else { |
| 107 | + res.setHeader('Cross-Origin-Embedder-Policy', 'credentialless') |
| 108 | + } |
| 109 | + res.setHeader('Cross-Origin-Opener-Policy', 'same-origin') |
| 110 | + res.setHeader('Cross-Origin-Resource-Policy', 'cross-origin') |
| 111 | + next() |
| 112 | + }) |
| 113 | + }, |
| 114 | + }, |
| 115 | + ], |
| 116 | + |
| 117 | + server: { |
| 118 | + headers: { |
| 119 | + 'Cross-Origin-Opener-Policy': 'same-origin', |
| 120 | + 'Cross-Origin-Embedder-Policy': 'require-corp', |
| 121 | + 'Cross-Origin-Resource-Policy': 'cross-origin', |
| 122 | + }, |
| 123 | + }, |
| 124 | +}); |
| 125 | +``` |
| 126 | +
|
| 127 | +`vercel.json` 设置: |
| 128 | +
|
| 129 | +```json |
| 130 | +{ |
| 131 | + "headers": [ |
| 132 | + { |
| 133 | + "source": "/(.*)", |
| 134 | + "headers": [ |
| 135 | + { |
| 136 | + "key": "Cross-Origin-Embedder-Policy", |
| 137 | + "value": "credentialless" |
| 138 | + }, |
| 139 | + { |
| 140 | + "key": "Cross-Origin-Opener-Policy", |
| 141 | + "value": "same-origin" |
| 142 | + }, |
| 143 | + { |
| 144 | + "key": "Cross-Origin-Resource-Policy", |
| 145 | + "value": "cross-origin" |
| 146 | + } |
| 147 | + ] |
| 148 | + }, |
| 149 | + { |
| 150 | + "source": "/(.*)", |
| 151 | + "has": [ |
| 152 | + { |
| 153 | + "type": "header", |
| 154 | + "key": "User-Agent", |
| 155 | + "value": { |
| 156 | + "re": ".*(iPhone|iPad|iPod|iOS|Android).*" |
| 157 | + } |
| 158 | + } |
| 159 | + ], |
| 160 | + "headers": [ |
| 161 | + { |
| 162 | + "key": "Cross-Origin-Embedder-Policy", |
| 163 | + "value": "require-corp" |
| 164 | + }, |
| 165 | + { |
| 166 | + "key": "Cross-Origin-Opener-Policy", |
| 167 | + "value": "same-origin" |
| 168 | + }, |
| 169 | + { |
| 170 | + "key": "Cross-Origin-Resource-Policy", |
| 171 | + "value": "cross-origin" |
| 172 | + } |
| 173 | + ] |
| 174 | + }, |
| 175 | + { |
| 176 | + "source": "/(.*)", |
| 177 | + "has": [ |
| 178 | + { |
| 179 | + "type": "header", |
| 180 | + "key": "User-Agent", |
| 181 | + "value": { |
| 182 | + "re": "^(?=.*Safari)(?!.*Chrome).*$" |
| 183 | + } |
| 184 | + } |
| 185 | + ], |
| 186 | + "headers": [ |
| 187 | + { |
| 188 | + "key": "Cross-Origin-Embedder-Policy", |
| 189 | + "value": "require-corp" |
| 190 | + }, |
| 191 | + { |
| 192 | + "key": "Cross-Origin-Opener-Policy", |
| 193 | + "value": "same-origin" |
| 194 | + }, |
| 195 | + { |
| 196 | + "key": "Cross-Origin-Resource-Policy", |
| 197 | + "value": "cross-origin" |
| 198 | + } |
| 199 | + ] |
| 200 | + } |
| 201 | +} |
| 202 | +``` |
| 203 | +
|
| 204 | +## 副作用 |
| 205 | +
|
| 206 | +- 设置了 `COEP` 后,会阻塞 `CORS` 导致引入的外部资源无法加载,需要单独指定 `crossorigin`。 |
| 207 | + 尤其是 外部的 CDN资源,CSS, 图片,视频,等资源。 |
| 208 | +
|
| 209 | + ```html |
| 210 | + <img src="https://example.com/image.jpg" crossorigin="anonymous"> |
| 211 | + ``` |
| 212 | +
|
| 213 | + 但是,一些外部资源动态追加的资源无法控制,导致无法加载。比如,百度统计, clarity 统计。 |
| 214 | +
|
| 215 | + Safari 浏览器会无法加载 CDN 资源,导致图标无法正常显示。 |
| 216 | + 哪位朋友知道如何解决的话,忘不吝赐教。 |
| 217 | +
|
| 218 | +- 设置了 `COEP` 后,`iframes` 会无法加载。需要指定 `sandbox="allow-scripts allow-same-origin" credentialless` 属性。 |
0 commit comments