Android为WebView中的Java与JavaScript的交互漏洞提供安全可靠的多样互通方案
2016-06-29 14:07
951 查看
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 下载用法
如何开始
初始化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 ----------
git地址:https://github.com/pedant/safe-java-js-webview-bridge
相关文章推荐
- android 调用系统的音乐和视频播放器
- Android RxJava 之FlatMap()
- Android基础--ActionBar的基本用法,自定义View,标题栏
- Android开发优化之——对Bitmap的内存优化
- Android中的@SystemApi@hide等
- Manifest属性对android横竖屏切换的生命周期的影响
- Android 接口回调机制
- Android平台下利用zxing实现二维码开发
- Android View的getLeft, getRight, getTop, getBottom
- Android中有没有必要调用Bitmap的recycle()
- android事件
- Instrumentation框架分析及其使用
- listView的item侧滑显示操作
- 简谈属性动画
- Android基础--Android的版本对应的API
- android 如何将图片转化成字符串
- android:gravity 和 android:layout_Gravity的区别
- 开发错误12:gradle编译错误:Conflict with dependency com.android.support:support-annotations
- 自定义水波纹View
- android开发之-相对布局的属性