前端项目完整防窃取&安全防护实战笔记

文章目录

一、笔记前言 & 核心安全认知

1.1 核心安全定律

前端无绝对安全,只有「提高窃取成本、可溯源、防普通盗爬」」

所有前端 JS/CSS 防护均可被以下方式绕过:

  • 关闭 JavaScript
  • 浏览器抓包、view-source 查看源码
  • 系统截图、录屏、OCR 识别
  • 爬虫无头浏览器绕过检测
    真正的安全 = 前端多层防护 + 后端强校验 + 资源防盗链 + 接口签名 + 行为溯源
    1.2 本笔记覆盖范围
    整合全网验证、企业级项目实战方案:内容防复制、防右键、防图片保存、防调试控制台、页面水印、隐形暗水印、SM3国密签名、防XSS、防iframe劫持、防爬虫、防打印、防截屏API、存储加密、接口安全、页面脱敏等全套方案。
    所有功能全部组件化/Hooks化/指令化,支持 Vue3+TS / 原生 JS 项目复用。
    1.3 项目防护分层架构(标准落地顺序)
  1. 底层服务端安全:安全响应头、防盗链、接口鉴权、防重放、SM3签名校验
  2. 浏览器基础安全:CSP策略、XSS防御、iframe防嵌套、权限策略
  3. 页面内容防窃取:禁止复制/右键/拖拽/保存图片/打印
  4. 环境防调试防爬:控制台冻结、无限debugger、开发者工具检测、爬虫检测
  5. 溯源安全:明水印、隐形暗水印、用户唯一标识溯源
  6. 动态脱敏安全:页面切后台脱敏、窗口失焦保护

二、页面内容防窃取核心模块(用户侧防盗)

2.1 禁止复制/剪切/粘贴(useCopyGuard)

功能:全局禁止复制,或复制自动追加版权溯源文本

适用场景:文档、合同、付费内容、涉密页面

绕过漏洞:关闭JS、查看源码手动复制

2.2 禁止右键菜单(useContextMenuGuard)

核心优化:不拦截 input/textarea/可编辑区域,不影响正常业务操作

功能:禁止右键另存为、检查元素、查看网页源代码

2.3 图片防保存/防拖拽/防长按(useImageGuard)

覆盖端:PC拖拽保存、移动端长按保存、图片另存为

实现原理:拦截 dragstart + 禁用移动端触摸菜单 + 禁止选中

2.4 全局文本禁止选中(vProtectContent 指令)

统一兼容多浏览器 user-select 样式,禁止鼠标选中页面内容,辅助防复制。

2.5 禁止页面打印(usePrintGuard)

拦截浏览器 Ctrl+P 打印行为,同时搭配 print 媒体查询,即使绕过JS也只打印水印文字。


三、防调试、防扒源码、防控制台篡改模块

3.1 控制台防护(useDevToolsGuard)

三大核心能力:

  • 冻结 console 对象,防止被覆盖篡改
  • 无限 debugger 阻塞控制台调试
  • 窗口尺寸差值检测开发者工具是否打开
    作用:极大提高前端源码调试、逆向、接口扒取成本

四、页面水印溯源体系(核心溯源手段,无法彻底规避)

4.1 显性画布水印组件(Watermark.vue)

核心特性(企业级)

  • Canvas 绘制,不占用DOM节点,性能高
  • MutationObserver 监听:删除水印、修改样式、隐藏DOM自动重建
  • 窗口resize自动重绘
  • pointer-events:none 不影响任何页面交互
  • 支持自定义内容、透明度、角度、间距
    价值:截图泄露可直接溯源到具体用户、账号、时间
    4.2 隐形暗水印(进阶)
    将用户信息、设备信息嵌入图片像素,肉眼不可见,截图/保存图片后可后台解析溯源。
    局限:重度压缩、裁剪、马赛克会失效

五、国密安全 & 接口防篡改体系

5.1 SM3 国密签名(useSm3Crypto)

业务作用:

  • 接口请求参数签名,防止参数篡改
  • 配合 timestamp + nonce 防止接口重放攻击
  • 响应数据摘要校验,保证数据完整性
    安全红线:前端禁止存放核心密钥,仅做签名展示,后端全权校验
    5.2 接口安全增强(useRequestSecure)
    统一生成请求头:时间戳、随机串、签名、请求唯一ID,用于日志溯源、防刷、防重放。

六、前端基础安全防护(防XSS、防劫持、防爬虫)

6.1 CSP 内容安全策略

浏览器级安全策略,禁止非法脚本、eval、外部资源注入,最有效防XSS手段。优先后端响应头配置,备选 Meta 标签。

6.2 XSS 输入过滤(useXssGuard)

转义特殊字符,防止用户输入恶意脚本注入,富文本场景搭配 DOMPurify 使用。

6.3 防 iframe 嵌套点击劫持(useFrameGuard)

检测页面是否被嵌套,自动跳出顶层框架;搭配后端 X-Frame-Options 头双重防护。

6.4 爬虫/无头浏览器检测(useEnvDetect)

检测 webdriver 标记,识别自动化爬虫、模拟器访问,拦截批量爬取数据。

6.5 禁止浏览器截屏API(useScreenCaptureGuard)

拦截网页录屏、窗口捕获API,无法阻止系统截屏,仅阻断网页级窃取。

6.6 剪贴板防护(useClipboardReadGuard)

禁用网页读取剪贴板权限,防止恶意脚本窃取用户剪贴板隐私。


七、前端存储安全 & 动态脱敏防护

7.1 本地存储加密过期(useStorageSecure)

对 localStorage 数据加密、自动过期,避免明文存储用户信息、业务数据。

规范:敏感数据绝不存前端,仅非核心缓存使用。

7.2 页面切后台动态脱敏(usePageVisibilityGuard)

页面失焦/切后台自动模糊、隐藏敏感内容,切回恢复,防止后台挂机被偷窥、截图。


八、后端配套强制安全配置(前端无法替代)

所有前端防护必须搭配以下后端能力,否则形同虚设

  1. 图片资源防盗链:Referer 校验 + 临时签名URL + 过期时效
  2. Cookie 安全配置:HttpOnly、Secure、SameSite,禁止JS读取核心Cookie
  3. 浏览器安全响应头:
  • X-Frame-Options: DENY 防嵌套劫持
  • X-XSS-Protection 浏览器XSS防护
  • X-Content-Type-Options: nosniff
  • Referrer-Policy、Permissions-Policy
  1. 接口安全:SM3签名校验、时间戳窗口校验、防重放、接口权限拦截
  2. 访问日志溯源:记录用户、设备、IP、时间、操作行为,泄露可追责

九、所有防护方案漏洞汇总(真实项目踩坑)

  1. JS类防护通用漏洞:关闭JavaScript即可绕过所有复制、右键、调试拦截
  2. 源码泄露漏洞:view-source、抓包可查看原始代码,前端无法拦截
  3. 终极绕过手段:系统截图、录屏、手机拍照、OCR识别,无任何前端手段可彻底阻止
  4. 爬虫绕过:无头浏览器可修改webdriver标记,绕过爬虫检测
  5. 水印漏洞:强压缩、裁剪、马赛克可破坏暗水印,明水印只能溯源不能阻止盗取
  6. CSP漏洞:配置不当会导致页面样式、脚本失效,需精细化配置白名单

10、项目目录标准化结构(可直接落地)

src/protect

├── hooks/

│ ├── useCopyGuard.ts // 复制防护

│ ├── useContextMenuGuard.ts // 右键防护

│ ├── useImageGuard.ts // 图片防盗

│ ├── useDevToolsGuard.ts // 控制台防调试

│ ├── usePrintGuard.ts // 禁止打印

│ ├── useXssGuard.ts // XSS过滤

│ ├── useFrameGuard.ts // 防iframe嵌套

│ ├── useEnvDetect.ts // 爬虫检测

│ ├── useScreenCaptureGuard.ts // 截屏API拦截

│ ├── useStorageSecure.ts // 存储加密

│ ├── usePageVisibilityGuard.ts // 切页脱敏

│ ├── useClipboardReadGuard.ts // 剪贴板防护

│ ├── useSm3Crypto.ts // SM3国密签名

│ └── useRequestSecure.ts // 接口安全

├── directives/

│ └── protectContent.ts // 禁止选中指令

├── components/

│ └── Watermark.vue // 防篡改水印组件

└── index.ts // 统一出口

目录严格按照笔记 src/protect,Vue3 + TypeScript,Hooks、指令、组件完整源码,可直接复制到项目使用

⚠️注意:sm‑crypto 需要安装依赖:npm install sm‑crypto

src/protect/hooks/useCopyGuard.ts

ts 复制代码
/**
 * 复制剪切防护:禁止复制 / 复制追加版权文本
 */
type CopyMode = 'disable' | 'append'

export function useCopyGuard(mode: CopyMode = 'disable', copyrightText = "\n内容版权所有,禁止私自转载") {

  const handleCopy = (e: ClipboardEvent) => {
    if (mode === 'disable') {
      e.preventDefault()
      return
    }
    const selText = window.getSelection()?.toString() ?? ''
    e.clipboardData?.setData('text/plain', selText + copyrightText)
    e.preventDefault()
  }

  const handleCut = (e: ClipboardEvent) => {
    e.preventDefault()
  }

  const bind = () => {
    document.addEventListener('copy', handleCopy)
    document.addEventListener('cut', handleCut)
  }

  const unbind = () => {
    document.removeEventListener('copy', handleCopy)
    document.removeEventListener('cut', handleCut)
  }

  return { bind, unbind }
}

src/protect/hooks/useContextMenuGuard.ts

ts 复制代码
/**
 * 右键菜单防护,放过 input/textarea/contenteditable
 */
export function useContextMenuGuard() {
  const handler = (e: MouseEvent) => {
    const target = e.target as HTMLElement
    const tag = target.tagName
    if (['INPUT', 'TEXTAREA'].includes(tag) || target.isContentEditable) {
      return
    }
    e.preventDefault()
  }

  const bind = () => document.addEventListener('contextmenu', handler)
  const unbind = () => document.removeEventListener('contextmenu', handler)

  return { bind, unbind }
}

src/protect/hooks/useImageGuard.ts

ts 复制代码
/**
 * 图片防拖拽、移动端长按保存
 */
export function useImageGuard() {
  const dragHandler = (e: DragEvent) => e.preventDefault()

  const bind = () => {
    document.querySelectorAll<HTMLImageElement>('img').forEach(img => {
      img.addEventListener('dragstart', dragHandler)
      img.style.webkitTouchCallout = 'none'
      img.style.userSelect = 'none'
    })
  }

  const unbind = () => {
    document.querySelectorAll<HTMLImageElement>('img').forEach(img => {
      img.removeEventListener('dragstart', dragHandler)
    })
  }

  return { bind, unbind }
}

src/protect/hooks/useDevToolsGuard.ts

ts 复制代码
/**
 * 开发者工具防护:冻结console、无限debugger、检测控制台打开
 */
export function useDevToolsGuard(onDetect?: () => void) {
  let debugTimer: number | null = null
  let detectTimer: number | null = null

  // 冻结console
  const freezeConsole = () => {
    try {
      Object.freeze(console)
    } catch {}
  }

  // 无限debugger
  const infiniteDebugger = () => {
    const fn = () => {
      try { debugger } catch {}
    }
    debugTimer = window.setInterval(fn, 400)
  }

  // 检测devtools窗口尺寸
  const detect = () => {
    const threshold = 150
    const wDiff = window.outerWidth - window.innerWidth
    const hDiff = window.outerHeight - window.innerHeight
    if (wDiff > threshold || hDiff > threshold) {
      onDetect?.()
    }
  }

  const start = () => {
    freezeConsole()
    infiniteDebugger()
    detectTimer = window.setInterval(detect, 700)
  }

  const stop = () => {
    if (debugTimer) clearInterval(debugTimer)
    if (detectTimer) clearInterval(detectTimer)
  }

  return { start, stop }
}

src/protect/hooks/usePrintGuard.ts

复制代码
/**
 * 拦截浏览器打印 Ctrl+P
 */
export function usePrintGuard() {
  const beforePrintHandler = (e: Event) => {
    e.preventDefault()
  }

  const bind = () => window.addEventListener('beforeprint', beforePrintHandler)
  const unbind = () => window.removeEventListener('beforeprint', beforePrintHandler)

  return { bind, unbind }
}

src/protect/hooks/useXssGuard.ts

ts 复制代码
/**
 * XSS简单转义,富文本建议使用DOMPurify
 */
export function useXssGuard() {
  const escapeHtml = (str: string): string => {
    if (!str) return ''
    const map: Record<string, string> = {
      '&': '&amp;',
      '<': '&lt;',
      '>': '&gt;',
      '"': '&quot;',
      "'": '&#039;'
    }
    return str.replace(/[&<>"']/g, m => map[m])
  }

  return { escapeHtml }
}

src/protect/hooks/useFrameGuard.ts

ts 复制代码
/**
 * 防iframe嵌套点击劫持
 */
export function useFrameGuard() {
  const check = () => {
    if (window.top !== window.self) {
      window.top.location.replace(window.location.href)
    }
  }
  return { check }
}

src/protect/hooks/useEnvDetect.ts

ts 复制代码
/**
 * 简易爬虫/无头浏览器检测
 */
export function useEnvDetect(onDetect: () => void) {
  const checkRobot = () => {
    if ((navigator as any).webdriver) {
      onDetect()
      return true
    }
    return false
  }
  return { checkRobot }
}

src/protect/hooks/useScreenCaptureGuard.ts

ts 复制代码
/**
 * 拦截网页录屏API,不能阻止系统截图
 */
export function useScreenCaptureGuard() {
  const blockCapture = () => {
    if (!navigator.mediaDevices?.getDisplayMedia) return
    const original = navigator.mediaDevices.getDisplayMedia
    navigator.mediaDevices.getDisplayMedia = async (...args) => {
      throw new Error('屏幕捕获已禁用')
      return await original.apply(navigator.mediaDevices, args)
    }
  }
  return { blockCapture }
}

src/protect/hooks/useStorageSecure.ts

ts 复制代码
/**
 * localStorage简单base64封装,带过期;禁止存真正敏感密码密钥
 */
export function useStorageSecure() {
  const setEncryptItem = (key: string, value: string, expireMs: number) => {
    const payload = { value, expire: Date.now() + expireMs }
    localStorage.setItem(key, btoa(JSON.stringify(payload)))
  }

  const getItem = (key: string): string | null => {
    const raw = localStorage.getItem(key)
    if (!raw) return null
    try {
      const data = JSON.parse(atob(raw))
      if (Date.now() > data.expire) {
        localStorage.removeItem(key)
        return null
      }
      return data.value
    } catch {
      return null
    }
  }

  return { setEncryptItem, getItem }
}

src/protect/hooks/usePageVisibilityGuard.ts

ts 复制代码
/**
 * 页面切后台/失焦监听,用于敏感内容自动脱敏
 */
export function usePageVisibilityGuard(onHide: () => void, onShow: () => void) {
  const handler = () => {
    if (document.hidden) {
      onHide()
    } else {
      onShow()
    }
  }

  const bind = () => document.addEventListener('visibilitychange', handler)
  const unbind = () => document.removeEventListener('visibilitychange', handler)

  return { bind, unbind }
}

src/protect/hooks/useClipboardReadGuard.ts

ts 复制代码
/**
 * 阻止网页JS读取剪贴板
 */
export function useClipboardReadGuard() {
  const bind = () => {
    if (!navigator.clipboard) return
    ;(navigator.clipboard as any).read = () => Promise.reject(new Error('剪贴板读取被禁止'))
    ;(navigator.clipboard as any).readText = () => Promise.reject(new Error('剪贴板读取被禁止'))
  }
  return { bind }
}

src/protect/hooks/useSm3Crypto.ts

ts 复制代码
/**
 * SM3国密摘要与请求签名工具
 * 重要:密钥secret禁止前端硬编码
 */
import { sm3 } from 'sm‑crypto'

export function useSm3Crypto() {
  const getSm3Hash = (plainText: string): string => {
    return sm3(plainText)
  }

  const createSign = (params: Record<string, any>, secret: string) => {
    const timestamp = Date.now().toString()
    const nonce = Math.random().toString(36).slice(2)
    const rawStr = Object.values(params).sort().join('') + timestamp + nonce + secret
    const sign = sm3(rawStr)
    return { sign, timestamp, nonce }
  }

  return { getSm3Hash, createSign }
}

src/protect/hooks/useRequestSecure.ts

ts 复制代码
/**
 * 请求安全头封装,配合SM3签名
 */
import { useSm3Crypto } from './useSm3Crypto'

export function useRequestSecure() {
  const { createSign } = useSm3Crypto()

  const getSecurityHeaders = (params: Record<string, any>, secret: string) => {
    const { sign, timestamp, nonce } = createSign(params, secret)
    return {
      'X‑Timestamp': timestamp,
      'X‑Nonce': nonce,
      'X‑Sign': sign,
      'X‑Request‑Id': crypto.randomUUID()
    }
  }

  return { getSecurityHeaders }
}

src/protect/directives/protectContent.ts

ts 复制代码
/**
 * v‑protect‑content 禁止文本选中指令
 */
import type { Directive } from 'vue'

export const vProtectContent: Directive = {
  mounted(el) {
    el.style.userSelect = 'none'
    el.style.webkitUserSelect = 'none'
    el.style.msUserSelect = 'none'
    el.style.MozUserSelect = 'none'
  }
}

src/protect/components/Watermark.vue

html 复制代码
<template>
  <div ref="watermarkRef" class="watermark-wrap"></div>
</template>

<script setup lang="ts">
import { onMounted, onUnmounted, ref, watch } from 'vue'

interface WatermarkOption {
  content: string
  fontSize?: number
  color?: string
  rotate?: number
  gap?: number
}

const props = defineProps<{
  option: WatermarkOption
}>()

const watermarkRef = ref<HTMLDivElement | null>(null)
let observer: MutationObserver | null = null
let resizeFn: () => void

function generateBg(opt: WatermarkOption): string {
  const canvas = document.createElement('canvas')
  const { content, fontSize = 14, color = 'rgba(180,180,180,0.12)', rotate = -25, gap = 130 } = opt
  canvas.width = gap
  canvas.height = gap
  const ctx = canvas.getContext('2d')!
  ctx.rotate((rotate * Math.PI) / 180)
  ctx.font = `${fontSize}px sans‑serif`
  ctx.fillStyle = color
  ctx.fillText(content, 12, gap / 2)
  return canvas.toDataURL('image/png')
}

function render() {
  if (!watermarkRef.value) return
  const bgUrl = generateBg(props.option)
  watermarkRef.value.style.cssText = `
    position: fixed;
    top: 0;
    left: 0;
    width: 100vw;
    height: 100vh;
    z‑index: 9999;
    pointer‑events: none;
    background‑repeat: repeat;
    background‑image: url(${bgUrl});
  `
}

onMounted(() => {
  render()
  observer = new MutationObserver(() => render())
  observer.observe(watermarkRef.value!, {
    attributes: true,
    childList: true,
    subtree: true,
    attributeFilter: ['style', 'class']
  })
  resizeFn = () => render()
  window.addEventListener('resize', resizeFn)
})

onUnmounted(() => {
  observer?.disconnect()
  window.removeEventListener('resize', resizeFn)
})

watch(() => props.option, () => render(), { deep: true })
</script>

<style scoped>
.watermark-wrap {
  overflow: hidden;
}
</style>

src/protect/index.ts 统一导出入口

ts 复制代码
// hooks
export { useCopyGuard } from './hooks/useCopyGuard'
export { useContextMenuGuard } from './hooks/useContextMenuGuard'
export { useImageGuard } from './hooks/useImageGuard'
export { useDevToolsGuard } from './hooks/useDevToolsGuard'
export { usePrintGuard } from './hooks/usePrintGuard'
export { useXssGuard } from './hooks/useXssGuard'
export { useFrameGuard } from './hooks/useFrameGuard'
export { useEnvDetect } from './hooks/useEnvDetect'
export { useScreenCaptureGuard } from './hooks/useScreenCaptureGuard'
export { useStorageSecure } from './hooks/useStorageSecure'
export { usePageVisibilityGuard } from './hooks/usePageVisibilityGuard'
export { useClipboardReadGuard } from './hooks/useClipboardReadGuard'
export { useSm3Crypto } from './hooks/useSm3Crypto'
export { useRequestSecure } from './hooks/useRequestSecure'

// directives
export { vProtectContent } from './directives/protectContent'

// components
export { default as Watermark } from './components/Watermark.vue'

App.vue 全局使用示例

html 复制代码
<script setup lang="ts">
import {
  useCopyGuard,
  useContextMenuGuard,
  useImageGuard,
  useDevToolsGuard,
  usePrintGuard,
  useFrameGuard,
  Watermark
} from '@/protect'

// 复制防护
const { bind: bindCopy } = useCopyGuard('disable')
// 右键防护
const { bind: bindRight } = useContextMenuGuard()
// 图片防盗
const { bind: bindImg } = useImageGuard()
// 打印防护
const { bind: bindPrint } = usePrintGuard()
// 防iframe嵌套
const { check: frameCheck } = useFrameGuard()
// 控制台防护
const { start: startDevProtect } = useDevToolsGuard(() => {
  console.warn('检测到开发者工具打开')
})

bindCopy()
bindRight()
bindImg()
bindPrint()
frameCheck()
startDevProtect()
</script>

<template>
  <Watermark :option="{ content: '内部文档|禁止外泄|userId‑001' }"/>
  <router-view />
</template>

CSS 全局打印补充(放到 global.css)

css 复制代码
@media print {
  body * {
    visibility: hidden !important;
  }
}

十一、全局统一引入使用方案

在 App.vue 全局一次性注册所有防护,全局生效,无需逐个页面引入。

敏感页面可单独开启动态脱敏、高阶水印强化防护。


十二、项目落地总结(核心结论)

  1. 前端防护的本质:防小白、防批量爬取、提高盗取成本、留存溯源证据
  2. 绝对安全依赖:后端校验 + 资源权限 + 签名防篡改 + 日志溯源
  3. 企业级标准方案:内容防护 + 环境防护 + 数据安全 + 溯源追责四层闭环
  4. 所有功能均已组件化,支持按需引入、按需关闭,不影响项目性能与业务
相关推荐
菜就多练forever10 天前
实战攻防中SNI技术应用
安全防护
行者-全栈开发4 个月前
【前端安全】CVE-2026-44578:Next.js SSRF 漏洞深度解析与修复实战指南
websocket·云原生·next.js·安全防护·vercel·cve-2026-44578·中间件绕过
一直会游泳的小猫4 个月前
gstack-guide
开源·安全防护·ai辅助开发·技能工具集·sprint流程
锐速网络4 个月前
锐速安全:跨境业务加速与安全防护一体化解决方案
waf·网络加速·安全防护·ddos防护·高防cdn·跨境业务·出海游戏
白山云北诗6 个月前
互联网常见网络攻击如何防护
网络·网络安全·ddos·waf·cc·安全防护
梵得儿SHI8 个月前
(第九篇)Spring AI 核心技术攻坚:安全防护 企业级 AI 应用的风控体系之全链路防护(API 安全到内容合规)
java·人工智能·安全·spring·安全防护·springai·企业级ai
微爱帮监所写信寄信9 个月前
微爱帮监狱写信寄信小程序针对互联网黑灰产攻击防护体系
小程序·负载均衡·信息与通信·安全架构·安全防护·监狱寄信
TracyCoder1239 个月前
DDoS/CC攻击防护常见措施
ddos·安全防护·cc攻击
xian_wwq9 个月前
【学习笔记】网络安全死于平庸
笔记·学习·安全防护