Android WebView file:// 带有 SameSite cookie 的 url
我在将应用程序升级到 Android API 级别 31 (Android 12) 时遇到 cookie 问题。我的 file://
URL 无法访问远程 cookie,即使我将它们设置为 SameSite=None
也是如此。
API 级别 31 中记录的主要更改之一是 更改Cookie 行为。
WebView 中的现代 SameSite cookies
Android 的 WebView 组件基于 Chromium,后者是为 Google Chrome 浏览器提供支持的开源项目。 Chromium 对第三方 cookie 的处理进行了更改,以提供更高的安全性和隐私性,并为用户提供更多的透明度和控制力。从 Android 12 开始,当应用面向 Android 12(API 级别 31)或更高版本时,这些更改也会包含在 WebView 中。
cookie 的 SameSite 属性控制它是否可以与任何请求一起发送,或者仅与同一站点请求一起发送。以下隐私保护更改改进了第三方 cookie 的默认处理,并有助于防止意外的跨站点共享:
- 没有 SameSite 属性的 Cookie 被视为 SameSite=Lax。
- SameSite=None 的 Cookie 还必须指定安全属性,这意味着它们需要安全上下文并且应通过 HTTPS 发送。
- 站点的 HTTP 和 HTTPS 版本之间的链接现在被视为跨站点请求,因此不会发送 Cookie,除非它们被适当标记为 SameSite=None;安全。
我的应用程序在 assets
文件夹中包含嵌入的 HTML 文件,我通过 file:///android_asset/myfile.html
等 URL 使用 WebView 显示这些文件。
在 API 级别 31 之前,WebView 能够与我的远程服务器通信、接收 cookie 并将这些 cookie 作为响应发回,但是当我定位 API 级别 31 时,WebView 拒绝重新传输我的服务器发送的 cookie,即使我设置了 SameSite=None
。
这是一个重现该问题的简单 PHP 文件示例。
<?php
header("Access-Control-Allow-Origin: " . $_SERVER['HTTP_ORIGIN']);
header('Access-Control-Allow-Credentials: true');
header('Access-Control-Max-Age: 86400'); // cache for 1 day
// Access-Control headers are received during OPTIONS requests
if ($_SERVER['REQUEST_METHOD'] == 'OPTIONS') {
if (isset($_SERVER['HTTP_ACCESS_CONTROL_REQUEST_METHOD']))
header("Access-Control-Allow-Methods: GET, POST, OPTIONS");
if (isset($_SERVER['HTTP_ACCESS_CONTROL_REQUEST_HEADERS']))
header("Access-Control-Allow-Headers: {$_SERVER['HTTP_ACCESS_CONTROL_REQUEST_HEADERS']}");
exit(0);
}
setcookie('x', "1", [
'expires' => time() + 365*24*60*60,
'path'=> '/',
'secure' => true,
'httponly' => true
]);
Header( "Content-Type: application/json");
echo "{\"hello\": \"".$_COOKIE['x']."\"}\n"
?>
这是一个示例 HTML 文件,暂时指向我的域 www.choiceofgames.com
(但您应该使用自己的域进行测试):
<!DOCTYPE html>
<html>
<body></body>
<script type="module">
const url = 'https://www.choiceofgames.com/test/test.php';
let response;
response = await fetch(url, { credentials: "include" });
console.log(await response.text());
response = await fetch(url, { credentials: "include" });
console.log(await response.text());
</script>
</html>
当定位 API 级别 30 时,第一个 console.log
将返回 {"hello": ""}
,第二个 console.log
将返回 {"hello": "1 “}
。检查 WebView,我可以看到 cookie 在第二个请求中发送。
当以 API 级别 31 为目标时,它会两次记录 {"hello": ""}
;第二次请求时不会发送 cookie。
“好吧,”我想。我只需在我的 cookie 上设置 SameSite=None
即可。”我是这样做的:(
setcookie('x', "1", [
'expires' => time() + 365*24*60*60,
'path'=> '/',
'secure' => true,
'httponly' => true,
'samesite' => None
]);
我已将此版本提供为 https://www.choiceofgames.com/test/ test2.php 暂时。)
添加 SameSite=None
使我的 Android WebView 问题变得更糟。它并没有解决我的问题API 级别 31 中的 file:///android_asset/myfile.html WebView,但添加 SameSite=None 确实破坏了 API 级别 30 中的 WebView; code> 破坏了我的旧版本,并且在我的新版本中没有修复任何内容,
据我所知,SameSite=None
在 Android WebViews 中根本不起作用。 file://
URLs。
这让我想到了我的问题:
- 其他人可以重现我遇到的问题吗?Android WebViews 中的
file://
URLs 真的吗?不要在 API 级别 30 和 API 级别 31 中使用SameSite=None
发送 cookie?(这是一个可归档的错误吗?有人阅读或修复像我这样的普通人提交的 Android 错误吗?) - 有没有网页视图WebSetting 或者我可以用来解决这个问题的东西? (我目前正在使用
setAllowUniversalAccessFromFileURLs(true)
。 - 您能否建议我可以解决此问题的另一种方法?
I'm having a cookie issue upgrading my app to Android API level 31 (Android 12). My file://
URLs are unable to access remote cookies, even when I set them to SameSite=None
.
One of the major documented changes in API Level 31 is a change in cookie behavior.
Modern SameSite cookies in WebView
Android’s WebView component is based on Chromium, the open source project that powers Google’s Chrome browser. Chromium introduced changes to the handling of third-party cookies to provide more security and privacy and offer users more transparency and control. Starting in Android 12, these changes are also included in WebView when apps target Android 12 (API level 31) or higher.
The SameSite attribute of a cookie controls whether it can be sent with any requests, or only with same-site requests. The following privacy-protecting changes improve the default handling of third-party cookies and help protect against unintended cross-site sharing:
- Cookies without a SameSite attribute are treated as SameSite=Lax.
- Cookies with SameSite=None must also specify the Secure attribute, meaning they require a secure context and should be sent over HTTPS.
- Links between HTTP and HTTPS versions of a site are now treated as cross-site requests, so cookies are not sent unless they are appropriately marked as SameSite=None; Secure.
My app includes embedded HTML files in the assets
folder, which I display using a WebView via URLs like file:///android_asset/myfile.html
.
Prior to API level 31, the WebView was able to communicate to my remote server, receive cookies, and send those cookies back in responses, but when I target API Level 31, the WebView refuses to retransmit the cookies that my server sends, even when I set SameSite=None
.
Here's a trivial sample PHP file reproducing the issue.
<?php
header("Access-Control-Allow-Origin: " . $_SERVER['HTTP_ORIGIN']);
header('Access-Control-Allow-Credentials: true');
header('Access-Control-Max-Age: 86400'); // cache for 1 day
// Access-Control headers are received during OPTIONS requests
if ($_SERVER['REQUEST_METHOD'] == 'OPTIONS') {
if (isset($_SERVER['HTTP_ACCESS_CONTROL_REQUEST_METHOD']))
header("Access-Control-Allow-Methods: GET, POST, OPTIONS");
if (isset($_SERVER['HTTP_ACCESS_CONTROL_REQUEST_HEADERS']))
header("Access-Control-Allow-Headers: {$_SERVER['HTTP_ACCESS_CONTROL_REQUEST_HEADERS']}");
exit(0);
}
setcookie('x', "1", [
'expires' => time() + 365*24*60*60,
'path'=> '/',
'secure' => true,
'httponly' => true
]);
Header( "Content-Type: application/json");
echo "{\"hello\": \"".$_COOKIE['x']."\"}\n"
?>
And here's a sample HTML file, pointing to my domain www.choiceofgames.com
for the time being (but you should use your own domain for testing):
<!DOCTYPE html>
<html>
<body></body>
<script type="module">
const url = 'https://www.choiceofgames.com/test/test.php';
let response;
response = await fetch(url, { credentials: "include" });
console.log(await response.text());
response = await fetch(url, { credentials: "include" });
console.log(await response.text());
</script>
</html>
When targeting API Level 30, the first console.log
would return {"hello": ""}
, and the second console.log
would return {"hello": "1"}
. Inspecting the WebView, I can see that the cookie gets sent in the second request.
When targeting API Level 31, it logs {"hello": ""}
both times; the cookie isn't sent on the second request.
"OK," I thought. I'll just set SameSite=None
on my cookie." I did it like this:
setcookie('x', "1", [
'expires' => time() + 365*24*60*60,
'path'=> '/',
'secure' => true,
'httponly' => true,
'samesite' => None
]);
(I've made this version available as https://www.choiceofgames.com/test/test2.php
for the time being.)
Adding SameSite=None
made my Android WebView problem worse. It didn't fix my file:///android_asset/myfile.html
WebView in API Level 31, but it did break my WebView in API Level 30; adding SameSite=None
broke my old version, and fixed nothing in my new version.
As far as I can tell, SameSite=None
just doesn't work at all in Android WebViews from file://
URLs.
That brings me to my questions:
- Can others repro the problem I'm having? Is it true that
file://
URLs in Android WebViews just don't send cookies withSameSite=None
, in both API Level 30 and in API Level 31? (Is this a fileable bug? Does anyone read or fix Android bugs filed by ordinary mortals like myself?) - Is there a WebView WebSetting or something I can use to workaround this issue? (I'm currently using
setAllowUniversalAccessFromFileURLs(true)
. - Can you suggest another way I can workaround this issue?
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(1)
https://chromium.googlesource.com/chromium/src/+/1d127c933a4a39c65dc32cbd35bd511fd68ea452/android_webview/browser/cookie_manager.cc#317
看起来在 Android 中加载资源文件的“最佳”方法不是使用
file:///android_asset/myfile.html
,但要使用WebViewAssetLoader
。WebViewAssetLoader 拦截 WebView 请求,使您的所有资源文件出现在虚假的 HTTPS 域 URL
https://appassets.androidplatform.net/assets
上。您可以从https://appassets.androidplatform.net/assets/myfile.html
加载,而不是file:///android_asset/myfile.html
。浏览器会将其视为“真正的”HTTPS 域。
SameSite=None
将正常工作,CORS 将具有传统的非空 Origin,并且在file://
URL 之间共享 cookie 不会有任何奇怪的情况。(但是,比
SameSite=None
更好的方法是使用实际域的假子域。WebViewAssetLoader 有一个构建器参数,允许您将域设置为您控制的域,例如,如果您拥有 < code>example.com,您可以在https://appassets.example.com
上托管资产,从而允许您与您的网站共享 Cookie,即使使用SameSite=Strict.)
https://chromium.googlesource.com/chromium/src/+/1d127c933a4a39c65dc32cbd35bd511fd68ea452/android_webview/browser/cookie_manager.cc#317
It looks like the "best" way to load asset files in Android is not to use
file:///android_asset/myfile.html
, but to use aWebViewAssetLoader
.WebViewAssetLoader intercepts WebView requests, making all of your asset files appear on a fake HTTPS domain URL
https://appassets.androidplatform.net/assets
. Instead offile:///android_asset/myfile.html
you'd load fromhttps://appassets.androidplatform.net/assets/myfile.html
.The browser will treat that like a "real" HTTPS domain.
SameSite=None
will work normally, CORS will have a conventional non-null Origin, and there won't be any weirdness around sharing cookies betweenfile://
URLs.(But, even better than
SameSite=None
would be to use a fake subdomain of your actual domain. WebViewAssetLoader has a builder parameter allowing you to set the domain to a domain you control, e.g. if you ownexample.com
, you could host assets onhttps://appassets.example.com
, allowing you to share cookies with your website even withSameSite=Strict
.)