GithubHelp home page GithubHelp logo

pedant / safe-java-js-webview-bridge Goto Github PK

View Code? Open in Web Editor NEW
2.4K 122.0 570.0 1.72 MB

为WebView中的Java与JavaScript提供【安全可靠】的多样互通方案

Home Page: http://pedant.github.io/2014/07/04/webview-js-java-interface-research/

Java 19.78% JavaScript 80.22%

safe-java-js-webview-bridge's Introduction

Safe Java-JS WebView Bridge

抛弃使用高风险的WebView addJavascriptInterface方法,通过对js层调用函数及回调函数的包装,支持异步回调,方法参数支持js所有已知的类型,包括number、string、boolean、object、function。

安装

使用Safe Java-JS WebView Bridge最简单的办法就是像下面这样添加项目依赖。

Maven

<dependency>
  <groupId>com.google.code.gson</groupId>
  <artifactId>gson</artifactId>
  <version>2.3</version>
</dependency>
<dependency>
  <groupId>cn.pedant.safewebviewbridge</groupId>
  <artifactId>library</artifactId>
  <version>1.4</version>
  <type>aar</type>
</dependency>

Gradle

repositories {
    mavenCentral()
}

dependencies {
    compile 'cn.pedant.safewebviewbridge:library:1.4'
}

Sample

Sample 下载

image

用法

如何开始

初始化Webview WebSettings时允许js脚本执行,同时使用你的注入名和注入类来实例化一个InjectedChromeClient对象,然后关联到你的Webview实例。如demo中的例子(页面中引用的对象名为HostApp,指定的注入类为HostJsScope):

WebView wv = new WebView(this);
WebSettings ws = wv.getSettings();
ws.setJavaScriptEnabled(true);
wv.setWebChromeClient(
    new InjectedChromeClient("HostApp", HostJsScope.class)
);
wv.loadUrl("file:///android_asset/test.html");

自定义WebChromeClient子类

如果你需要实现自己的WebChromeClient子类,然后设置到WebView。为了保持InjectedChromeClient的功能,你需要将此类继承自InjectedChromeClient,同时像下面这样覆盖这三个方法。

public class CustomChromeClient extends InjectedChromeClient {

    public CustomChromeClient (String injectedName, Class injectedCls) {
        super(injectedName, injectedCls);
    }

    @Override
    public boolean onJsAlert(WebView view, String url, String message, final JsResult result) {
        // to do your work
        // ...
        return super.onJsAlert(view, url, message, result);
    }

    @Override
    public void onProgressChanged (WebView view, int newProgress) {
        super.onProgressChanged(view, newProgress);
        // to do your work
        // ...
    }

    @Override
    public boolean onJsPrompt(WebView view, String url, String message, String defaultValue, JsPromptResult result) {
        // to do your work
        // ...
        return super.onJsPrompt(view, url, message, defaultValue, result);
    }
}

方法的定义

需要注入到网页的方法,必须在注入类中定义为public static且第一个参数接收WebView,其他参数的类型可以是int、long、double、boolean、String、JSONObject、JsCallback。方法执行时会默认将当前Webview的实例放到第一个参数,所以你的定义可能看起来像这样子的:

public static void testA (WebView webView) {}

网页调用如下:

HostApp.testA();

方法的重载

在定义时,支持不同参数类型或参数个数的方法重载,如:

public static int overloadMethod(WebView view, int val) {
    return val;
}
public static String overloadMethod(WebView view, String val) {
    return val;
}

但需要注意的是,由于JS中数字类型不区分整型、长整型、浮点类型等,是统一由64位浮点数表示,故Java方法在定义时int/long/double被当作是一种类型,也即:

public static int overloadMethod(WebView view, int val) {
    return val;
}
public static long overloadMethod(WebView view, long val) {
    return val;
}
public static double overloadMethod(WebView view, double val) {
    return val;
}

上面这三个方法并没有发生重载,HostApp.overloadMethod(1)调用时只会调用最后一个定义的方法(double类型定义的那个)。

方法的返回值

Java层方法可以返回void 或 能转为字符串的类型(如int、long、String、double、float等)或 可序列化的自定义类型。关于自定义类型的返回可以参见Demo下“从Java层返回Java对象”项对HostApp.retJavaObject()的调用。另外如果方法定义时返回void,那么网页端调用得到的返回值为null。

如果方法执行过程中出现异常,那么在网页JS端会抛出异常,可以catch后打印详细的错误说明。

关于异步回调

举例说明,首先你可以在Java层定义如下方法,该方法的作用是延迟设定的时间之后,用你传入的参数回调Js函数:

public static void delayJsCallBack(WebView view, int ms, final String backMsg, final JsCallback jsCallback) {
  TaskExecutor.scheduleTaskOnUiThread(ms*1000, new Runnable() {
      @Override
      public void run() {
          jsCallback.apply(backMsg);
      }
  });
}

那么在网页端的调用如下:

HostApp.delayJsCallBack(3, 'call back haha', function (msg) {
  HostApp.alert(msg);
});

即3秒之后会弹出你传入的'call back haha'信息。 故从上面的例子我们可以看出,你在网页端定义的回调函数是可以附加多个参数,Java方法在执行回调时需要带入相应的实参就行了。当然这里的回调函数的参数类型目前还不支持过复杂的类型,仅支持能够被转为字符串的类型

另外需要注意的是一般传入到Java方法的js function是一次性使用的,即在Java层jsCallback.apply(...)之后不能再发起回调了。如果需要传入的function能够在当前页面生命周期内多次使用,请在第一次apply前setPermanent(true)。例如:

public static void setOnScrollBottomListener (WebView view, JsCallback jsCallback) {
	jsCallback.setPermanent(true);
	...
}

小心过大数字

JS中使用过大数字时,可能会导致精度丢失或者错误的数字结果,如下面:

HostApp.passLongType(14102300951321235)

传入一个大数14102300951321235到Java层,但Java层接收的数字实际上将会是14102300951321236这样一个错误的数字,所以当需要使用大数的情景下时,Java方法参数类型最好定义为String类型,而js层调用时也转为string,比如上面就为

HostApp.passLongType(14102300951321235+'')。	

更多实现细节见: http://www.pedant.cn/2014/07/04/webview-js-java-interface-research/

发布时防混淆

发布时需在你的混淆配置加入像下面这样的代码,注意返回到页面的自定义Java类以及注入类都要换成你项目中实际使用类名:

#--------------- BEGIN: Gson防混淆 ----------
-keepattributes *Annotation*
-keep class sun.misc.Unsafe { *; }
-keep class com.idea.fifaalarmclock.entity.***
-keep class com.google.gson.stream.** { *; }
#--------------- END ----------

#--------------- BEGIN: 返回到页面的自定义Java对象防混淆 ----------
-keepclassmembers class cn.pedant.SafeWebViewBridge.sample.HostJsScope$RetJavaObj{ *; }
#--------------- END ----------

#--------------- BEGIN: 注入到页面的接口类防混淆 ----------
-keepclassmembers class cn.pedant.SafeWebViewBridge.sample.HostJsScope{ *; }
#--------------- END ----------

License

The MIT License (MIT)

Copyright (c) 2014 Pedant(http://pedant.cn)

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

safe-java-js-webview-bridge's People

Contributors

pedant avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

safe-java-js-webview-bridge's Issues

为什么不用gson呢

大部分人的项目中都用了gson,如果这个项目集成gson,那么就不需要Jackson了,省点空间

很多机型上注入不成功

您好,进度大于25%才进行注入,在某些机型上注入不成功,或者偶尔成功偶尔不成功,这个方案不稳定啊。能再优化不? 目前验证注入失败机型:小米 MIUI-JLB52.0 android4.1.1 偶尔成功机型:索尼android4.4.2版本

请教一个问题

您好,能请教一下,你的这种实现方式比在shouldOverrideUrlLoading拦截js再去处理的优势在哪儿吗

发现问题

关于网页调用玩Java后回调js的问题,例如弹出个dialog点击确定后后回调js,但却不成功

发现问题

发现一问题,JsCallback回调时报空指针错误:
Caused by: java.lang.NullPointerException
at android.webkit.WebViewClassic.loadUrlImpl(WebViewClassic.java:2678)
at android.webkit.WebViewClassic.loadUrlImpl(WebViewClassic.java:2694)
at android.webkit.WebViewClassic.loadUrl(WebViewClassic.java:2687)
at android.webkit.WebView.loadUrl(WebView.java:804)
at cn.pedant.SafeWebViewBridge.JsCallback.apply(JsCallback.java:42)
at com.jidian.android.edo.util.HostJsScope$2.onComplete(HostJsScope.java:137)

我猜测可能是这样的,因为回调有延迟,callback.apply()的时候可能webview已经被关闭,导致的这个错误

我把你的JsCallJava.java修改了一下,这样使用起来更加灵活:(你可以参考一下)

我把你的JsCallJava.java修改了一下,这样使用起来更加灵活:(你可以参考一下)

new MyInjectedChromeClient(new JsCallJava.InjectObj("myjs.ui", MyJs.class),new JsCallJava.InjectObj("myjs.ui2", MyJs2.class,MyJs3.class,MyJs4.class))

package cn.pedant.SafeWebViewBridge;

import android.text.TextUtils;
import android.webkit.WebView;
import android.util.Log;

import com.google.gson.Gson;

import org.json.JSONArray;
import org.json.JSONObject;

import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.HashMap;

public class JsCallJava
{
private final static String TAG = "JsCallJava";
private final static String RETURN_RESULT_FORMAT = "{"code": %d, "result": %s}";
private HashMap<String, Method> mMethodsMap;
/////private String mInjectedName;/////
private String mPreloadInterfaceJS;
private Gson mGson;

/////
public static class InjectObj
{
    String namespace;
    Class<?>[] interfaceClasses;

    public InjectObj(String namespace, Class<?>... interfaceClasses)
    {
        if (TextUtils.isEmpty(namespace))
        {
            throw new RuntimeException("namespace can not be null!");
        }
        this.namespace = namespace;
        this.interfaceClasses = interfaceClasses;
    }
}
/////

public JsCallJava(InjectObj... injectObjs)
{
    try
    {

        mMethodsMap = new HashMap<String, Method>();
        StringBuilder sbuilder = new StringBuilder("javascript:");

        for (InjectObj injectObj : injectObjs)
        {
            injectOne(sbuilder, injectObj);
        }

        mPreloadInterfaceJS = sbuilder.toString();
    } catch (Exception e)
    {
        e.printStackTrace();
        throw new RuntimeException("init js error:" + e.getMessage());
    }
}

private void injectOne(StringBuilder sbuilder, InjectObj injectObj)
{
    String mInjectedName = injectObj.namespace;

    StringBuilder sb = new StringBuilder("(function(b){console.log(\"");/////去掉前面的javascript://////

    sb.append(mInjectedName);
    sb.append(
            " initialization begin\");var a={");

    sb.append("namespace:\"").append(mInjectedName).append("\",");//////添加一个namespace///////

    sb.append("queue:[],callback:function(){var d=Array.prototype.slice.call" +
            "(arguments,0);var c=d.shift();var e=d.shift();this.queue[c].apply(this,d);if(!e){delete " +
            "this.queue[c]}}};");


    ///////
    for (Class<?> c : injectObj.interfaceClasses)
    {
        searchClass(sb, c);
    }

    StringBuilder namespaces = new StringBuilder();

    {
        StringBuilder temp = new StringBuilder();
        String[] ss = injectObj.namespace.split("\\.");
        for (String s : ss)
        {
            if ("".equals(s))
            {
                continue;
            } else
            {
                temp.append(".").append(s);
                namespaces.append("b").append(temp).append("=").append("b").append(temp).append("||{};");
            }
        }
    }

    //////

    sb.append("function(){var f=Array.prototype.slice.call(arguments,0);if(f.length<1){throw\"");
    sb.append(mInjectedName);
    sb.append(
            " call error, message:miss method name\"}var e=[];for(var h=1;h<f.length;h++){var c=f[h];var " +
                    "j=typeof c;e[e.length]=j;if(j==\"function\"){var d=a.queue.length;a.queue[d]=c;" +
                    "f[h]=d}}var g=JSON.parse(prompt(JSON.stringify({method:f.shift(),types:e,args:f");

    sb.append(",namespace:a.namespace");/////////加入namespace/////////

    sb.append("})));if(g" +
            ".code!=200){throw\"");
    sb.append(mInjectedName);
    sb.append(
            " call error, code:\"+g.code+\", message:\"+g.result}return g.result};Object.getOwnPropertyNames" +
                    "(a).forEach(function(d){var c=a[d];if(typeof c===\"function\"&&d!==\"callback\")" +
                    "{a[d]=function(){return c.apply(a,[d].concat(Array.prototype.slice.call(arguments,0)))" +
                    "}}});");

    sb.append(namespaces);//////////加入,如:b.ui={};b.ui.abc={};

    sb.append("b.").append(mInjectedName);
    sb.append("=a;console.log(\"");
    sb.append(mInjectedName);
    sb.append(" initialization end\")})(window);");

    /////////
    sbuilder.append(sb);
    ////////
}

private void searchClass(StringBuilder sb, Class<?> c)
{
    /////个人建议还是用getMethods,这样可以不用把所有的static函数都挤在一个类里,而可以把一部分放在父类中.//////
    Method[] methods = c.getMethods();

    for (Method method : methods)
    {
        String sign;
        if (method.getModifiers() != (Modifier.PUBLIC | Modifier.STATIC) || (sign = genJavaMethodSign(
                method)) == null)
        {
            continue;
        }
        mMethodsMap.put(sign, method);
        sb.append(String.format("a.%s=", method.getName()));
    }
}

private String genJavaMethodSign(Method method)
{
    String sign = method.getName();
    Class[] argsTypes = method.getParameterTypes();
    int len = argsTypes.length;
    if (len < 1 || argsTypes[0] != WebView.class)
    {
        Log.w(TAG, "method(" + sign + ") must use webview to be first parameter, will be pass");
        return null;
    }
    for (int k = 1; k < len; k++)
    {
        Class cls = argsTypes[k];
        if (cls == String.class)
        {
            sign += "_S";
        } else if (cls == int.class ||
                cls == long.class ||
                cls == float.class ||
                cls == double.class)
        {
            sign += "_N";
        } else if (cls == boolean.class)
        {
            sign += "_B";
        } else if (cls == JSONObject.class)
        {
            sign += "_O";
        } else if (cls == JsCallback.class)
        {
            sign += "_F";
        } else
        {
            sign += "_P";
        }
    }
    return sign;
}

public String getPreloadInterfaceJS()
{
    return mPreloadInterfaceJS;
}

public String call(WebView webView, String jsonStr)
{
    if (!TextUtils.isEmpty(jsonStr))
    {
        try
        {
            JSONObject callJson = new JSONObject(jsonStr);

            String mInjectedName = callJson.getString("namespace");//////得到namespace///////

            String methodName = callJson.getString("method");
            JSONArray argsTypes = callJson.getJSONArray("types");
            JSONArray argsVals = callJson.getJSONArray("args");
            String sign = methodName;
            int len = argsTypes.length();
            Object[] values = new Object[len + 1];
            int numIndex = 0;
            String currType;

            values[0] = webView;

            for (int k = 0; k < len; k++)
            {
                currType = argsTypes.optString(k);
                if ("string".equals(currType))
                {
                    sign += "_S";
                    values[k + 1] = argsVals.isNull(k) ? null : argsVals.getString(k);
                } else if ("number".equals(currType))
                {
                    sign += "_N";
                    numIndex = numIndex * 10 + k + 1;
                } else if ("boolean".equals(currType))
                {
                    sign += "_B";
                    values[k + 1] = argsVals.getBoolean(k);
                } else if ("object".equals(currType))
                {
                    sign += "_O";
                    values[k + 1] = argsVals.isNull(k) ? null : argsVals.getJSONObject(k);
                } else if ("function".equals(currType))
                {
                    sign += "_F";
                    values[k + 1] = new JsCallback(webView, mInjectedName, argsVals.getInt(k));
                } else
                {
                    sign += "_P";
                }
            }

            Method currMethod = mMethodsMap.get(sign);

            // 方法匹配失败
            if (currMethod == null)
            {
                return getReturn(jsonStr, 500, "not found method(" + sign + ") with valid parameters");
            }
            // 数字类型细分匹配
            if (numIndex > 0)
            {
                Class[] methodTypes = currMethod.getParameterTypes();
                int currIndex;
                Class currCls;
                while (numIndex > 0)
                {
                    currIndex = numIndex - numIndex / 10 * 10;
                    currCls = methodTypes[currIndex];
                    if (currCls == int.class)
                    {
                        values[currIndex] = argsVals.getInt(currIndex - 1);
                    } else if (currCls == long.class)
                    {
                        //WARN: argsJson.getLong(k + defValue) will return a bigger incorrect number
                        values[currIndex] = Long.parseLong(argsVals.getString(currIndex - 1));
                    } else
                    {
                        values[currIndex] = argsVals.getDouble(currIndex - 1);
                    }
                    numIndex /= 10;
                }
            }

            return getReturn(jsonStr, 200, currMethod.invoke(null, values));
        } catch (Exception e)
        {
            //优先返回详细的错误信息
            if (e.getCause() != null)
            {
                return getReturn(jsonStr, 500, "method execute error:" + e.getCause().getMessage());
            }
            return getReturn(jsonStr, 500, "method execute error:" + e.getMessage());
        }
    } else
    {
        return getReturn(jsonStr, 500, "call data empty");
    }
}

private String getReturn(String reqJson, int stateCode, Object result)
{
    String insertRes;
    if (result == null)
    {
        insertRes = "null";
    } else if (result instanceof String)
    {
        result = ((String) result).replace("\"", "\\\"");
        insertRes = "\"" + result + "\"";
    } else if (!(result instanceof Integer)
            && !(result instanceof Long)
            && !(result instanceof Boolean)
            && !(result instanceof Float)
            && !(result instanceof Double)
            && !(result instanceof JSONObject))
    {    // 非数字或者非字符串的构造对象类型都要序列化后再拼接
        if (mGson == null)
        {
            mGson = new Gson();
        }
        insertRes = mGson.toJson(result);
    } else
    {  //数字直接转化
        insertRes = String.valueOf(result);
    }
    String resStr = String.format(RETURN_RESULT_FORMAT, stateCode, insertRes);
    ////////Log.d(TAG, mInjectedName + " call json: " + reqJson + " result:" + resStr);
    return resStr;
}

}

JS怎么传方法给JAVA进行执行

在 JsCallJava的call方法中 有一个类型判断:values[k + 1] = new JsCallback(webView, mInjectedName, argsVals.getInt(k));进行执行的时候有一个argsVals.getInt(k)获取到对应的全局方法索引,但是假如我需要传入一个对应的方法名称(String 类型),需要怎么做,我没有看到onJsPrompt代码是怎么对message进行封装的,例如我检测到的:
message="{"method":"delayJsCallBack1","types":["number","string","function"],"args":[1,"call back haha","1"]}";其中1是message传过来的,请问我需要怎么做,期待您的回复。

import error

JsCallback.java:
import java.lang.ref.SoftReference;
->
import java.lang.ref.WeakReference;

请教个问题

在webview中加载的网页时常调用注入的js对象不存在,这个是什么问题呢,非网页加载完毕立即执行

JsCallback.apply(String对象)出错

Log:I/chromium: [INFO:CONSOLE(1)] "Uncaught SyntaxError: Unexpected identifier", source: (1)。
我在调用jsCallback.apply()时,传入一个String对象,在apply方法里,有这样的代码:

StringBuilder sb = new StringBuilder();
for (Object arg : args){
    sb.append(",");
    boolean isStrArg = arg instanceof String;
    if (isStrArg) {
        sb.append("\"");
    }
    sb.append(String.valueOf(arg));
    if (isStrArg) {
        sb.append("\"");
    }
}

此方法会在参数首尾加上双引号。此时当传入的String对象包含双引号时(JSONObject.toString()方法生成的String对象就会包含双引号),就会出错。不包含则不会出现问题。
将append里面的双引号改成单引号能解决此问题。但是如果String对象包含单引号就又会导致这个问题了- -
延伸到既包含双引号又包含单引号的String对象又该如何传递呢?

建议

在很多时候用webview打开别人的网页需要向网页中注入js代码,以检测某些操作,可不可以把这部分功能也加入进去呢?
还有个疑问,HostJsScope类中方法的多少会不会影响效率或是页面打开的速度呢?

我把你的JsCallJava的代码改了一下,用起来会更加灵活:(你可以参考一下)

我把你的代码改了一下,用起来会更加灵活:(你可以参考一下)
new MyInjectedChromeClient(new JsCallJava.InjectObj("myjs.ui", MyJs.class),
new JsCallJava.InjectObj("myjs.ui2", MyJs2.class,MyJs3.class,MyJs4.class));

package cn.pedant.SafeWebViewBridge;

import android.text.TextUtils;
import android.webkit.WebView;
import android.util.Log;

import com.google.gson.Gson;

import org.json.JSONArray;
import org.json.JSONObject;

import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.HashMap;

public class JsCallJava
{
private final static String TAG = "JsCallJava";
private final static String RETURN_RESULT_FORMAT = "{"code": %d, "result": %s}";
private HashMap<String, Method> mMethodsMap;
/////<private String mInjectedName;>/////
private String mPreloadInterfaceJS;
private Gson mGson;

/////<
public static class InjectObj
{
    String namespace;
    Class<?>[] interfaceClasses;

    public InjectObj(String namespace, Class<?>... interfaceClasses)
    {
        if (TextUtils.isEmpty(namespace))
        {
            throw new RuntimeException("namespace can not be null!");
        }
        this.namespace = namespace;
        this.interfaceClasses = interfaceClasses;
    }
}
/////>

public JsCallJava(InjectObj... injectObjs)
{
    try
    {

        mMethodsMap = new HashMap<String, Method>();
        StringBuilder sbuilder = new StringBuilder("javascript:");

        for (InjectObj injectObj : injectObjs)
        {
            injectOne(sbuilder, injectObj);
        }

        mPreloadInterfaceJS = sbuilder.toString();
    } catch (Exception e)
    {
        e.printStackTrace();
        throw new RuntimeException("init js error:" + e.getMessage());
    }
}

private void injectOne(StringBuilder sbuilder, InjectObj injectObj)
{
    String mInjectedName = injectObj.namespace;

    StringBuilder sb = new StringBuilder("(function(b){console.log(\"");/////去掉前面的javascript://////

    sb.append(mInjectedName);
    sb.append(
            " initialization begin\");var a={");

    sb.append("namespace:\"").append(mInjectedName).append("\",");//////添加一个namespace///////

    sb.append("queue:[],callback:function(){var d=Array.prototype.slice.call" +
            "(arguments,0);var c=d.shift();var e=d.shift();this.queue[c].apply(this,d);if(!e){delete " +
            "this.queue[c]}}};");


    //////////////<
    for (Class<?> c : injectObj.interfaceClasses)
    {
        searchClass(sb, c);
    }

    StringBuilder namespaces = new StringBuilder();

    {
        StringBuilder temp = new StringBuilder();
        String[] ss = injectObj.namespace.split("\\.");
        for (String s : ss)
        {
            if ("".equals(s))
            {
                continue;
            } else
            {
                temp.append(".").append(s);
                namespaces.append("b").append(temp).append("=").append("b").append(temp).append("||{};");
            }
        }
    }

    ///////////>

    sb.append("function(){var f=Array.prototype.slice.call(arguments,0);if(f.length<1){throw\"");
    sb.append(mInjectedName);
    sb.append(
            " call error, message:miss method name\"}var e=[];for(var h=1;h<f.length;h++){var c=f[h];var " +
                    "j=typeof c;e[e.length]=j;if(j==\"function\"){var d=a.queue.length;a.queue[d]=c;" +
                    "f[h]=d}}var g=JSON.parse(prompt(JSON.stringify({method:f.shift(),types:e,args:f");

    sb.append(",namespace:a.namespace");/////////加入namespace/////////

    sb.append("})));if(g" +
            ".code!=200){throw\"");
    sb.append(mInjectedName);
    sb.append(
            " call error, code:\"+g.code+\", message:\"+g.result}return g.result};Object.getOwnPropertyNames" +
                    "(a).forEach(function(d){var c=a[d];if(typeof c===\"function\"&&d!==\"callback\")" +
                    "{a[d]=function(){return c.apply(a,[d].concat(Array.prototype.slice.call(arguments,0)))" +
                    "}}});");

    sb.append(namespaces);//////////加入,如:b.ui={};b.ui.abc={};

    sb.append("b.").append(mInjectedName);
    sb.append("=a;console.log(\"");
    sb.append(mInjectedName);
    sb.append(" initialization end\")})(window);");

    /////////
    sbuilder.append(sb);
    ////////
}

private void searchClass(StringBuilder sb, Class<?> c)
{
    /////个人建议还是用getMethods,这样可以不用把所有的static函数都挤在一个类里,而可以把一部分放在父类中.//////
    Method[] methods = c.getMethods();

    for (Method method : methods)
    {
        String sign;
        if (method.getModifiers() != (Modifier.PUBLIC | Modifier.STATIC) || (sign = genJavaMethodSign(
                method)) == null)
        {
            continue;
        }
        mMethodsMap.put(sign, method);
        sb.append(String.format("a.%s=", method.getName()));
    }
}

private String genJavaMethodSign(Method method)
{
    String sign = method.getName();
    Class[] argsTypes = method.getParameterTypes();
    int len = argsTypes.length;
    if (len < 1 || argsTypes[0] != WebView.class)
    {
        Log.w(TAG, "method(" + sign + ") must use webview to be first parameter, will be pass");
        return null;
    }
    for (int k = 1; k < len; k++)
    {
        Class cls = argsTypes[k];
        if (cls == String.class)
        {
            sign += "_S";
        } else if (cls == int.class ||
                cls == long.class ||
                cls == float.class ||
                cls == double.class)
        {
            sign += "_N";
        } else if (cls == boolean.class)
        {
            sign += "_B";
        } else if (cls == JSONObject.class)
        {
            sign += "_O";
        } else if (cls == JsCallback.class)
        {
            sign += "_F";
        } else
        {
            sign += "_P";
        }
    }
    return sign;
}

public String getPreloadInterfaceJS()
{
    return mPreloadInterfaceJS;
}

public String call(WebView webView, String jsonStr)
{
    if (!TextUtils.isEmpty(jsonStr))
    {
        try
        {
            JSONObject callJson = new JSONObject(jsonStr);

            String mInjectedName = callJson.getString("namespace");//////得到namespace///////

            String methodName = callJson.getString("method");
            JSONArray argsTypes = callJson.getJSONArray("types");
            JSONArray argsVals = callJson.getJSONArray("args");
            String sign = methodName;
            int len = argsTypes.length();
            Object[] values = new Object[len + 1];
            int numIndex = 0;
            String currType;

            values[0] = webView;

            for (int k = 0; k < len; k++)
            {
                currType = argsTypes.optString(k);
                if ("string".equals(currType))
                {
                    sign += "_S";
                    values[k + 1] = argsVals.isNull(k) ? null : argsVals.getString(k);
                } else if ("number".equals(currType))
                {
                    sign += "_N";
                    numIndex = numIndex * 10 + k + 1;
                } else if ("boolean".equals(currType))
                {
                    sign += "_B";
                    values[k + 1] = argsVals.getBoolean(k);
                } else if ("object".equals(currType))
                {
                    sign += "_O";
                    values[k + 1] = argsVals.isNull(k) ? null : argsVals.getJSONObject(k);
                } else if ("function".equals(currType))
                {
                    sign += "_F";
                    values[k + 1] = new JsCallback(webView, mInjectedName, argsVals.getInt(k));
                } else
                {
                    sign += "_P";
                }
            }

            Method currMethod = mMethodsMap.get(sign);

            // 方法匹配失败
            if (currMethod == null)
            {
                return getReturn(jsonStr, 500, "not found method(" + sign + ") with valid parameters");
            }
            // 数字类型细分匹配
            if (numIndex > 0)
            {
                Class[] methodTypes = currMethod.getParameterTypes();
                int currIndex;
                Class currCls;
                while (numIndex > 0)
                {
                    currIndex = numIndex - numIndex / 10 * 10;
                    currCls = methodTypes[currIndex];
                    if (currCls == int.class)
                    {
                        values[currIndex] = argsVals.getInt(currIndex - 1);
                    } else if (currCls == long.class)
                    {
                        //WARN: argsJson.getLong(k + defValue) will return a bigger incorrect number
                        values[currIndex] = Long.parseLong(argsVals.getString(currIndex - 1));
                    } else
                    {
                        values[currIndex] = argsVals.getDouble(currIndex - 1);
                    }
                    numIndex /= 10;
                }
            }

            return getReturn(jsonStr, 200, currMethod.invoke(null, values));
        } catch (Exception e)
        {
            //优先返回详细的错误信息
            if (e.getCause() != null)
            {
                return getReturn(jsonStr, 500, "method execute error:" + e.getCause().getMessage());
            }
            return getReturn(jsonStr, 500, "method execute error:" + e.getMessage());
        }
    } else
    {
        return getReturn(jsonStr, 500, "call data empty");
    }
}

private String getReturn(String reqJson, int stateCode, Object result)
{
    String insertRes;
    if (result == null)
    {
        insertRes = "null";
    } else if (result instanceof String)
    {
        result = ((String) result).replace("\"", "\\\"");
        insertRes = "\"" + result + "\"";
    } else if (!(result instanceof Integer)
            && !(result instanceof Long)
            && !(result instanceof Boolean)
            && !(result instanceof Float)
            && !(result instanceof Double)
            && !(result instanceof JSONObject))
    {    // 非数字或者非字符串的构造对象类型都要序列化后再拼接
        if (mGson == null)
        {
            mGson = new Gson();
        }
        insertRes = mGson.toJson(result);
    } else
    {  //数字直接转化
        insertRes = String.valueOf(result);
    }
    String resStr = String.format(RETURN_RESULT_FORMAT, stateCode, insertRes);
    ////////Log.d(TAG, mInjectedName + " call json: " + reqJson + " result:" + resStr);
    return resStr;
}

}

WebView默认接口searchBoxJavaBridge_依然存在js注入漏洞

       您好,我发现该库没有移除WebView的默认接口searchBoxJavaBridge_,使用该默认接口依然可以通过js注入漏洞,注入方法可以参考文章中第三小节中的js方法execute。
       在使用该库之前调用webView.removeJavascriptInterface("searchBoxJavaBridge_");应该就可以解决这个问题。

动态注入的js内容怎么查看?

通过view.loadUrl("javascript:(function(b){fuction test(){}})(window))注入的类容注入到webview的哪个地方,在注入完成后通过document.getElementsByTagName('html')[0].innerHTML获取不到注入的类容。

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.