一、WebView 简单使用
WebView 是 Android 系统中的原生控件,其主要功能与前端页面进行响应交互,快捷省时地实现如期的功能,相当于增强版的内置浏览器。使用时需要在配置文件里设置网络权限,定义布局大小和样式,绑定和操作控件。
manifest.xml<uses-permission android:name="android.permission.INTERNET" />
activity_main.xml<WebView android:id="@+id/web_view" android:layout_width="match_parent" android:layout_height="match_parent"></WebView>
MainActivity.javaString url = "https://www.baidu.com";WebView webView = (WebView) findViewById(R.id.web_view);webView.loadUrl(url);
网页跳转,包括前进、后退、自定义。boolean back = webview.canGoBack(); -> 判断网页是否可以回退webview.goBack(); -> 回退一页
boolean forward = webview.canGoForward(); -> 判断网页是否可以前进webview.goForward(); -> 前进一页
webview.goBackOrForward(1); -> 正数为前进webview.goBackOrForward(-1); -> 负数为后退
复制代码
二、WebView 生命周期
onResume():活跃状态,可以正常执行网页的响应。onPause():暂停状态,页面被失去焦点,暂停所有进行中的动作,如:DOM解析、CSS和JavaScript执行等。pauseTimers():全局WebView暂停状态,如:layout、parsing、javascripttimer等。resumeTimers():恢复到pauseTimers()执行前的状态。destroy():销毁状态,释放资源。注意:使用WebView不当容易引起内存泄漏,所以WebView的生命周期方法应跟随Activity的生命周期的方法来调用。
复制代码
三、WebSettings
控件的相关配置WebSettings webSettings = webView.getSettings();
webSettings.setJavaScriptEnabled(true); -> 是否开启JS支持webSettings.setPluginsEnabled(true); -> 是否开启插件支持webSettings.setJavaScriptCanOpenWindowsAutomatically(true); -> 是否允许JS打开新窗口
webSettings.setUseWideViewPort(true); -> 缩放至屏幕大小webSettings.setLoadWithOverviewMode(true); -> 缩放至屏幕大小webSettings.setSupportZoom(true); -> 是否支持缩放webSettings.setBuiltInZoomControls(true); -> 是否支持缩放变焦,前提是支持缩放webSettings.setDisplayZoomControls(false); -> 是否隐藏缩放控件
webSettings.setAllowFileAccess(true); -> 是否允许访问文件webSettings.setDomStorageEnabled(true); -> 是否节点缓存webSettings.setDatabaseEnabled(true); -> 是否数据缓存webSettings.setAppCacheEnabled(true); -> 是否应用缓存webSettings.setAppCachePath(uri); -> 设置缓存路径
webSettings.setMediaPlaybackRequiresUserGesture(false); -> 是否要手势触发媒体webSettings.setStandardFontFamily("sans-serif"); -> 设置字体库格式webSettings.setFixedFontFamily("monospace"); -> 设置字体库格式webSettings.setSansSerifFontFamily("sans-serif"); -> 设置字体库格式webSettings.setSerifFontFamily("sans-serif"); -> 设置字体库格式webSettings.setCursiveFontFamily("cursive"); -> 设置字体库格式webSettings.setFantasyFontFamily("fantasy"); -> 设置字体库格式webSettings.setTextZoom(100); -> 设置文本缩放的百分比webSettings.setMinimumFontSize(8); -> 设置文本字体的最小值(1~72)webSettings.setDefaultFontSize(16); -> 设置文本字体默认的大小
webSettings.setLayoutAlgorithm(LayoutAlgorithm.SINGLE_COLUMN); -> 按规则重新布局webSettings.setLoadsImagesAutomatically(false); -> 是否自动加载图片webSettings.setDefaultTextEncodingName("UTF-8"); -> 设置编码格式webSettings.setNeedInitialFocus(true); -> 是否需要获取焦点webSettings.setGeolocationEnabled(false); -> 设置开启定位功能webSettings.setBlockNetworkLoads(false); -> 是否从网络获取资源
复制代码
四、WebViewClient
控件客户端,用于处理各种通知和请求事件。onPageStarted():页面开始加载时调用,这时候可以显示加载进度条,让用户耐心等待页面的加载。onPageFinished():页面完成加载时调用,这时候可以隐藏加载进度条,提醒用户页面已经完成加载。onLoadResource():页面每次加载资源时调用。shouldOverrideUrlLoading():WebView加载url默认会调用系统的浏览器,通过重写该方法,实现在当前应用内完成页面加载。onReceivedError():页面加载发生错误时调用,这时候可以跳转到自定义的错误提醒页面,总比系统默认的错误页面美观,优化用户体验。onReceivedHttpError():页面加载请求时发生错误。onReceivedSslError():页面加载资源时发生错误。shouldOverrideKeyEvent():覆盖按键默认的响应事件,这时候可以根据自身的需求在点击某些按键时加入相应的逻辑。onScaleChanged():页面的缩放比例发生变化时调用,这时候可以根据当前的缩放比例来重新调整WebView中显示的内容,如修改字体大小、图片大小等。shouldInterceptRequest():可以根据请求携带的内容来判断是否需要拦截请求。WebViewClient webViewClient = new WebViewClient(){ @Override public void onPageStarted(WebView view, String url, Bitmap favicon) {
}
@Override public void onPageFinished(WebView view, String url) {
}
@Override public boolean onLoadResource(WebView view, String url) {
}
@Override public boolean shouldOverrideUrlLoading(WebView view, String url) { view.loadUrl(url); return true; -> 消费事件终止传递 }
@Override public void onReceivedError(WebView view, int errorCode, String description, String failingUrl){ view.loadUrl("file:///android_assets/error.html"); -> assets目录下放置文件}
webView.setWebViewClient(webViewClient);
复制代码
五、WebChromeClient
浏览器客户端,用于处理网站图标、网站标题、网站弹窗等。onProgressChanged():页面加载进度发生变化时调用,可以通过该方法实时向用户反馈加载情况,如显示进度条等。onReceivedIcon():接收Web页面的图标,可以通过该方法把图标设置在原生的控件上,如Toolbar等。onReceivedTitle():接收Web页面的标题,可以通过该方法把图标设置在原生的控件上,如Toolbar等。onJsAlert():处理JS的Alert对话框。onJsPrompt():处理JS的Prompt对话框。onJsConfirm():处理JS的Confirm对话框。onPermissionRequest():Web页面请求Android权限时调用。onPermissionRequestCanceled():Web页面请求Android权限被取消时调用。onShowFileChooser():Web页面上传文件时调用。getVideoLoadingProgressView():自定义媒体文件播放加载时的进度条。getDefaultVideoPoster():设置媒体文件默认的预览图。onShowCustomView():媒体文件进入全屏时调用。onHideCustomView():媒体文件退出全屏时调用。WebChromeClient webChromeClient = new WebChromeClient();
复制代码
六、Js 调用 Android
注:该方法针对Android 4.2(API 17)及以上,只有标有@JavascriptInterface注解的public方法才能从js调用。而对targetSdkVersion为API Level 16及以下的app,js可以调用Java所有的public方法。 前置设定:webview.getSettings().setJavaScriptEnabled(true)
复制代码
addJavascriptInterface()安全风险及解决办法
Android 代码
public class Bridge{ @JavascriptInterface public void jsInvoke(String jsonParams) { //获取js传递过来的数据,并解析出方法名 try { JSONObject jsonObject = new JSONObject(json); final String callback = jsonObject.optString("callbackMethod"); //根据获得的方法名,安卓进行回调,回调前封装数据过去 //封装数据 final JSONObject data = new JSONObject(); data.put("name", "zhangsan"); data.put("age", "38");// Toast.makeText(mContext,json,Toast.LENGTH_LONG).show(); mHandler.post(new Runnable() { @Override public void run() { //回调,需要保证在主线程中 mWebView.loadUrl("javascript:" + callbackMethod + "(" + data.toString() + ")"); } }); } catch (JSONException e) { e.printStackTrace(); } } //JavascriptInterface 可以标注多个方法 @JavascriptInterface public void xxxx(String jsonParams) { }} Bridge bridge = new Bridge();mWebView.addJavascriptInterface(bridge,"androidJsInterface");
复制代码
Js 代码
//将需要 安卓回调的方法名 以callbackMethod方式传递 action 可自定义操作类型 callbackMethod App可调用的JS方法,用于Js的回调 // 注:androidJsInterface 由安卓传递的对象别名,也就是Js能拿到java端的桥对象
例子1:function xxx(){var json = {"action":"startScan","callbackMethod":"getScanResult","port":"xxx"}; window.androidJsInterface.jsInvoke(JSON.stringify(json));}例子2: function xxx(){var json = {"action":"otherAction","callbackMethod":"otherCallback","port":"xxx"};window.androidJsInterface.jsInvoke(JSON.stringify(json));}例子3:function xxx(){var json = {"action":"noCallback","callbackMethod":"","port":"xxx"};window.androidJsInterface.jsInvoke(JSON.stringify(json));}
复制代码
注:这个方法的本来是拦截所有WebView的Url跳转的。我们可以通过这个API 构造一个特殊自定义格式的Url跳转,shouldOverrideUrlLoading拦截Url后判断其格式,然后Native 解析到对应的方法和数据再去执行本地方法。
复制代码
Android 代码
webview.setWebViewClient(new WebViewClient() { @Override public boolean shouldOverrideUrlLoading(WebView view, String url) { Log.i("qcl0228", "拦截到的url:" + url); //url如果以qiushi开头,就是h5和我们定义的传值协议 if (url.startsWith("qiushi")) { Uri uriRequest = Uri.parse(url); String scheme = uriRequest.getScheme(); String action = uriRequest.getHost(); String query = uriRequest.getQuery(); if ("qiushi".equals(scheme)) { if (!TextUtils.isEmpty(query)) { //把url携带的参数存到一个map里 HashMap maps = new HashMap(); Set<String> names = uriRequest.getQueryParameterNames(); for (String name : names) { maps.put(name, uriRequest.getQueryParameter(name)); } JSONObject jsonObject = new JSONObject(maps); if ("setH5Info".equals(action)) { if (jsonObject != null && jsonObject.has("params")) { String h5InfoParams = jsonObject.optString("params"); Log.i("qcl0228", "拦截到的参数:" + h5InfoParams); } } } } } else { view.loadUrl(url); } return true; } });
复制代码
拦截到的 url:qiushi://setH5Info?param=%7B%22title%22:%22%E5%95%86%E5%93%81%E6%A0%87%E9%A2%98%22%7D 拦截到的参数:{"title":"商品标题"}
Js 代码
//简单重定向<div id="div" onclick="chongdingxiang()"style="width:100px; height:100px; background-color:#099;"></div><script type="text/javascript">function chongdingxiang(){ //模拟重定向 window.location.href="qiushi://setH5Info?params=%7B%22title%22%3A%22%E5%95%86%E5%93%81%E8%AF%A6%E6%83%85%22%7D";}</script>
复制代码
七、Android 调用 Js
注: 这是无JS返回值的调用 且js方法不能有返回值,如下js代码 若有返回值,web则会发生请求重定向问题 function jsMethod(jsonParams){ return "123456"; };
复制代码
若调用的JS有返回值,webView会发生请求重定向
android 代码
String jsonParams = "123456"; //String url = "javascript:jsMethod()";//不拼接参数,直接调用js的jsMethod函数 String url = "javascript:jsMethod(" + jsonParams + ")";//拼接参数,就可以把数据传递给js webview.loadUrl(url);
复制代码
js 代码
例子1 function jsMethod(){ console.log("callbackMethod执行了"); };
例子2 function jsMethod(jsonParams){ console.log("callbackMethod执行了"); alert(JSON.stringify(jsonParams)); };
例子3 fun HyJsBridge.onComplete(port,json){ };
复制代码
注:可以获取js方法的返回值,仅支持android4.4及以上
复制代码
android 代码
callback =new ValueCallback<String>() { @Override public void onReceiveValue(String value) { Log.i("qcl0228", "js返回的数据" + value); } });mWebView.evaluateJavascript("javascript:jsMethod("+ jsonParams +")",callback);
或者不需要回调mWebView.evaluateJavascript("javascript:jsMethod("+ jsonParams +")",null);
复制代码
js 代码
// js中的方法 --> 将会被Android端调用 例子1 function jsMethod(jsonParams){ return "123456"; };
复制代码
js 返回的数据:123456
八、Html Demo
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"><html xmlns="http://www.w3.org/1999/xhtml"><head> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/> <title>Untitled Document</title></head><body><div id="div" onclick="getAndroidValue()"style="width:100px; height:100px; background-color:#099;"></div><script type="text/javascript">function chongdingxiang(){ //模拟重定向 window.location.href="qiushi://setH5Info?params=%7B%22title%22%3A%22%E5%95%86%E5%93%81%E8%AF%A6%E6%83%85%22%7D";}function jsMethod(jsonParams){ document.getElementById("div").style.backgroundColor='red'; document.getElementById("div").innerHTML=jsonParams; return '123456';}function getAndroidValue(){ document.getElementById("div").innerHTML=window.androidObject.androidMethod();}</script></body></html>
复制代码
九、Webview 漏洞及安全设置
JS调用Android代码是通过addJavascriptInterface接口进行对象映射。
复制代码
漏洞产生的原因
// java代码public class AndroidJS extends Object { @JavascriptInterface public void hello(String msg) { System.out.println("JS调用了Android的hello方法"); }}//参数1:Android的本地对象 参数2:对象别名webView.addJavascriptInterface(new AndroidJS(), "test");
复制代码
// JS代码function callAndroid(){ test.hello("js调用了android中的hello方法");}
复制代码
JS 通过 test 对象别名 映射到 Android 的地对象 AndroidJs(),从而实现 JS 调用 Android 的对象和方法。
漏洞产生原因是:当 JS 拿到 Android 这个对象后,就可以调用这个 Android 对象中所有的方法,包括系统类(java.lang.Runtime 类),从而进行任意代码执行。如可以执行命令获取本地设备的 SD 卡中的文件等信息从而造成信息泄露
具体获取系统类的描述(结合 Java 反射机制):Android 中的对象有一公共的方法——getClass(),该方法可以获取到当前类类型 Class,该类有一关键的方法——Class.forName,该方法可以加载一个类(可加载 java.lang.Runtime 类),而该类是可以执行本地命令的。
以下是攻击的 JS 核心代码:
function execute(cmdArgs) { // 步骤1:遍历window对象,目的是为了找到包含getClass()的对象,因为Android映射的JS对象在window中,所以肯定会遍历到 for (var obj in window) { if ("getClass" in window[obj]) { // 步骤2:利用反射调用forName方法得到Runtime类对象 alert(obj); return window[obj].getClass().forName("java.lang.Runtime") // 步骤3:以后就可以调用静态方法来执行一些命令,比如访问文件的命令 getMethod("getRuntime",null).invoke(null,null).exec(cmdArgs); // 从执行命令后返回的输入流中得到字符串,有很严重暴露隐私的危险 // 如执行完访问文件的命令之后,就可以得到文件名的信息了 } } }
复制代码
当一些 APP 通过扫描二维码打开一个外部网页时,攻击者就可以执行这段 JS 代码进行漏洞攻击。
解决方案:
方案 1:在 Android 4.2 版本之后,Google 在 Android 4.2 版本中规定对被调用的函数以 @JavascriptInterface 进行注解从而避免漏洞攻击。
方案 2:Android 4.2 版本之前,采用拦截 prompt()进行漏洞修复。 具体如下:
每次当 WebView 加载页面前加载一段本地的 JS 代码,原理是:让 JS 调用一 Javascript 方法:该方法是通过调用 prompt 把 JS 中的信息(含特定标识,方法名称等)传递到 Android 端;在 Android 的 onJsPrompt 中 ,解析传递过来的信息,再通过反射机制调用 Java 对象的方法,这样实现安全的 JS 调用 Android 代码;(关于 Android 返回给 JS 的值,可通过 prompt 把 Java 中方法的处理结果返回到 JS 中)
javascript:(function JsAddJavascriptInterface_() { // window.jsInterface表示在window上声明了一个Js对象,jsInterface = 注册的对象名 // 它注册了两个方法,onButtonClick(arg0)和onImageClick(arg0, arg1, arg2),如果有返回值,就添加上return if (typeof(window.jsInterface)!='undefined') { console.log('window.jsInterface_js_interface_name is exist!!'); } else { window.jsInterface = {
onButtonClick:function(arg0) { // prompt()返回约定的字符串,该字符串可自己定义 // 包含特定的标识符MyApp和JSON字符串(方法名,参数,对象名等) return prompt('MyApp:'+JSON.stringify({obj:'jsInterface',func:'onButtonClick',args:[arg0]})); },
onImageClick:function(arg0,arg1,arg2) { return prompt('MyApp:'+JSON.stringify({obj:'jsInterface',func:'onImageClick',args:[arg0,arg1,arg2]})); }, }; } })()
复制代码
关闭密码保存提醒:webSettings.setSavePassword(false)
复制代码
如下代码:public class WebViewActivity extends Activity { private WebView webView; public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_webview); webView = (WebView) findViewById(R.id.webView); //webView.getSettings().setAllowFileAccess(false); (1) //webView.getSettings().setAllowFileAccessFromFileURLs(true); (2) //webView.getSettings().setAllowUniversalAccessFromFileURLs(true); (3) Intent i = getIntent(); String url = i.getData().toString(); //url = file:///data/local/tmp/attack.html webView.loadUrl(url); }}
复制代码
在清单文件中将 WebViewActivity 设置 android:exported = "true"属性,表示当前 Activity 是否可以被另一个 Application 的组件启动。即 A 应用可以通过 B 应用导出的 Activity 让 B 应用加载一个恶意的 file 协议的 url,从而可以获取 B 应用的内部私有文件,从而带来数据泄露威胁。
当其他应用启动可以允许外部调用的 Activity 时, intent 中的 data 直接被当作 url 来加载(假定传进来的 url 为 file:///data/local/tmp/attack.html),其它 APP 通过使用显式 ComponentName 或者其他类似方式就可以很轻松的启动该 WebViewActivity 并加载恶意 url。
对于不需要使用 file 协议的应用,禁用 file 协议:
setAllowFileAccess(false);
复制代码
对于需要使用 file 协议的应用,禁止 file 协议加载 JavaScript:
setAllowFileAccess(true); // 禁止file协议加载 JavaScriptif (url.startsWith("file://") { setJavaScriptEnabled(false);} else { setJavaScriptEnabled(true);}
复制代码
文章引述及参考
webview参数详解 潜力之心—简书android与js通信 编程的小石头—知乎android与js通信 竖子敢尔—CSDNweb安全漏洞 橙子19911016—CSDN
评论