@@ -6,163 +6,94 @@ categories: android webview
66tags : android webview okhttp cookie
77---
88
9- ## 背景
9+ ---
10+ layout: post
11+ title: "Android WebView 拦截请求导致的会话不一致问题及解决方案"
12+ date: 2025-12-23 21:00:00 +0000
13+ categories: android webview
14+ tags: android webview okhttp cookie
15+ ---
16+
17+ ## 问题现象
1018
11- 在 Android 开发中,为了优化网络请求,我们经常会在 WebView 中使用 ` shouldInterceptRequest ` 方法拦截请求,并替换为 OkHttp 来执行。这样做的好处是可以使用 HTTPDNS、统一的网络配置、请求日志等 。
19+ 在 Android 开发中,常通过重写 WebView 的 ` shouldInterceptRequest ` 方法,使用自定义网络栈(如 OkHttp)来接管部分资源请求(例如仅拦截 GET 请求以使用 HTTPDNS 或统一缓存) 。
1220
13- 然而,如果只拦截部分请求(比如只拦截 GET 请求),就可能导致** 会话(Session)不一致** 的问题。
21+ 这种** 部分拦截** 的策略极易引发一个严重问题:** 会话(Session)不一致** 。
22+ 用户在登录后(原生网络栈 POST 请求),后续页面依然处于未登录状态;或图形验证码校验始终不过。
1423
15- ## 问题描述
24+ ## 根本原因
1625
17- 典型的场景如下:
26+ 会话不一致的根本原因是: ** 原生 WebView 的 ` CookieManager ` 与第三方网络库(OkHttp)的 Cookie 存储相互隔离且未同步。 **
1827
1928```
20- 用户登录流程 :
21- 1. [GET] 加载登录页面 → 拦截走 OkHttp
22- 2. [POST] 提交登录表单 → 走 WebView 原生
23- 3. [GET] 获取用户信息 → 拦截走 OkHttp
29+ 用户登录流程的时序割裂 :
30+ 1. [GET] 加载登录页面 → 被拦截,走 OkHttp 请求
31+ 2. [POST] 提交登录表单 → 未被拦截, 走 WebView 原生请求,服务端返回 Set-Cookie
32+ 3. [GET] 获取用户信息 → 被拦截,走 OkHttp 请求
2433```
2534
26- 问题出现在:
27- - OkHttp 收到的 ` Set-Cookie ` 响应头没有同步到 WebView 的 ` CookieManager `
28- - WebView 设置的 Cookie 也可能没有同步到 OkHttp 的请求里
35+ - WebView 原生请求收到的 ` Set-Cookie ` 会保存在 ` CookieManager ` 中。
36+ - 后续拦截走 OkHttp 的请求,如果没有提取并携带 ` CookieManager ` 中的 Cookie,或者 OkHttp 响应的 ` Set-Cookie ` 没有反向同步给 ` CookieManager ` ,两套网络栈使用的就是不同的 Session 标识。
2937
30- 这就导致 GET 请求和 POST 请求使用的是不同的 Session,服务端自然就认不出你了。
38+ ## 解决方案
3139
32- ## 问题代码
40+ 核心思路: ** 在拦截逻辑中,充当 Cookie 的双向同步桥梁。 **
3341
34- ``` kotlin
35- override fun shouldInterceptRequest (view : WebView ? , request : WebResourceRequest ? ): WebResourceResponse ? {
36- // 只拦截 GET 请求
37- if (" GET" .equals(request?.method, true )) {
38- return getResponseByOkHttp(request)
39- }
40- return super .shouldInterceptRequest(view, request)
41- }
42+ > 注:` WebResourceRequest.requestHeaders ` 在调用 ` shouldInterceptRequest ` 时,系统已经自动从 ` CookieManager ` 注水了最新的 Cookie,因此只需要关注** 响应后** 的 Cookie 回写。
4243
44+ 在拦截实现中增加响应后的 Cookie 同步逻辑:
45+
46+ ``` kotlin
4347private fun getResponseByOkHttp (request : WebResourceRequest ? ): WebResourceResponse ? {
4448 request ? : return null
4549 val url = request.url.toString()
4650 val requestBuilder = okhttp3.Request .Builder ()
4751 .url(url)
4852 .method(request.method, null )
4953
50- // ❌ 问题1:直接使用 requestHeaders,未确认 Cookie 来源
54+ // 1. 携带 WebView 已有的 Cookie (系统已注入到 requestHeaders)
5155 request.requestHeaders?.forEach { (key, value) ->
5256 requestBuilder.addHeader(key, value)
5357 }
5458
5559 val response = okhttpClient.newCall(requestBuilder.build()).execute()
5660
57- // ❌ 问题2:响应中的 Set-Cookie 被直接丢弃了!
61+ // 2. 关键:同步响应头中的 Set-Cookie 到 WebView的 CookieManager
62+ // 必须在判断状态码之前同步,以防 302 重定向丢失 Cookie
63+ syncCookiesToManager(url, response)
64+
65+ if (response.code != 200 ) {
66+ response.close()
67+ return null
68+ }
5869
5970 val body = response.body ? : return null
60- return WebResourceResponse ( .. .)
71+ // 构建并返回 WebResourceResponse...
6172}
62- ```
63-
64- ## 解决方案
65-
66- 核心思路:** 将 OkHttp 响应中的 ` Set-Cookie ` 同步到 WebView 的 ` CookieManager ` ** 。
6773
68- ### 修复后的代码
69-
70- ``` kotlin
7174/* *
72- * 将响应中的 Set-Cookie 同步到 CookieManager
75+ * 将 OkHttp 响应中的 Set-Cookie 写入 CookieManager
7376 */
7477private fun syncCookiesToManager (url : String , response : okhttp3.Response ) {
7578 val cookies = response.headers(" Set-Cookie" )
7679 if (cookies.isNotEmpty()) {
7780 val cookieManager = android.webkit.CookieManager .getInstance()
7881 cookies.forEach {
79- Timber .d(" syncCookiesToManager: $url -> $it " )
8082 cookieManager.setCookie(url, it)
8183 }
82- cookieManager.flush() // 确保立即持久化
83- }
84- }
85-
86- private fun getResponseByOkHttp (request : WebResourceRequest ? ): WebResourceResponse ? {
87- request ? : return null
88- val url = request.url.toString()
89- val requestBuilder = okhttp3.Request .Builder ()
90- .url(url)
91- .method(request.method, null )
92-
93- // ✅ 直接使用 requestHeaders
94- // WebView 在调用 shouldInterceptRequest 时已经从 CookieManager 获 取了 Cookie
95- request.requestHeaders?.forEach { (key, value) ->
96- requestBuilder.addHeader(key, value)
97- }
98-
99- val response = okhttpClient.newCall(requestBuilder.build()).execute()
100-
101- // ✅ 关键:同步响应中的 Cookie 到 CookieManager
102- // 注意:要在判断状态码之前同步,因为 302 重定向也可能带 Set-Cookie
103- syncCookiesToManager(url, response)
104-
105- if (response.code != 200 ) {
106- response.close()
107- return null
108- }
109-
110- val body = response.body ? : return null
111- // ... 构建 WebResourceResponse
112- }
113- ```
114-
115- ### 关键点解释
116-
117- 1 . ** 为什么只需要处理响应的 Cookie?**
118-
119- 因为 ` WebResourceRequest.requestHeaders ` 已经包含了 WebView 从 ` CookieManager ` 获取的 Cookie。WebView 在调用 ` shouldInterceptRequest ` 之前 ,会自动把当前 URL 对应的 Cookie 放进 ` requestHeaders ` 里。
120-
121- 2 . ** 为什么要在判断状态码之前同步 Cookie?**
122-
123- HTTP 302 重定向响应也可能携带 ` Set-Cookie ` 。如果先判断状态码再同步,就会丢失这些 Cookie。
124-
125- 3 . ** ` flush() ` 的作用**
126-
127- ` CookieManager.setCookie() ` 是异步的,调用 ` flush() ` 可以确保 Cookie 立即持久化到磁盘,避免因为进程被杀而丢失。
128-
129- ## 验证方法
130-
131- 1 . 在 ` syncCookiesToManager ` 添加日志,确认 Cookie 被正确同步
132- 2 . 测试登录流程:登录 → 刷新页面 → 确认用户状态保持
133- 3 . 测试验证码场景:获取验证码图片 → 提交验证码 → 确认校验通过
134-
135- ## 其他注意事项
136-
137- ### 不适合拦截的域名
138-
139- 某些第三方域名(如支付页面)可能有额外的安全校验,建议排除:
140-
141- ``` kotlin
142- val FILTER_HOSTS = listOf (
143- " qq.com" , // 腾讯
144- " alipay.com" , // 支付宝
145- // ...
146- )
147-
148- private fun shouldInterceptToOkhttp (request : WebResourceRequest ? ): Boolean {
149- val url = request?.url ? : return false
150- if (" GET" .equals(request.method, true )) {
151- // 过滤掉不需要拦截的域名
152- return FILTER_HOSTS .none { url.toString().contains(it) }
84+ cookieManager.flush() // 确保立即持久化到本地,防止进程被杀导致丢失
15385 }
154- return false
15586}
15687```
15788
158- ### HTTPDNS 可能带来的问题
159-
160- 使用 HTTPDNS 会让请求直连 IP,但 HTTP 请求的 ` Host ` 头仍然是域名。某些 服务端可能对 IP 和 Host 做额外校验,需要注意。
89+ ## 扩展建议
16190
162- ## 总结
91+ ** 1. 过滤第三方域名**
92+ 并非所有请求都适合拦截。涉及第三方支付、授权的网页,建议通过白名单/黑名单机制过滤,让原生 WebView 自行处理,避免引发未知的安全或跨域 Cookie 异常。
16393
164- WebView 请求拦截是把双刃剑。用得好可以优化网络性能,用不好就会导致各种 诡异的会话问题。关键是要记住:
94+ ** 2. HTTPDNS 附带效应**
95+ 使用 HTTPDNS 替换域名为 IP 后发起请求,其真实的 ` Host ` 头需要手动补全,否则可能触发 CDN 阻断或服务端 Host 校验严格环境下的 400 错误。
16596
166- > ** 拦截了请求,就要接管 Cookie 同步的责任。 **
97+ ## 小结
16798
168- 希望这篇文章能帮助你少踩一个坑 。
99+ WebView 请求拦截是优化利器,但 ** 拦截了请求,就必须完整接管并维护 HTTP 的状态机(Cookie/Session) ** 。构建稳定的双向 Cookie 同步机制,是此类架构落地的必修课 。
0 commit comments