App 用 HttpURLConnection POST 登錄,獲取 Cookie,需注入嵌入 WebView 實現免登訪問受保護頁面。
// 1. 登錄成功後,從 HttpURLConnection 獲取 Cookie
String cookieHeader = connection.getHeaderField("Set-Cookie"); // 可能多個,需解析合併
// 例: "SESSION=abc123; Path=/; HttpOnly, USER_ID=456; Path=/"
// 2. 同步到 WebView 的 CookieManager
CookieManager cookieManager = CookieManager.getInstance();
cookieManager.setAcceptCookie(true); // 確保接受 Cookie
// 3. 逐條設置 (需解析域、路徑、屬性)
String url = "https://api.example.com"; // 目標域名
// 簡單粗暴:直接設置完整 Cookie 字串 (API 21+ 支援)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
CookieManager.getInstance().setCookie(url, cookieHeader);
} else {
// 舊版需手動解析 name=value; domain=...; path=...
// 建議用開源庫: cookie-manager 或自行解析
}
// 4. 強制刷新持久化 (API 21+)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
CookieManager.getInstance().flush();
}
public class LoginActivity extends AppCompatActivity {
private WebView webView;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_login);
webView = findViewById(R.id.webview);
WebSettings settings = webView.getSettings();
settings.setJavaScriptEnabled(true);
// 關鍵:允許第三方 Cookie (API 21+)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
CookieManager.getInstance().setAcceptThirdPartyCookies(webView, true);
}
// 異步登錄
new LoginTask().execute("user", "pass");
}
private class LoginTask extends AsyncTask {
@Override
protected String doInBackground(String... params) {
try {
URL url = new URL("https://api.example.com/login");
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
conn.setRequestMethod("POST");
conn.setDoOutput(true);
// 寫入表單數據...
// 獲取 Cookie
String cookie = conn.getHeaderField("Set-Cookie");
return cookie;
} catch (IOException e) {
return null;
}
}
@Override
protected void onPostExecute(String cookie) {
if (cookie != null) {
// 同步 Cookie
String domain = "https://api.example.com";
CookieManager.getInstance().setCookie(domain, cookie);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
CookieManager.getInstance().flush();
}
// 載入受保護頁面
webView.loadUrl("https://api.example.com/protected/page");
} else {
Toast.makeText(LoginActivity.this, "登錄失敗", Toast.LENGTH_SHORT).show();
}
}
}
}
// 登錄成功後保存到 SharedPreferences
SharedPreferences prefs = getSharedPreferences("auth", MODE_PRIVATE);
prefs.edit().putString("cookie", cookie).apply();
// App 啟動時恢復
String savedCookie = prefs.getString("cookie", null);
if (savedCookie != null) {
CookieManager.getInstance().setCookie(domain, savedCookie);
CookieManager.getInstance().flush();
}
| 問題 | 原因 | 解決 |
|---|---|---|
| WebView 仍提示未登錄 | Cookie 域/路徑不匹配、HttpOnly 無法讀取 | 確保 setCookie 域名與 WebView 載入 URL 域名一致 |
| API < 21 setCookie 失效 | 舊版不支援完整 Cookie 字串解析 | 手動解析 name=value; domain=...; path=... 逐條 setCookie |
| 第三方 Cookie 被瀏覽器阻擋 | Android 5.0+ 默認阻擋第三方 Cookie | setAcceptThirdPartyCookies(webView, true) |
| 重定向丟失 Cookie | HttpURLConnection 默認不自動處理重定向 Cookie | conn.setInstanceFollowRedirects(false) 手動處理重定向並合併 Cookie |
OkHttp 自動管理 CookieJar,攔截器中同步到 CookieManager 更優雅:
OkHttpClient client = new OkHttpClient.Builder()
.cookieJar(new CookieJar() {
private final HashMap> store = new HashMap<>();
@Override public void saveFromResponse(HttpUrl url, List cookies) {
store.put(url, cookies);
syncToWebView(url, cookies); // 同步到 WebView
}
@Override public List loadForRequest(HttpUrl url) {
return store.getOrDefault(url, Collections.emptyList());
}
})
.build();