【问题标题】:Android WebView - Cannot set basic auth again after first timeAndroid WebView - 第一次后无法再次设置基本身份验证
【发布时间】:2012-01-07 04:14:01
【问题描述】:

在 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

【问题讨论】:

    标签: android webview basic-authentication


    【解决方案1】:

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

    deleteDatabase("webview.db");
    

    对我有用。

    【讨论】:

    • 事实证明这实际上并不总是对我有用。但是使用不调用 set 的方法(在我的回答中)确实有效。
    • @mike-venzke 是的,您可以自己实现该存储,或者我现在所做的:我存储用户名和领域,并在调用 clearHttpAuthUsernamePassword 之前将所有密码设置为空白密码。
    【解决方案2】:
    【解决方案3】:

    虽然它没有解决最初的重置 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 的 Activity 中。

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

    【讨论】:

      【解决方案4】:

      我建议根本不要调用 setHttpAuthUsernamePassword()。

      相反,每次只使用 onReceivedHttpAuthRequest() 动态处理身份验证挑战。

      这个,再加上

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

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

      【讨论】:

      • 我在 Sprint Note 2 上运行 Android 4.1.2,这并没有解决我的问题。
      【解决方案5】:

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

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

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

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

      下面是这两种解决方法的完整工作示例。如果您必须处理多个 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);
          }
      }
      

      【讨论】:

        【解决方案6】:

        我也有这个问题。并找到了解决方案,希望对您有所帮助。 首先,onReceivedHttpAuthRequest()方法在应用程序中只被调用一次,除了使用cookies。

        我已经写了方法:

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

        【讨论】:

          【解决方案7】:

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

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

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

          启动一个服务,其中包含一个 webview 来处理 http 基本身份验证,并从上面的对话框中传递帐户和密码。

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

          作为AuthService中完整的http基本认证,杀掉AuthService所在的进程。并且可以重置 http 基本身份验证。

          【讨论】:

            【解决方案8】:

            我有另一个解决方案似乎运作良好。基本上,您使用 WebView.loadUrl(url, additionalHeaders) 加载 URL 并传入一个空白的 Authorization 标头。这似乎可以正确重置 webview。唯一的问题是,如果您不重新加载原始 URL,您将在循环中调用 onReceivedHttpAuthRequest。因此,一旦您获得 onReceivedHttpAuthRequest,您将需要从用户那里收集用户名/密码,然后在不传递空白 Authorization 标头的情况下重新加载原始 URL。

            基本上它是这样工作的(具体代码未经测试)。

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

            【讨论】:

              猜你喜欢
              • 2019-06-10
              • 1970-01-01
              • 2015-11-30
              • 2015-08-22
              • 2014-11-17
              • 2013-03-19
              • 2019-12-31
              • 1970-01-01
              • 1970-01-01
              相关资源
              最近更新 更多