Reputation: 14289
Running 3.1 (Honeycomb) on Galaxy Tab 10.1
Regardless of several different methods, I have been unable to reset the basic auth username and password for a WebView. The only way I can reset these values is to restart the app. I have searched around and have yet to find a solution and even dug into the Android source.
This code is from an activity that is created every time I want to display a webpage that requires basic auth. Some parts shouldn't have any effect but were tried out of frustration. Even when I exit this activity (which is then destroyed) and relaunch it with an intent from my main activity, the basic auth information remains and onReceivedHttpAuthRequest in the WebViewClient is never executed again.
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.base_simple_v01);
findViewById(R.id.lyt_bsv01_layout).setBackgroundColor(0xFF000000);
baseContainer = (ViewGroup) findViewById(R.id.lyt_bsv01_baseContainer);
statusProgressBar = (ProgressBar) findViewById(R.id.lyt_bsv01_statusProgress);
resultNotificationTextView = (TextView) findViewById(R.id.lyt_bsv01_resultNotification);
// -- Attempt to prevent and clear WebView cookies
CookieSyncManager.createInstance(this);
CookieManager cookieManager = CookieManager.getInstance();
cookieManager.removeAllCookie();
cookieManager.removeSessionCookie();
cookieManager.setAcceptCookie(false);
// -- Attempt to clear WebViewDatabase
WebViewDatabase.getInstance(this).clearHttpAuthUsernamePassword();
WebViewDatabase.getInstance(this).clearUsernamePassword();
WebViewDatabase.getInstance(this).clearFormData();
// -- Brute force attempt to clear WebViewDatabase - didn't work
//deleteDatabase("webview.db");
//deleteDatabase("webviewCache.db");
LayoutInflater vi = (LayoutInflater) getSystemService(Context.LAYOUT_INFLATER_SERVICE);
networkWebView = (WebView)vi.inflate(R.layout.social_connect, baseContainer, false);
// -- Removes white flickering in Honeycomb WebView page loading.
networkWebView.setLayerType(View.LAYER_TYPE_SOFTWARE, null);
networkWebView.getSettings().setJavaScriptEnabled(true);
networkWebView.getSettings().setSavePassword(false);
networkWebView.getSettings().setCacheMode(WebSettings.LOAD_NO_CACHE);
networkWebView.clearSslPreferences();
networkWebView.setWebViewClient(mLocalDataRequester.endorseBackendAuthWebViewClient(
new BackendAuthWebViewClient() {
@Override
public void onReceivedHttpAuthRequest (WebView view, HttpAuthHandler handler, String host, String realm) {
Toast.makeText(getApplicationContext(), "AUTH REQUESTED", Toast.LENGTH_SHORT).show();
super.onReceivedHttpAuthRequest (view, handler, host, realm);
}
@Override
public void onReceivedSslError(WebView view, SslErrorHandler handler, SslError error) {
Toast.makeText(getApplicationContext(), "SSL ERROR", Toast.LENGTH_SHORT).show();
super.onReceivedSslError(view, handler, error);
}
@Override
public void onPageStarted(WebView view, String url, Bitmap favicon) {
statusProgressBar.setVisibility(View.VISIBLE);
networkWebView.setVisibility(View.INVISIBLE);
}
@Override
public void onPageFinished(WebView view, String url) {
statusProgressBar.setVisibility(View.INVISIBLE);
networkWebView.setVisibility(View.VISIBLE);
}
})
);
baseContainer.addView(networkWebView);
networkWebView.setVisibility(View.INVISIBLE);
networkWebView.setBackgroundColor(0x00000000);
clearWebView();
}
private void clearWebView() {
networkWebView.loadData("", "text/html", "utf-8");
//networkWebView.clearView();
networkWebView.clearCache(false);
networkWebView.clearCache(true);
networkWebView.clearFormData();
networkWebView.clearHistory();
networkWebView.clearCache(true);
networkWebView.clearMatches();
networkWebView.freeMemory();
}
@Override
public void onResume() {
super.onResume();
networkWebView.loadUrl(mBackendNetworkConnectUrl);
WebViewDatabase.getInstance(this).clearHttpAuthUsernamePassword();
}
@Override
public void onDestroy() {
super.onDestroy();
Toast.makeText(getApplicationContext(), "Destruction", Toast.LENGTH_SHORT).show();
networkWebView.destroy();
}
This is a WebViewClient subclass that is initialized with the basic auth credentials. I have verified that the username and password change when an authentication should occur.
public class BackendAuthWebViewClient extends WebViewClient {
private AuthenticateData mAuthenticateData = null;
public BackendAuthWebViewClient() {
}
public BackendAuthWebViewClient(AuthenticateData authenticateData) {
this.mAuthenticateData = authenticateData;
}
@Override
public void onReceivedHttpAuthRequest (WebView view, HttpAuthHandler handler, String host, String realm){
handler.proceed(mAuthenticateData.mUserId, mAuthenticateData.mUserPassword);
}
@Override
public void onReceivedSslError(WebView view, SslErrorHandler handler, SslError error) {
handler.proceed();
}
public void setAuthenticatedData(AuthenticateData authenticateData) {
this.mAuthenticateData = authenticateData;
}
}
I have tried the following to no avail:
Android WebView - reset HTTP session
Clearing user's Facebook session in Webview
Make Android WebView not store cookies or passwords
Android WebView Cookie Problem
This is interesting but necessity of the brute force would be disappointing. Though I'll try it next.
EDIT: Didn't work.
Android Webview - Completely Clear the Cache
Upvotes: 2
Views: 9872
Reputation: 9284
I have another solution that seems to work fairly well. Basically you load the URL using WebView.loadUrl(url, additionalHeaders) and pass a blank Authorization header in. This seems to reset the webview properly. The only issue is that you'll the onReceivedHttpAuthRequest will get called in a loop if you don't reload the original URL. So once you get the onReceivedHttpAuthRequest you will need to collect the username/password from the user, and then reload the original URL without passing a blank Authorization header.
Essentially it works like this below (specific code is untested).
MyLoginActivity extends AppCompatActivity {
private boolean clearAuth = true;
private String mUsername, mPassword, mHost, mRealm;
private static final String URL = "https://yourdomain.com";
public void onCreate(bundle ss) {
super.onCreate(ss);
webview = findViewById(R.id.webview);
webview.setWebViewClient(new WebViewClient() {
@Override
public void onReceivedHttpAuthRequest(View webview, HttpAuthHandler authHandler, String host, String realm) {
mAuthHandler = authHandler;
mHost = host;
mRealm = realm;
if (mUsername != null && mPassword != null && authHandler.useHttpAuthUsernamePassword()) {
proceed(mUsername, mPassword);
} else {
showLoginDialog();
}
});
}
public void onDestroy() {
super.onDestroy();
//ensure an auth handler that was displayed but never finished is cancelled
//if you don't do this the app will start hanging and acting up after pressing back when a dialog was visible
if (mAuthHandler != null) {
mAuthHandler.cancel();
}
mAuthHandler = null;
}
public void onCredentialsProvided(String username, String password) {
mUsername = username;
mPassword = password;
if (clearAuth) {
//reload the original URL but this time without adding the blank
//auth header
clearAuth = false;
loadUrl(URL);
} else {
proceed(username, password);
}
}
private void loadUrl(String url) {
if (clearAuth) {
Map<String, String> clearAuthMap = new HashMap();
map.put("Authorization", "");
webView.loadUrl(url, clearAuthMap);
} else {
webView.loadUrl(url);
}
}
private void proceed(String username, String password) {
mAuthHandler.proceed(username, password);
mAuthHandler = null;
}
private void showLoginDialog() {
//show your login dialog here and when user presses submit, call
//activity.onCredentialsProvided(username, password);
}
}
Upvotes: 1
Reputation: 11
I used multi-process to solve this problem.
As WebView in your Activity/Fragment need to handle Http Basic Authentication, onRecievedHttpAuthRequest() will be trigger. Creating a dialog for user to input login info.
onRecievedHttpAuthRequest(final WebView view, final HttpAuthHandler handler, final String host, String realm){
final Dialog dialog = new Dialog(context);
dialog.setContentView(R.layout.dialog_layout);
dialog.findViewById(R.id.confirmBtn).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
final String account = ((EditText)dialog.findViewById(R.id.accountET)).getText().toString();
final String pwd = ((EditText)dialog.findViewById(R.id.pwdET)).getText().toString();
serviceIntent = new Intent(context, SSOAuthService.class);
serviceIntent.putExtra("url", authUrl);
serviceIntent.putExtra("account", account);
serviceIntent.putExtra("pwd", pwd);
context.startService(serviceIntent);
dialog.dismiss();
}
});
dialog.show();
}
Launch a service contains a webview to handle http basic auth, and pass account and pwd get from the dialog above.
public class AuthService extends Service {
private String account;
private String pwd;
private String url;
private String webView;
private boolean isProcess = false;
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
url = (String) intent.getExtras().get("url");
account = (String) intent.getExtras().get("account");
pwd = (String) intent.getExtras().get("pwd");
webView = new WebView(this);
webView.getSettings().setJavaScriptEnabled(true);
webView.setWebViewClient(new WebViewClient() {
@Override
public void onPageFinished(WebView view, String url) {
//todo Do whatever u want to do.
closeServiceAndProcess();
}
@Override
public void onReceivedHttpAuthRequest(final WebView view, final HttpAuthHandler handler, final String host, String realm) {
if (!isProcess) {
isProcess = true;
handler.proceed(account, pwd);
} else {
isProcess = false;
closeServiceAndProcess();
}
}
});
webView.loadUrl(url);
return Service.START_REDELIVER_INTENT;
}
private void closeServiceAndProcess() {
stopSelf();
android.os.Process.killProcess(android.os.Process.myPid());
}
}
As complete http basic authentication in the AuthService, kill the process which the AuthService is lived. And http basic authentication could be reset.
Upvotes: 0
Reputation: 61
I also had this issue. And found the solution, hope this can help you.
First of all, the method onReceivedHttpAuthRequest()
is only called one time in an application except use cookies.
I had write the method:
public void syncCookie(Context context, String url) {
HttpClient httpClient = HttpClientContext.getInstance();
Cookie[] cookies = httpClient.getState().getCookies();
Cookie sessionCookie = null;
if (cookies.length > 0) {
sessionCookie = cookies[cookies.length - 1];
}
CookieManager cookieManager = CookieManager.getInstance();
if (sessionCookie != null) {
String cookieString = sessionCookie.getName() + "="
+ sessionCookie.getValue() + ";domain="
+ sessionCookie.getDomain();
CookieSyncManager cookieSyncManager = CookieSyncManager.createInstance(context);
cookieSyncManager.startSync();
cookieManager.setCookie(url, cookieString);
CookieSyncManager.getInstance().sync();
}
}
use like this:
WebView webView = ...;
webView.getSettings().setJavaScriptEnabled(true);
syncCookie(this,url);
webView.loadUri(url);
webView.setWebViewClient();
Upvotes: 0
Reputation: 3046
It turned out, there are two potential issues here.
One being the WebViewDatabase.clearHttpAuthUsernamePassword()
seems not working correctly on some devices/versions of Android, in a way that calling WebView.getHttpAuthUsernamePassword()
still yields the stored password after clearing the database.
This one can be addressed by implementing these methods yourself.
The second issue is, that the auth data also seems to be stored in memory, which is basically a good thing, because the WebView
has not to query the database for every subsequent HTTP request. However this cache seems to be shared between all WebViews and there is no obvious method to clear it. It turns out though, that WebViews created with privateBrowsing = true
, share a different cache which also behaves slightly different: After the last private browsing WebView is being destroyed, this cache seems to be cleared out completely, and the next request will actually trigger onReceivedHttpAuthRequest
.
Below a complete working example of those two workarounds. If you have to deal with multiple WebViews it might get more complex, because you need to make sure to destroy them all, before recreating them.
public class HttpAuthTestActivity extends Activity {
ViewGroup webViewContainer;
Button logoutButton;
Button reloadButton;
WebView webView;
AuthStoreInterface authStore;
public interface AuthStoreInterface {
public void clear();
public void setHttpAuthUsernamePassword(String host, String realm, String username, String password);
public Pair<String, String> getHttpAuthUsernamePassword(String host, String realm);
}
//if you want to make the auth store persistent, you have implement a persistent version of this interface
public class MemoryAuthStore implements AuthStoreInterface {
Map<Pair<String, String>, Pair<String, String>> credentials;
public MemoryAuthStore() {
credentials = new HashMap<Pair<String, String>, Pair<String, String>>();
}
public void clear() {
credentials.clear();
}
public void setHttpAuthUsernamePassword(String host, String realm, String username, String password) {
credentials.put(new Pair<String, String>(host, realm), new Pair<String, String>(username, password));
}
public Pair<String, String> getHttpAuthUsernamePassword(String host, String realm) {
return credentials.get(new Pair<String, String>(host, realm));
}
}
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
authStore = new MemoryAuthStore();
webViewContainer = (ViewGroup)findViewById(R.id.webview_container);
logoutButton = (Button)findViewById(R.id.logout_button);
reloadButton = (Button)findViewById(R.id.reload_button);
createWebView();
logoutButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
authStore.clear();
destroyWebView();
createWebView();
}
});
reloadButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
webView.reload();
}
});
}
@Override
protected void onDestroy() {
webView.destroy();
super.onDestroy();
}
private void destroyWebView() {
webView.destroy();
webViewContainer.removeView(webView);
}
private void createWebView() {
//this is the important line: if you use this ctor with privateBrowsing: true, the internal auth cache will
//acutally be deleted in WebView.destroy, if there is no other privateBrowsing enabled WebView left only
webView = new WebView(this, null, android.R.attr.webViewStyle, true);
webView.setWebViewClient(new WebViewClient() {
@Override
public void onReceivedHttpAuthRequest(final WebView view, final HttpAuthHandler handler, final String host, final String realm) {
Pair<String, String> credentials = authStore.getHttpAuthUsernamePassword(host, realm);
if (credentials != null && handler.useHttpAuthUsernamePassword()) {
handler.proceed(credentials.first, credentials.second);
} else {
LayoutInflater inflater = (LayoutInflater)getSystemService(Context.LAYOUT_INFLATER_SERVICE);
final View form = inflater.inflate(R.layout.http_auth_request, null);
new AlertDialog.Builder(HttpAuthTestActivity.this).setTitle(String.format("HttpAuthRequest (realm: %s, host %s)", realm, host))
.setView(form).setPositiveButton(android.R.string.ok, new AlertDialog.OnClickListener() {
public void onClick(DialogInterface dialog, int which) {
EditText usernameEdt = (EditText) form.findViewById(R.id.username);
EditText passwordEdt = (EditText) form.findViewById(R.id.password);
String u = usernameEdt.getText().toString();
String p = passwordEdt.getText().toString();
authStore.setHttpAuthUsernamePassword(host, realm, u, p);
handler.proceed(u, p);
}
}).setCancelable(true).setNegativeButton(android.R.string.cancel, new AlertDialog.OnClickListener() {
public void onClick(DialogInterface dialog, int which) {
dialog.cancel();
}
}).setOnCancelListener(new DialogInterface.OnCancelListener() {
public void onCancel(DialogInterface dialog) {
handler.cancel();
}
}).create().show();
}
}
});
webView.loadUrl("http://httpbin.org/basic-auth/test/test");
webViewContainer.addView(webView);
}
}
Upvotes: 0
Reputation: 3972
Issue 25507: WebViewDatabase.clearHttpAuthUsernamePassword() does not work
Upvotes: 1
Reputation: 774
I suggest not actually calling setHttpAuthUsernamePassword() at all.
Rather, only use onReceivedHttpAuthRequest() to handle the auth challenge dynamically every time.
This, coupled with
WebViewDatabase.getInstance(getContext()).clearHttpAuthUsernamePassword();
WebViewDatabase.getInstance(getContext()).clearUsernamePassword();
WebViewDatabase.getInstance(getContext()).clearFormData();
calls on load to clear out legacy entries, and my problem with this went away.
Upvotes: 0
Reputation: 3046
I am pretty sure its a bug in WebViewDatabase.getInstance(this).clearHttpAuthUsernamePassword();
,
because
deleteDatabase("webview.db");
does the trick for me.
Upvotes: 1
Reputation: 14289
Although it doesn't address the original reset WebView basic auth issue, I'm using this as a workaround. Using this SO as a reference:
This solution uses a HttpClient request (preferably in another thread or AsyncTask to avoid ANR - application not responding) and then loading that response into a WebView. Since I need to interact with links on the loaded page, I need to use loadDataWithBaseURL.
For this answer, I license all code below under Apache License 2.0.
HttpClient code - best used in another thread or AsyncTask. Variables authenticateData, method, url, and nameValuePairs will need to be defined or removed.
public String send() {
try {
// -- Create client.
HttpParams httpParameters = new BasicHttpParams();
// Set the timeout in milliseconds until a connection is established.
int timeoutConnection = 10000;
HttpConnectionParams.setConnectionTimeout(httpParameters, timeoutConnection);
// Set the default socket timeout (SO_TIMEOUT)
// in milliseconds which is the timeout for waiting for data.
int timeoutSocket = 10000;
HttpConnectionParams.setSoTimeout(httpParameters, timeoutSocket);
DefaultHttpClient httpClient = new DefaultHttpClient(httpParameters);
HttpGet httpGet;
HttpPost httpPost;
HttpDelete httpDelete;
HttpResponse httpResponse;
String authHeader;
if( authenticateData != null ) {
// -- Set basic authentication in header.
String base64EncodedCredentials = Base64.encodeToString(
(authenticateData.username + ":" + authenticateData.password).getBytes("US-ASCII"), Base64.URL_SAFE|Base64.NO_WRAP);
authHeader = "Basic " + base64EncodedCredentials;
} else {
authHeader = null;
}
// -- Send to server.
if( method == GET ) {
httpGet = new HttpGet(url);
if( authHeader != null ) {
httpGet.setHeader("Authorization", authHeader);
}
httpResponse = httpClient.execute(httpGet);
}
else if( method == POST) {
httpPost = new HttpPost(url);
if( authHeader != null ) {
httpPost.setHeader("Authorization", authHeader);
}
httpPost.setEntity(new UrlEncodedFormEntity(nameValuePairs));
httpResponse = httpClient.execute(httpPost);
}
else if( method == DELETE) {
httpDelete = new HttpDelete(url);
httpDelete.setHeader("Content-Length", "0");
if( authHeader != null ) {
httpDelete.setHeader("Authorization", authHeader);
}
httpResponse = httpClient.execute(httpDelete);
}
else {
return null;
}
// -- Method 1 for obtaining response.
/*
InputStream is = httpResponse.getEntity().getContent();
// -- Convert response.
Scanner scanner = new Scanner(is);
// -- TODO: specify charset
String response = scanner.useDelimiter("\\A").next();
*/
// -- Method 2 for obtaining response.
String response = new BasicResponseHandler().handleResponse(httpResponse);
return response;
}
catch(SocketTimeoutException exception) {
exception.printStackTrace();
}
catch(ConnectTimeoutException exception) {
exception.printStackTrace();
}
catch(NoHttpResponseException exception) {
exception.printStackTrace();
}
catch(UnknownHostException exception) {
exception.printStackTrace();
}
catch(ClientProtocolException exception) {
exception.printStackTrace();
}
catch(IOException exception) {
exception.printStackTrace();
}
return null;
}
WebView code - should be in the activity that contains the WebView.
WebView webView = new WebView(Activity.this);
webView.loadDataWithBaseURL(url, response, "text/html", "utf-8", null);
Upvotes: 0