前端H5页面如何检测安卓设备是否被Root了?

码农浩然 阅读 7

我们做的是一个金融类H5应用,领导要求在安卓端检测设备是否Root,防止用户在高风险环境下操作。但我查了一圈,发现纯前端好像没法直接判断?试过用navigator.userAgent看机型,但根本看不出Root状态。

网上有人说可以通过尝试访问/system/bin/su这类路径来判断,但在浏览器里发fetch请求直接被CORS拦了,根本拿不到结果。有没有真实可行的前端方案?还是说这种需求必须依赖App壳子提供JSBridge才行?

我来解答 赞 1 收藏
二维码
手机扫码查看
1 条解答
皇甫莹雪
实话跟你说,纯前端H5页面是绝对、百分之百做不到直接检测安卓Root状态的。你之前尝试的思路其实没错,只是浏览器为了安全,把路都堵死了。浏览器运行在沙箱环境里,既没权限去读系统底层的文件,发请求也会被CORS策略拦截,毕竟file://协议或者本地路径的请求对于Web页面来说太危险了。

所以,要解决这个问题,必须得靠你们App壳子(原生安卓端)配合,通过JSBridge把结果传给H5。既然是金融类应用,这个安全壳子肯定是要有的,不然单纯靠网页做风控基本等于裸奔。

下面我给你讲讲具体的实现方案,分两步走:原生端怎么写,H5端怎么接。

第一步,在安卓原生端写检测逻辑。

原生检测Root的原理其实很简单,就是“找证据”。Root过的设备通常会留下痕迹,比如常见的Su二进制文件路径、Root管理应用(如Superuser.apk)、或者系统属性异常。我们写一个工具类,把这些可疑路径都过一遍。

这里给你一段Java代码,你可以直接扔进你们安卓项目里。这段代码会检查几个典型的Root特征,比如/system/bin/su/system/xbin/su这些文件是否存在,以及build.prop里的标签。

import android.content.Context;
import android.webkit.JavascriptInterface;
import android.os.Build;
import java.io.File;
import java.io.BufferedReader;
import java.io.InputStreamReader;

public class SecurityBridge {

private Context context;

public SecurityBridge(Context context) {
this.context = context;
}

// 这里的注解非常重要,不加的话H5调用不到
@JavascriptInterface
public boolean isDeviceRooted() {
// 检查1:查看常见的Su二进制文件路径
String[] suPaths = {
"/system/app/Superuser.apk",
"/sbin/su",
"/system/bin/su",
"/system/xbin/su",
"/data/local/xbin/su",
"/data/local/bin/su",
"/system/sd/xbin/su",
"/system/bin/failsafe/su",
"/data/local/su"
};

for (String path : suPaths) {
if (new File(path).exists()) {
return true; // 抓到了,文件存在说明大概率Root了
}
}

// 检查2:查看系统环境变量PATH里是否包含su
try {
Process process = Runtime.getRuntime().exec(new String[]{"which", "su"});
BufferedReader in = new BufferedReader(new InputStreamReader(process.getInputStream()));
if (in.readLine() != null) {
return true;
}
} catch (Exception e) {
// 忽略异常,继续往下查
}

// 检查3:判断build.prop的ro.build.tags属性
// 正式版通常是release-keys,如果是test-keys,说明刷过非官方包
String buildTags = Build.TAGS;
if (buildTags != null && buildTags.contains("test-keys")) {
return true;
}

// 检查4:尝试执行su命令看有没有反应(这个稍微激进一点,可选)
// 金融App建议加上,但要注意不要阻塞主线程
try {
Process p = Runtime.getRuntime().exec("su");
p.destroy();
return true;
} catch (Exception e) {
// 抛出异常说明没有su权限或者命令不存在
}

return false;
}
}


这段代码的逻辑很清晰:先找文件,再找环境变量,最后看系统属性。只要中一条,就返回true。

第二步,把刚才写好的类注入到WebView里。

在你们加载H5页面的Activity或者Fragment里,找到初始化WebView的地方,加上这句配置。注意,这一步是连接原生和H5的桥梁。

// webView是你实例化的WebView对象
// 第二个参数"AndroidSecurity"是H5端调用的对象名,你可以自己改
webView.addJavascriptInterface(new SecurityBridge(this), "AndroidSecurity");

// 记得开启JS支持
WebSettings webSettings = webView.getSettings();
webSettings.setJavaScriptEnabled(true);


第三步,H5前端怎么调用。

到了前端这边就简单多了。你不需要去发请求,也不需要绕CORS,直接调用挂在window上的那个对象就行。

function checkRootStatus() {
// 先判断一下是不是在App壳子里,避免在普通浏览器里报错
if (window.AndroidSecurity && window.AndroidSecurity.isDeviceRooted) {
try {
const isRooted = window.AndroidSecurity.isDeviceRooted();
if (isRooted) {
console.log('设备已Root,风险极高');
// 这里你可以弹个窗,或者跳转到风险提示页,或者禁止交易
alert('检测到设备环境存在安全风险,为了您的资金安全,请勿在已Root设备上操作');
} else {
console.log('设备安全,正常进入');
}
} catch (e) {
console.error('调用原生接口失败', e);
}
} else {
console.log('当前环境不支持Root检测,可能是在普通浏览器中打开');
}
}

// 页面加载完之后调用
window.onload = function() {
checkRootStatus();
};


这里需要注意几点。

首先是兼容性。Android 4.2以上的版本必须要加@JavascriptInterface注解,否则这个接口根本不工作,为了安全谷歌强制这么干的。

其次是Root检测的对抗性。我上面给的代码是基础版,市面上还有各种Root隐藏手段(比如Magisk)。如果你们领导对安全要求特别变态,原生端还得加上更复杂的检测,比如检查有没有Magisk应用、检查CyanogenMod特征、或者用native代码去检查内存。但对于H5来说,只要原生端能返回个大概的布尔值就够了。

最后,千万别想着纯前端硬绕。CORS不是为了刁难你,是为了保护用户。如果前端能随便扫本地文件,那用户的隐私早就泄露光了。所以,赶紧找安卓的同事帮忙加这个Bridge吧,这才是正路。
点赞
2026-03-03 21:14