Android WebView - 第一次无法再次设置基本身份验证

时间:2011-11-27 03:15:20

标签: android webview http-basic-authentication

在Galaxy Tab 10.1上运行3.1(Honeycomb)

无论使用多种方法,我都无法重置WebView的基本身份验证用户名和密码。我可以重置这些值的唯一方法是重新启动应用程序。我已经四处搜索,还没有找到解决方案,甚至挖到Android源代码。

此代码来自每次要显示需要基本身份验证的网页时创建的活动。有些部分应该没有任何效果,但是试图摆脱挫败感。即使我退出此活动(然后销毁)并使用我的主活动的意图重新启动它,基本的身份验证信息仍然存在,并且WebViewClient中的onReceivedHttpAuthRequest永远不会再次执行。

@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();
}

这是一个使用基本身份验证凭据初始化的WebViewClient子类。我已经验证了在进行身份验证时用户名和密码会发生变化。

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;
    }

}

我试过以下无济于事:

Android WebView - reset HTTP session

Clearing user's Facebook session in Webview

Delete data in the browser

Make Android WebView not store cookies or passwords

Android WebView Cookie Problem

这很有趣,但蛮力的必要性令人失望。虽然我接下来会尝试。

编辑:无效。

Android Webview - Completely Clear the Cache

8 个答案:

答案 0 :(得分:1)

我很确定它是WebViewDatabase.getInstance(this).clearHttpAuthUsernamePassword();中的一个错误, 因为

deleteDatabase("webview.db");

为我做了诀窍。

答案 1 :(得分:1)

答案 2 :(得分:0)

虽然它没有解决原始重置WebView基本身份验证问题,但我将其用作解决方法。使用此SO作为参考:

Android Webview POST

此解决方案使用HttpClient请求(最好在另一个线程或AsyncTask中以避免ANR - 应用程序没有响应),然后将该响应加载到WebView中。由于我需要与加载页面上的链接进行交互,因此我需要使用loadDataWithBaseURL。

对于这个答案,我在Apache License 2.0.

下授权了以下所有代码

HttpClient代码 - 最好用于另一个线程或AsyncTask。需要定义或删除变量authenticateData,method,url和nameValuePairs。

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代码 - 应该在包含WebView的活动中。

WebView webView = new WebView(Activity.this);
webView.loadDataWithBaseURL(url, response, "text/html", "utf-8", null);

答案 3 :(得分:0)

我建议不要实际调用setHttpAuthUsernamePassword()。

相反,每次只使用onReceivedHttpAuthRequest()来动态处理auth挑战。

这个,再加上

WebViewDatabase.getInstance(getContext()).clearHttpAuthUsernamePassword();
WebViewDatabase.getInstance(getContext()).clearUsernamePassword();
WebViewDatabase.getInstance(getContext()).clearFormData();

调用加载来清除遗留条目,我的问题就消失了。

答案 4 :(得分:0)

事实证明,这里有两个潜在的问题。

WebViewDatabase.clearHttpAuthUsernamePassword()似乎无法在Android的某些设备/版本上正常工作,因为在清除数据库后调用WebView.getHttpAuthUsernamePassword()仍会产生存储的密码。

可以通过自己实施这些方法解决这个问题。

第二个问题是,auth数据似乎也存储在内存中,这基本上是件好事,因为WebView不必为每个后续HTTP请求查询数据库。但是,此缓存似乎在所有WebView之间共享,并且没有明显的方法可以清除它。事实证明,使用privateBrowsing = true创建的WebView共享一个不同的缓存,其行为也略有不同:在最后一次私有浏览WebView被销毁之后,这个缓存似乎被彻底清除,下一个请求实际上是触发onReceivedHttpAuthRequest

下面是这两个解决方法的完整工作示例。如果你必须处理多个WebView,它可能会变得更复杂,因为你需要确保在重新创建它们之前将它们全部销毁。

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);
    }
}

答案 5 :(得分:0)

我也遇到过这个问题。并找到了解决方案,希望这可以帮到你。 首先,方法onReceivedHttpAuthRequest()仅在应用程序中被调用一次,除了使用cookie。

我写过这个方法:

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();
        }
    }

像这样使用:

WebView webView =  ...;
  webView.getSettings().setJavaScriptEnabled(true);
  syncCookie(this,url);
  webView.loadUri(url);
  webView.setWebViewClient();

答案 6 :(得分:0)

我使用多进程来解决这个问题。

由于您的Activity / Fragment中的WebView需要处理Http Basic Authentication,因此将触发onRecievedHttpAuthRequest()。创建一个用户输入登录信息的对话框。

iex(1)> quote do
...(1)>   fn
...(1)>     unquote()
...(1)>     _ -> true
...(1)>   end
...(1)> end
** (SyntaxError) iex:2: expected clauses to be defined with -> inside: 'fn'

启动服务包含一个webview来处理http basic auth,并从上面的对话框中传递account and pwd get。

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();
}

作为AuthService中的完整http基本身份验证,请终止AuthService所处的进程。并且可以重置http基本身份验证。

答案 7 :(得分:0)

我还有另一种似乎很好用的解决方案。基本上,您使用WebView.loadUrl(url,AdditionalHeaders)加载URL并传递一个空白的Authorization标头。这似乎可以正确重置Webview。唯一的问题是,如果不重新加载原始URL,则会在循环中调用onReceivedHttpAuthRequest。因此,一旦获得onReceivedHttpAuthRequest,您将需要从用户处收集用户名/密码,然后重新加载原始URL,而无需传递空白的Authorization标头。

本质上,它的工作方式如下(未测试特定代码)。

Content