为什么 Android WebView 偶尔不发送我的会话 cookie?

我有一个服务器,发送我的安卓应用程序会话 cookie,用于认证通信。我试图加载一个网络视图与一个 URL 指向同一个服务器,我尝试传入会话 cookie 进行身份验证。我观察到它间歇性地工作,但我不知道为什么。我使用相同的会话 cookie 在我的服务器上进行其他调用,这些调用从不会失败。我只有在尝试在 WebView 中加载 URL 时才会发现这个问题,而且这种情况并不是每次都会发生。非常令人沮丧。

下面是我正在使用的代码,任何帮助将不胜感激。

String myUrl = "http://example.com/";
CookieSyncManager.createInstance(this);
CookieManager cookieManager = CookieManager.getInstance();
Cookie sessionCookie =  getCookie();
if(sessionCookie != null){
String cookieString = sessionCookie.getName() +"="+sessionCookie.getValue()+"; domain="+sessionCookie.getDomain();
cookieManager.setCookie(myUrl, cookieString);
CookieSyncManager.getInstance().sync();
}


WebView webView = (WebView) findViewById(R.id.webview);
webView.getSettings().setBuiltInZoomControls(true);
webView.getSettings().setJavaScriptEnabled(true);
webView.setWebViewClient(new MyWebViewClient());
webView.loadUrl(myUrl);
175840 次浏览

I would save that session cookie as a preference and forcefully repopulate the cookie manager with it. It sounds that session cookie in not surviving Activity restart

I've spent the greater half of 3 hours working on a very similar issue. In my case I had a number of calls, that I made to a web service using a DefaulHttpClient and then I wanted to set the session and all other corresponding cookies in my WebView.

I don't know if this will solve your problem, since I don't know what your getCookie() method does, but in my case I actually had to call.

cookieManager.removeSessionCookie();

First to remove the session cookie and then re-add it. I was finding that when I tried to set the JSESSIONID cookie without first removing it, the value I wanted to set it to wasn't being save. Not sure if this will help you particular issue, but thought I'd share what I had found.

Thanks justingrammens! That worked for me, I managed to share the cookie within my DefaultHttpClient requests and WebView activity:

//------- Native request activity
private DefaultHttpClient httpClient;
public static Cookie cookie = null;


//After Login
List<Cookie> cookies = httpClient.getCookieStore().getCookies();
for (int i = 0; i < cookies.size(); i++) {
cookie = cookies.get(i);
}


//------- Web Browser activity
Cookie sessionCookie = myapp.cookie;
CookieSyncManager.createInstance(this);
CookieManager cookieManager = CookieManager.getInstance();
if (sessionCookie != null) {
cookieManager.removeSessionCookie();
String cookieString = sessionCookie.getName() + "=" + sessionCookie.getValue() + "; domain=" + sessionCookie.getDomain();
cookieManager.setCookie(myapp.domain, cookieString);
CookieSyncManager.getInstance().sync();
}

the solution is to give the Android enough time to proccess cookies. You can find more information here: http://code.walletapp.net/post/46414301269/passing-cookie-to-webview

Solution:Webview CookieSyncManager

CookieSyncManager cookieSyncManager = CookieSyncManager.createInstance(mWebView.getContext());
CookieManager cookieManager = CookieManager.getInstance();
cookieManager.setAcceptCookie(true);
cookieManager.removeSessionCookie();
cookieManager.setCookie("http://xx.example.com","mid="+MySession.GetSession().sessionId+" ; Domain=.example.com");
cookieSyncManager.sync();


String cookie = cookieManager.getCookie("http://xx.example.com");


Log.d(LOGTAG, "cookie ------>"+cookie);
mWebView.getSettings().setJavaScriptEnabled(true);
mWebView.setWebViewClient(new TuWebViewClient());
mWebView.loadUrl("http://xx.example.com");

I have a different approach from other people here, and it an approach that is guaranteed work without dealing with the CookieSyncManager (where you are at the mercy of semantics like "Note that even sync() happens asynchronously").

Essentially, we browse to the correct domain, then we execute javascript from the page context to set cookies for that domain (the same way the page itself would). Two drawbacks to the method are that may introduce an extra round trip time due to the extra http request you have to make; and if your site does not have the equivalent of a blank page, it may flash whatever URL you load first before taking you to the right place.

import org.apache.commons.lang.StringEscapeUtils;
import org.apache.http.cookie.Cookie;
import android.annotation.SuppressLint;
import android.webkit.CookieManager;
import android.webkit.CookieSyncManager;
import android.webkit.WebView;
import android.webkit.WebViewClient;


public class WebViewFragment {
private static final String BLANK_PAGE = "/blank.html"


private CookieSyncManager mSyncManager;
private CookieManager mCookieManager;


private String mTargetUrl;
private boolean mInitializedCookies;
private List<Cookie> mAllCookies;


public WebViewFragment(Context ctx) {
// We are still required to create an instance of Cookie/SyncManager.
mSyncManager = CookieSyncManager.createInstance(ctx);
mCookieManager = CookieManager.getInstance();
}


@SuppressLint("SetJavaScriptEnabled") public void loadWebView(
String url, List<Cookie> cookies, String domain) {
final WebView webView = ...


webView.setWebViewClient(new CookeWebViewClient());
webView.getSettings().setJavaScriptEnabled(true);


mInitializedCookies = false;
mTargetUrl = url;
mAllCookies = cookies;
// This is where the hack starts.
// Instead of loading the url, we load a blank page.
webView.loadUrl("http://" + domain + BLANK_PAGE);
}


public static String buildCookieString(final Cookie cookie) {
// You may want to add the secure flag for https:
// + "; secure"
// In case you wish to convert session cookies to have an expiration:
// + "; expires=Thu, 01-Jan-2037 00:00:10 GMT"
// Note that you cannot set the HttpOnly flag as we are using
// javascript to set the cookies.
return cookie.getName() + "=" + cookie.getValue()
+ "; path=" + cookie.getPath()
+ "; domain=" + cookie.getDomain()
};


public synchronized String generateCookieJavascript() {
StringBuilder javascriptCode = new StringBuilder();
javascriptCode.append("javascript:(function(){");
for (final Cookie cookie : mAllCookies) {
String cookieString = buildCookieString(cookie);
javascriptCode.append("document.cookie=\"");
javascriptCode.append(
StringEscapeUtils.escapeJavascriptString(cookieString));
javascriptCode.append("\";");
}
// We use javascript to load the next url because we do not
// receive an onPageFinished event when this code finishes.
javascriptCode.append("document.location=\"");
javascriptCode.append(
StringEscapeUtils.escapeJavascriptString(mTargetUrl));
javascriptCode.append("\";})();");
return javascriptCode.toString();
}


private class CookieWebViewClient extends WebViewClient {
@Override public void onPageFinished(WebView view, String url) {
super.onPageFinished(view, url);
if (!mInitializedCookies) {
mInitializedCookies = true;
// Run our javascript code now that the temp page is loaded.
view.loadUrl(generateCookieJavascript());
return;
}
}
}
}

If you trust the domain the cookies are from, you may be able to get away without apache commons, but you have to understand that this can present a XSS risk if you are not careful.

This is a working bit of code.

    private void setCookie(DefaultHttpClient httpClient, String url) {
List<Cookie> cookies = httpClient.getCookieStore().getCookies();
if (cookies != null) {
CookieSyncManager.createInstance(context);
CookieManager cookieManager = CookieManager.getInstance();
cookieManager.setAcceptCookie(true);


for (int i = 0; i < cookies.size(); i++) {
Cookie cookie = cookies.get(i);
String cookieString = cookie.getName() + "=" + cookie.getValue();
cookieManager.setCookie(url, cookieString);
}
CookieSyncManager.getInstance().sync();
}
}

Here the httpclient is the DefaultHttpClient object you used in the HttpGet/HttpPost request. Also one thing to make sure is the cookie name and value, it should be given

String cookieString = cookie.getName() + "=" + cookie.getValue();

setCookie will the set the cookie for the given URL.

I magically solved all my cookie problems with this one line in onCreate:

CookieHandler.setDefault(new CookieManager());

edit: it stopped working today. :( what the crap, android.

Encountered this too also. Here's what I did.

On my LoginActivity, inside my AsyncTask, I have the following:

CookieStoreHelper.cookieStore = new BasicCookieStore();
BasicHttpContext localContext = new BasicHttpContext();
localContext.setAttribute(ClientContext.COOKIE_STORE, CookieStoreHelper.cookieStore);


HttpResponse postResponse = client.execute(httpPost,localContext);
CookieStoreHelper.sessionCookie = CookieStoreHelper.cookieStore.getCookies();

//WHERE CookieStoreHelper.sessionCookie is another class containing the variable sessionCookie defined as List cookies; and cookieStore define as BasicCookieStore cookieStore;

Then on my Fragment, where my WebView is located i have the following:

//DECLARE LIST OF COOKIE
List<Cookie> sessionCookie;

inside my method or just before you are setting the WebViewClient()

WebSettings settings = webView.getSettings();
settings.setJavaScriptEnabled(true);
webView.setScrollBarStyle(WebView.SCROLLBARS_OUTSIDE_OVERLAY);


sessionCookie = CookieStoreHelper.cookieStore.getCookies();
CookieSyncManager.createInstance(webView.getContext());
CookieSyncManager.getInstance().startSync();
CookieManager cookieManager = CookieManager.getInstance();
CookieManager.getInstance().setAcceptCookie(true);
if (sessionCookie != null) {
for(Cookie c:  sessionCookie){
cookieManager.setCookie(CookieStoreHelper.DOMAIN, c.getName() + "=" + c.getValue());
}
CookieSyncManager.getInstance().sync();


}


webView.setWebViewClient(new WebViewClient() {
//AND SO ON, YOUR CODE
}

Quick Tip: Have firebug installed on firefox or use developer console on chrome and test first your webpage, capture the Cookie and check the domain so you can store it somewhere and be sure that you are correctly setting the right domain.

Edit: edited CookieStoreHelper.cookies to CookieStoreHelper.sessionCookie

Don't use your raw url

Instead of:

cookieManager.setCookie(myUrl, cookieString);

use it like this:

cookieManager.setCookie("your url host", cookieString);

My working code

public View onCreateView(...){
mWebView = (WebView) view.findViewById(R.id.webview);


WebSettings webSettings = mWebView.getSettings();
webSettings.setJavaScriptEnabled(true);


...
...
...


CookieSyncManager.createInstance(mWebView.getContext());
CookieManager cookieManager = CookieManager.getInstance();
cookieManager.setAcceptCookie(true);
//cookieManager.removeSessionCookie(); // remove
cookieManager.removeAllCookie(); //remove
// Recommended "hack" with a delay between the removal and the installation of "Cookies"
SystemClock.sleep(1000);


cookieManager.setCookie("https://my.app.site.com/", "cookiename=" + value + "; path=/registration" + "; secure"); // ;
CookieSyncManager.getInstance().sync();


mWebView.loadUrl(sp.getString("url", "") + end_url);


return view;
}

To debug the query, "cookieManager.setCookie (....);" I recommend you to look through the contents of the database webviewCookiesChromium.db (stored in "/data/data/my.app.webview/database") There You can see the correct settings.

Disabling "cookieManager.removeSessionCookie();" and/or "cookieManager.removeAllCookie();"

//cookieManager.removeSessionCookie();
// and/or
//cookieManager.removeAllCookie();"

Compare the set value with those that are set by the browser. Adjust the request for the installation of the cookies before until "flags" browser is not installed will fit with what You decide. I found that a query can be "flags":

// You may want to add the secure flag for https:
+ "; secure"
// In case you wish to convert session cookies to have an expiration:
+ "; expires=Thu, 01-Jan-2037 00:00:10 GMT"
// These flags I found in the database:
+ "; path=/registration"
+ "; domain=my.app.site.com"

Thanks Android for ruining my Sunday . . . Heres what fixed my Apps ( after you init your webview )

if ( Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP ) {


CookieManager cookieManager = CookieManager.getInstance();


cookieManager.setAcceptThirdPartyCookies( webView, true );


}

I should say the above answers will probably work but in my situation the moment Android went v5+ my android webview javascript 'apps' died.

Couple of comments (at least for APIs >= 21) which I found out from my experience and gave me headaches:

  1. http and https urls are different. Setting a cookie for http://www.example.com is different than setting a cookie for https://www.example.com
  2. A slash in the end of the url can also make a difference. In my case https://www.example.com/ works but https://www.example.com does not work.
  3. CookieManager.getInstance().setCookie is performing an asynchronous operation. So, if you load a url right away after you set it, it is not guaranteed that the cookies will have already been written. To prevent unexpected and unstable behaviours, use the CookieManager#setCookie(String url, String value, ValueCallback callback) (link) and start loading the url after the callback will be called.

I hope my two cents save some time from some people so you won't have to face the same problems like I did.

I have faced same problem and It will resolve this issue in all android versions

private void setCookie() {
try {
CookieSyncManager.createInstance(context);
CookieManager cookieManager = CookieManager.getInstance();
cookieManager.setAcceptCookie(true);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
cookieManager.setCookie(Constant.BASE_URL, getCookie(), value -> {
String cookie = cookieManager.getCookie(Constant.BASE_URL);
CookieManager.getInstance().flush();
CustomLog.d("cookie", "cookie ------>" + cookie);
setupWebView();
});
} else {
cookieManager.setCookie(webUrl, getCookie());
new Handler().postDelayed(this::setupWebView, 700);
CookieSyncManager.getInstance().sync();
}


} catch (Exception e) {
CustomLog.e(e);
}
}

After some time researching I've gathered some pieces that made me get to this solution. Once that CookieSyncManager is deprecated, this may be the best way to set a specific cookie for a webview in Kotlin nowadays, you shouldn't need anything else.

private fun setCookie(){
val webView = WebView(this) // this = context
val cookieManager = CookieManager.getInstance()
cookieManager.acceptCookie()


val domain = "https://www.yourdomain.com/"


webView.webViewClient = WebViewClient()
webView.settings.javaScriptEnabled = true


cookieManager.setCookie(domain,"$cookieKey=$cookieValue")
cookieManager.setAcceptThirdPartyCookies(webView, true)


webView.loadUrl(domain)
}

Note that it may be better use subdomains instead of usual URL. So, set .example.com instead of https://example.com/.

Thanks to Jody Jacobus Geers and others I wrote so:

if (savedInstanceState == null) {
val cookieManager = CookieManager.getInstance()
cookieManager.acceptCookie()
val domain = ".example.com"
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
cookieManager.setCookie(domain, "token=$token") {
view.webView.loadUrl(url)
}
cookieManager.setAcceptThirdPartyCookies(view.webView, true)
} else {
cookieManager.setCookie(domain, "token=$token")
view.webView.loadUrl(url)
}
} else {
// Check whether we're recreating a previously destroyed instance.
view.webView.restoreState(savedInstanceState)
}