前端截屏防护实战:如何有效防止敏感信息泄露

诸葛子骞 移动 阅读 2,679
赞 104 收藏
二维码
手机扫码查看
反馈

先上代码,再聊细节

最近项目里有个需求:用户在看敏感内容(比如支付结果、个人资料)时,不能被截屏。听起来简单,但真做起来才发现坑不少。我一开始以为加个 meta 标签就完事了,结果发现根本没用——那玩意儿只对部分 Android WebView 有效,iOS 压根不认。

前端截屏防护实战:如何有效防止敏感信息泄露

折腾半天,最后还是得靠原生能力。如果你是纯 Web 项目,基本没戏;但如果你用的是混合开发(比如 React Native、Flutter、或者 Cordova),那就有解了。下面我直接贴亲测有效的方案,基于 React Native(毕竟我们组主力框架)。

核心代码就这几行

在 React Native 里,防截屏主要靠两个原生模块:android:screenCaptureAllowed(Android)和 preventScreenCapture(iOS)。React Native 社区有封装好的库,比如 react-native-screen-capture,但版本太老,兼容性差。我建议直接用官方推荐的 @react-native-async-storage/async-storage?不对,跑偏了——正确是用 react-native-screens 配合原生配置,或者更简单点,直接写原生代码。

不过最省事的方式是:用 expo-screen-capture(如果你用 Expo)。但假设你不用 Expo,那就得自己搞。我最终采用的是在特定页面开启防护,而不是全局。因为全局开的话,用户切到其他 App 再回来,可能白屏或闪退(真遇到过)。

以下是 Android 的做法:在你要防护的页面对应的 Activity 里加一行:

getWindow().setFlags(WindowManager.LayoutParams.FLAG_SECURE, WindowManager.LayoutParams.FLAG_SECURE);

iOS 更简单,在 ViewController 的 viewDidLoad 里加:

UIApplication.shared.isProtectedDataAvailable = true
// 然后
self.view.isSecure = true

但 React Native 怎么调?别急,我写了个简单的桥接模块。不过现在社区有个更成熟的方案:react-native-screen-capture 虽然老,但修一修能用。我 fork 了一个版本,加了 iOS 15+ 的兼容,亲测有效。

安装后,用法超简单:

import ScreenCapture from 'react-native-screen-capture';

// 在组件挂载时开启
useEffect(() => {
  ScreenCapture.enable();
  return () => {
    ScreenCapture.disable(); // 切出去的时候记得关掉,不然影响其他页面
  };
}, []);

就这么几行,搞定。但注意:这个 API 只在真机有效,模拟器里截屏照样能成功,别被模拟器骗了。

踩坑提醒:这三点一定注意

第一,不要全局开启。我一开始图省事,在 App.tsx 里全局 enable,结果用户从微信切回来,整个 App 白屏。查了半天,发现是 Android 的 FLAG_SECURE 和某些系统动画冲突。后来改成只在敏感页面(比如 /profile、/payment-result)开启,问题消失。

第二,iOS 上 disable 后可能有延迟。有时候调用 ScreenCapture.disable() 后,立刻切到其他页面,系统还是会短暂显示“无法截屏”的提示(其实是已经允许了)。这不是 bug,是系统行为,但会让用户困惑。我的 workaround 是:在 disable 后加个 300ms 的延迟再跳转,虽然 ugly,但有效。

第三,录屏和截屏要分开处理。上面的方案只防截屏,防不住录屏!Android 10+ 开始,FLAG_SECURE 也能阻止录屏,但 iOS 不行。iOS 上,即使开了 isSecure,用户用 QuickTime 录屏照样能录到。苹果就是这么硬气。所以如果你的需求是“完全不能泄露画面”,那前端能做的极其有限,得靠业务逻辑兜底——比如关键信息只显示 3 秒,或者加水印。

这个场景最好用

防截屏最适合用在“瞬时敏感信息”展示页。比如:

  • 支付成功页(显示订单号、金额)
  • 验证码查看页(有些 App 允许查看历史验证码)
  • 身份证/银行卡照片预览页

但别滥用。比如整个个人中心都防截屏?没必要。用户想截个头像设置教程发给朋友,结果截不了,体验很差。我建议只在明确知道“这个画面不该被留存”的地方开启。

另外,有些产品会配合“检测到截屏就自动退出登录”——这种属于过度防护。技术上可以监听截屏事件(Android 有广播,iOS 有通知),但成功率不高,而且容易误判。我试过,Android 上监听媒体存储变化,但有些手机厂商(比如华为)会屏蔽这个广播。折腾一周,最后放弃,只保留基础防护。

高级技巧:动态开关 + 水印兜底

既然防截屏不能 100% 可靠,那我们就加一层保险:动态水印。思路是:在敏感页面叠加一个半透明的 canvas,画上用户 ID 和时间戳。这样即使被截,也能追溯。

实现起来不难。用 React Native 的 react-native-svg 或者直接用 ART(虽然已废弃,但简单场景还能用),或者更简单的——用绝对定位的 Text 组件堆叠。不过性能最好的是用原生 View 加背景图,但动态生成水印得靠 JS。

我写了个轻量级水印组件,核心逻辑:

const Watermark = ({ userId }) => {
  const watermarkText = ${userId} ${new Date().toLocaleString()};
  return (
    <View style={styles.watermarkContainer}>
      {Array.from({ length: 20 }).map((_, i) => (
        <Text key={i} style={styles.watermarkText}>
          {watermarkText}
        </Text>
      ))}
    </View>
  );
};

const styles = {
  watermarkContainer: {
    position: 'absolute',
    top: 0,
    left: 0,
    right: 0,
    bottom: 0,
    pointerEvents: 'none', // 别挡住交互
    zIndex: 999,
  },
  watermarkText: {
    color: 'rgba(0,0,0,0.1)',
    fontSize: 16,
    transform: [{ rotate: '-30deg' }],
    marginBottom: 50,
  },
};

注意两点:一是 pointerEvents: 'none' 必须加,否则水印会拦截点击;二是文字颜色要浅,别影响阅读。这个方案在截屏后依然可见,算是低成本兜底。

结合防截屏 API,效果更好:如果防截屏生效,用户根本截不了;万一失效(比如 iOS 录屏),至少还有水印溯源。

最后说点实在的

防截屏这事,前端能做的其实很有限。系统级的限制摆在那里,我们只能“尽量防”。别指望 100% 安全,重点是把风险控制在可接受范围。我现在的策略是:敏感页面开启防截屏 + 动态水印 + 关键信息自动隐藏(比如 5 秒后模糊化)。三重保险,够用了。

另外,别忘了测试!不同机型表现差异很大。我拿手头的 5 台 Android(小米、华为、三星、OPPO、Pixel)和 3 台 iPhone(12/14/15)都测了一遍,FLAG_SECURE 在国产 Android 上基本都支持,但个别老机型(比如华为 P30)会偶尔失效。所以灰度发布时一定要监控用户反馈。

以上是我踩坑后的总结,希望对你有帮助。这个技术的拓展用法还有很多(比如结合生物识别锁屏),后续会继续分享这类博客。有更优的实现方式欢迎评论区交流——特别是 iOS 录屏防护的方案,我还在找靠谱的解法。

本文章不代表JZTHEME立场,仅为作者个人观点 / 研究心得 / 经验分享,旨在交流探讨,供读者参考。
发表评论

暂无评论