Appearance
H5 登录态与 Cookie 强注入 SSO 机制
回到总览:混合开发与桥接
相关模块:WebView H5 JSBridge 交互与安全防护 · Token 鉴权体系、JWT、OAuth2 与移动端双 Token 自动无感刷新
一句话定义
H5 登录态同步与 SSO(单点登录)是指在 Native 移动端已完成用户登录的前提下,通过 CookieManager 强注入、URL 参数重定向 或 JSBridge 动态换票 机制,将 Native 端的身份凭证(Token / Session)无缝传递给 WebView 内嵌的 H5 页面,实现用户免二次登录的单点登录体验。
代码索引
对应 Lab:h5-sso-cookie-demo · H5SsoCookieDemoActivity.kt
labs/android/host/topics/bridge-hybrid/h5-sso-cookie-demo/— CookieManager 强注入、flush 异步持久化与第三方 Cookie 跨域配置
为什么需要
- 为什么在 Native 已登录的情况下,打开 H5 页面经常仍然提示“请先登录”?
- 一句话答:Native 端的 Token 存储在 SharedPreferences/MMKV 中,而 WebView 的 H5 页面依赖浏览器的 Cookie 存储;两者的存储空间天然隔离,如果不做显式同步,H5 发起 HTTP 请求时就不会自动带上身份 Cookie。
- 为什么注入 Cookie 后必须显式调用
CookieManager.getInstance().flush()?- 一句话答:
setCookie()默认只是写入了内存缓冲区;如果不调用flush()强行刷入磁盘,当应用突然崩溃或被系统杀死时,写入的 Cookie 状态就会丢失。
- 一句话答:
- 现代 Android (Android 9.0+) 对 H5 Cookie 的跨域限制有何变化?
- 一句话答:Android 9.0+ 默认禁用了第三方 Cookie(Third-Party Cookies)和跨域 file:// 访问,必须显式配置
CookieManager.setAcceptThirdPartyCookies(webView, true)。
- 一句话答:Android 9.0+ 默认禁用了第三方 Cookie(Third-Party Cookies)和跨域 file:// 访问,必须显式配置
底层机制
1. CookieManager 强注入同步时序
图注补充:App: 1. Native 完成登录,拿到 AuthToken;CM: 2. setCookie("https://example.com", "token=XYZ; Domain=.example.com; Path=/");WV: 4. webView.loadUrl("https://example.com/profile");Server: 5. 自动在 Request Header 中带上 Cookie: token=XYZ;WV: 6. 校验通过,直接渲染登录状态页面!。
Android / Flutter / Web / Backend 对照
| 维度 | Android Native WebView | iOS WKWebView | Flutter webview_flutter |
|---|---|---|---|
| Cookie 注入容器 | android.webkit.CookieManager | WKHTTPCookieStore | WebViewCookieManager |
| 磁盘刷新 API | CookieManager.getInstance().flush() | 系统自动同步 | setCookie() 自动持久化 |
| 第三方 Cookie | setAcceptThirdPartyCookies(wv, true) | 受 ITP (Intelligent Tracking Protection) 限制 | 继承 Native 属性 |
常见场景与代码实现
1. Android Native 强注入 Cookie 最佳实践
对应 Lab:h5-sso-cookie-demo
java
public void injectH5Cookies(Context context, String url, String token) {
CookieManager cookieManager = CookieManager.getInstance();
cookieManager.setAcceptCookie(true);
// 绑定域名与 Cookie 属性
String cookieString = "token=" + token + "; Domain=.example.com; Path=/; Secure";
cookieManager.setCookie(url, cookieString);
// Android 5.0+ 必须开启第三方 Cookie 支持
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
cookieManager.setAcceptThirdPartyCookies(webView, true);
cookieManager.flush(); // 强制写入磁盘持久化!
} else {
CookieSyncManager.createInstance(context).sync();
}
}2. URL 参数重定向(一次性免登,适合跨端拉起场景)
当 H5 从外部(分享卡片 / 推送 / 短信链接)被拉起,且无法预注入 Cookie 时,用带一次性 ticket 的 URL 完成免登:
text
// 拉起链接(ticket 一次性、短时效,由 Native 侧生成或代理换取)
https://h5.example.com/campaign?ticket=abc123xyz
// H5 侧拿到 ticket 后调用后端换取真实登录态
POST /api/sso/exchange
Body: { "ticket": "abc123xyz" }
Response: { "session": "srv_session_xxx", "expires_in": 3600 }
// H5 把 session 写入自身域 Cookie,之后请求自动携带- 观察重点:ticket 必须是一次性 + 短时效 + 绑定设备/来源,否则链接被转发即可冒用他人会话;换取动作必须在 H5 服务端完成,不能让 H5 前端拿到长期凭据。
3. JSBridge 动态换票(Cookie 失效时的静默续期)
H5 页面已打开、但 Cookie 过期时,通过 JSBridge 回调 Native 静默刷新 Token:
javascript
// H5 侧:拦截 401,走 bridge 换票后重放请求
async function fetchWithAutoRefresh(url, options) {
let res = await fetch(url, options);
if (res.status === 401) { // 登录态失效
const newToken = await NativeBridge.refreshToken(); // JSBridge → Native 静默续期
document.cookie = `token=${newToken}; Domain=.example.com; Path=/`;
return fetch(url, options); // 重放原请求
}
return res;
}kotlin
// Native 侧:对应 JSBridge 的 refreshToken 实现
@JavascriptInterface
fun refreshToken(callbackId: String) {
// 用 refresh_token 换新 access_token(不弹出登录页,静默完成)
val newToken = authManager.refreshAccessToken() // 失败则回调拒绝
evaluateJavascript("window.NativeBridge.onRefresh('$newToken')")
}- 可能执行顺序:H5 请求 401 → 调
refreshTokenbridge → Native 静默换新 Token → 写回 Cookie → H5 重放请求。 - 观察重点:换票只应发生在"确实已登录过"的会话上;refresh_token 过期时该链路必须降级为跳转登录页,不能死循环静默失败。
常见误配、事故后果与排障
1. 事故:setCookie 未指定 Domain 导致子域名 H5 页面取不到 Token
- 误配原因:注入 Cookie 时写死为
cookieManager.setCookie("https://m.example.com", "token=123"),未设置Domain=.example.com。 - 后果:当 H5 页面内部跳转到
https://pay.example.com时,由于 Cookie 属于m.example.com独占,跨子域名请求丢失 Cookie,导致用户在支付页二次跳出登录! - 排障与修法:设置 Cookie 时必须显式带上顶级通用域名
Domain=.example.com。
复习检查题
在 Android 中,为什么只调用
CookieManager.setCookie()还不够,必须显式调用flush()?答:因为
setCookie()方法为了性能提升,默认只是将 Cookie 字符串暂存在 WebKit 的内存缓冲区中,异步定时写入磁盘。如果在尚未写入磁盘时 App 发生了崩溃或被系统杀死,内存中的 Cookie 就会丢失。调用flush()能够强制将缓冲区中的 Cookie 立刻写入磁盘物理文件,保障登录态不丢失。为什么在为 H5 注入 Cookie 时,设置
Domain属性(如Domain=.example.com)至关重要?答:如果未设置
Domain属性,Cookie 默认只对当前的精确域名(如m.example.com)生效。当 H5 页面发起跨子域名请求(如请求api.example.com或跳转到pay.example.com)时,浏览器出于同源安全策略不会携带该 Cookie。显式指定顶级域名Domain=.example.com才能保证该 Token 在所有子域名下均可无缝共享。
速记
- 强注入公式:
setCookie加 Domain/Path →setAcceptThirdPartyCookies→flush()写入磁盘。 - 域名跨域:必须指定
Domain=.example.com才能覆盖所有子域名。 - 降级容错:Cookie 失效时,通过 JSBridge 唤起 Native 静默刷新 Token。