禁用 WebView 中的缓存、cookie 和其他所有内容

发布于 2024-10-16 19:42:52 字数 3681 浏览 3 评论 0原文

我有一个网络服务,我试图使用网络视图在后台进行身份验证。当我最初发送请求时,它将正常工作(基于凭据的失败/成功),但之后我似乎收到了缓存的响应。

这是我的 webview 设置代码:

WebView browser = new WebView(this);
WebSettings settings = browser.getSettings();
settings.setJavaScriptEnabled(true);
settings.setSavePassword(false);
settings.setCacheMode(WebSettings.LOAD_NO_CACHE);
settings.setAppCacheEnabled(false);
browser.setWebChromeClient(new WebChromeClient() {
    public void onProgressChanged(WebView view, int progress) {
    Log.d("BROWSERPROGRESS", Integer.toString(progress));
}
});
jsInterface = new AddAccountJSInterface();
browser.addJavascriptInterface(jsInterface, "ADDACCOUNTJSINTERFACE");
browser.setWebViewClient(new AddAccountClient(this));

正如您所看到的,我有两个额外的类控制我的 webView:

  1. 一个为 javascript 提供接口的对象 (AddAccountJSInterface)
  2. WebViewClient

另外,我确实有一个 WebChromeClient,但它仅用于调试,我'我很确定它不会干扰任何事情。

JS 接口只是提供了一种获取正文 HTML 来执行分析的简单方法,因此我相信这也不是问题。

WebViewClient 中包含以下代码,它根据来自 Web 服务的各种响应完成路由的大部分“自定义”工作。

    @Override
    public boolean shouldOverrideUrlLoading(WebView view, String url) {
        if(url.contains(INSTALL_PREFIX)) {
            HashMap<String, String> params = extractParameters(url);
            verificationComplete(params);
            return true;
        }
        return false;
    }

    @Override
    public void onPageFinished(WebView view, String url){
        if(invalidShop(view)) {
            Toast.makeText(context, context.getString(R.string.no_find_shop), Toast.LENGTH_SHORT).show();
            shopAddressField.requestFocus();
            replaceUiElements(loadingBar, addAccountButton);
        } else if(url.contains(ADMIN_AUTH_LOGIN)) {
            if(invalidLogin(view)) {
                Toast.makeText(context, context.getString(R.string.invalid_login),Toast.LENGTH_SHORT).show();
                emailField.requestFocus();
                replaceUiElements(loadingBar, addAccountButton);
            } else {
                String email = emailField.getText().toString();
                String password = passwordField.getText().toString();
                String submitJS = String.format(FORM_SUBMISSION_JS, email, password);

                jsInterface.setInnerHTML("");

                browser.loadUrl(submitJS);
            }
        }
    }

在我的活动中,我需要填写 3 个文本字段,然后单击按钮提交。然后,该活动从 3 个文本字段(shopAddressField、usernameField、passwordField)获取数据,然后执行一些 javascript 来填充一些表单数据(在不可见的 webView 中加载),然后单击提交按钮。

最后一部分是混乱的,它似乎正在缓存来自服务器的响应(也许使用cookie?)并返回该响应,而不是询问服务器数据是否正确。

需要澄清一点:

JSInterface 只是一个 Java 对象,它允许我在 webview 上执行 javascript,该 webview 与该对象中的函数绑定。就我而言,我的 JSInterface 有一个函数,即 setInnerHtml(String html)。

这是在 webview 上执行的 javascript:

javascript:window.ADDACOUNTJSINTERFACE.setInnerHTML(document.body.innerHTML)

这是 setInnerHtml 函数:

public void setInnerHtml(String innerHtml) {
    this.innerHtml = innerHtml;
}

因此,当我实际执行 jsInterface.setInnerHtml("") 时,我只是覆盖了拉入的 HTML(以确保我由于某种原因没有从那里获取我的旧数据)。

至于我的submitJS,它又是在我的webView上执行的一些Javascript,如下所示:

// submitJS will be something like this once all the credentials have been set
// Note: I know that the server will make jQuery available
// Note: Much of the Java string formatting has been removed to help clarify
// the code.
String submitJS = 
    "javascript:(function() {
        $('login-input').value='username';
        $('password').value='password';
        $('sign-in-form').up().submit();
    })()"
// I then simply get the webview to execute the javascript above
webView.loadData(submitJS);

I have a webservice that I am trying to authenticate with in the background using a webview. When I initially send the request it will work appropriately (failure/success based on credentials), but after it seems like I am getting a cached response.

Here is my webview setup code:

WebView browser = new WebView(this);
WebSettings settings = browser.getSettings();
settings.setJavaScriptEnabled(true);
settings.setSavePassword(false);
settings.setCacheMode(WebSettings.LOAD_NO_CACHE);
settings.setAppCacheEnabled(false);
browser.setWebChromeClient(new WebChromeClient() {
    public void onProgressChanged(WebView view, int progress) {
    Log.d("BROWSERPROGRESS", Integer.toString(progress));
}
});
jsInterface = new AddAccountJSInterface();
browser.addJavascriptInterface(jsInterface, "ADDACCOUNTJSINTERFACE");
browser.setWebViewClient(new AddAccountClient(this));

So as you may see I have two additional classes controlling my webView:

  1. An object that provides an interface for javascript (AddAccountJSInterface)
  2. A WebViewClient

Additionally I do have a WebChromeClient, but it's only there for debugging and I'm pretty sure that it won't interfere with anything.

The JS interface simply provides an easy way of getting the body HTML for performing analysis, so I'm confident that isn't the issue either.

The WebViewClient has the following code in it which does most of the "custom" work for routing based on various responses from the webservice.

    @Override
    public boolean shouldOverrideUrlLoading(WebView view, String url) {
        if(url.contains(INSTALL_PREFIX)) {
            HashMap<String, String> params = extractParameters(url);
            verificationComplete(params);
            return true;
        }
        return false;
    }

    @Override
    public void onPageFinished(WebView view, String url){
        if(invalidShop(view)) {
            Toast.makeText(context, context.getString(R.string.no_find_shop), Toast.LENGTH_SHORT).show();
            shopAddressField.requestFocus();
            replaceUiElements(loadingBar, addAccountButton);
        } else if(url.contains(ADMIN_AUTH_LOGIN)) {
            if(invalidLogin(view)) {
                Toast.makeText(context, context.getString(R.string.invalid_login),Toast.LENGTH_SHORT).show();
                emailField.requestFocus();
                replaceUiElements(loadingBar, addAccountButton);
            } else {
                String email = emailField.getText().toString();
                String password = passwordField.getText().toString();
                String submitJS = String.format(FORM_SUBMISSION_JS, email, password);

                jsInterface.setInnerHTML("");

                browser.loadUrl(submitJS);
            }
        }
    }

In my activity I have 3 text fields that I need to fill followed by clicking a button to submit it. The activity then takes in the data from 3 text fields (shopAddressField, usernameField, passwordField) and then executes some javascript that populates some form data (which was loaded in the invisible webView) then clicks the submit button.

It is the last part that is messing up, which appears to be caching the response from the server (perhaps using cookies?) and return that instead of asking the server if the data is correct or not.

A bit of clarification:

JSInterface is simply a Java object that allows me to execute javascript on my webview which is tied to a function within that object. In my case my JSInterface has one function which is setInnerHtml(String html).

This is the javascript that is executed on the webview:

javascript:window.ADDACOUNTJSINTERFACE.setInnerHTML(document.body.innerHTML)

And this is the setInnerHtml function:

public void setInnerHtml(String innerHtml) {
    this.innerHtml = innerHtml;
}

So when I actually execute jsInterface.setInnerHtml("") I'm just over-writing the HTML that was pulled in (to be sure I'm not getting my old data from there for some reason).

As for my submitJS it is once again some Javascript that is executed on my webView as follows:

// submitJS will be something like this once all the credentials have been set
// Note: I know that the server will make jQuery available
// Note: Much of the Java string formatting has been removed to help clarify
// the code.
String submitJS = 
    "javascript:(function() {
        $('login-input').value='username';
        $('password').value='password';
        $('sign-in-form').up().submit();
    })()"
// I then simply get the webview to execute the javascript above
webView.loadData(submitJS);

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(1

月依秋水 2024-10-23 19:42:52

所以事实证明问题不是基于缓存,也可能不是 cookie。

当在 webView 上执行 javascript 时,它会在单独的线程中执行此操作,并且速度可能会很慢。这会导致竞争条件,导致代码以错误的顺序执行。

我通过使用信号量作为互斥体解决了这个问题。这允许我阻止 getter 在 webView 上的 Javascript 能够执行之前返回。

我现在创建的界面如下所示:

private class AddAccountJSInterface {
    private final String TAG = getClass().getName().toUpperCase();
    private Semaphore mutex = new Semaphore(1, false);
    private String innerHTML;

    public void aquireSemaphore() {
        Log.d(TAG, "Attempting to lock semaphore");
        try {
            mutex.acquire();
        } catch(InterruptedException e) {
            Log.d(TAG, "Oh snap, we got interrupted.  Just going to abort.");
            return;
        }
        Log.d(TAG, "Semaphore has been aquired");
    }

    @SuppressWarnings("unused")
    public void setInnerHTML(String html) {
            this.innerHTML = html;
            Log.d(TAG, "setInnerHTML is now releasing semaphore.");
            mutex.release();
            Log.d(TAG, "setInnerHTML has successfully released the semaphore.");
    }

    public synchronized String getInnerHTML() {
        Log.d(TAG, "getInnerHTML attempting to aquire semaphore, may block...");
        String innerHTML = "";
        try {
            mutex.acquire();

            Log.d(TAG, "getInnerHTML has aquired the semaphore, grabbing data.");
            innerHTML = this.innerHTML;

            Log.d(TAG, "getInnerHTML no longer needs semaphore, releasing");
            mutex.release();
        } catch (InterruptedException e) {
            Log.d(TAG, "Something has gone wrong while attempting to aquire semaphore, aborting");
        }

        return innerHTML;
    }
}

现在我在代码中使用它的方式如下:

// I have access to the jsInterface object which is an instance of the class above as well as a webView which I will be executing the javascript on.
String getInnerHtmlJS = "javascript:window.MYJSINTERFACE.setInnerHTML(document.body.innerHTML);"
jsInterface.aquireSemaphore()
// Execute my JS on the webview
jsInterface.loadUrl(getInnerHtmlJS)
// Now we get our inner HTML
// Note: getInnerHTML will block since it must wait for the setInnerHTML (executed via the JS) function to release the semaphore
String theInnerHTML = jsInterface.getInnerHTML();

So it turns out the problem wasn't based around the Caching, and possibly not cookies.

When executing javascript on your webView it does this in a separate thread and can be quite slow. This lead to a race condition which caused code to be executed in the wrong order.

I've solved this problem by using a Semaphore as a Mutex. This allows me to prevent my getter from returning before the Javascript on the webView is able to execute.

The interface I created now looks like this:

private class AddAccountJSInterface {
    private final String TAG = getClass().getName().toUpperCase();
    private Semaphore mutex = new Semaphore(1, false);
    private String innerHTML;

    public void aquireSemaphore() {
        Log.d(TAG, "Attempting to lock semaphore");
        try {
            mutex.acquire();
        } catch(InterruptedException e) {
            Log.d(TAG, "Oh snap, we got interrupted.  Just going to abort.");
            return;
        }
        Log.d(TAG, "Semaphore has been aquired");
    }

    @SuppressWarnings("unused")
    public void setInnerHTML(String html) {
            this.innerHTML = html;
            Log.d(TAG, "setInnerHTML is now releasing semaphore.");
            mutex.release();
            Log.d(TAG, "setInnerHTML has successfully released the semaphore.");
    }

    public synchronized String getInnerHTML() {
        Log.d(TAG, "getInnerHTML attempting to aquire semaphore, may block...");
        String innerHTML = "";
        try {
            mutex.acquire();

            Log.d(TAG, "getInnerHTML has aquired the semaphore, grabbing data.");
            innerHTML = this.innerHTML;

            Log.d(TAG, "getInnerHTML no longer needs semaphore, releasing");
            mutex.release();
        } catch (InterruptedException e) {
            Log.d(TAG, "Something has gone wrong while attempting to aquire semaphore, aborting");
        }

        return innerHTML;
    }
}

Now the way I use this in my code is as follows:

// I have access to the jsInterface object which is an instance of the class above as well as a webView which I will be executing the javascript on.
String getInnerHtmlJS = "javascript:window.MYJSINTERFACE.setInnerHTML(document.body.innerHTML);"
jsInterface.aquireSemaphore()
// Execute my JS on the webview
jsInterface.loadUrl(getInnerHtmlJS)
// Now we get our inner HTML
// Note: getInnerHTML will block since it must wait for the setInnerHTML (executed via the JS) function to release the semaphore
String theInnerHTML = jsInterface.getInnerHTML();
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文