返回笔记首页

18.3 前端加密方案

主题配置

一、技术实现方案

1.1 前端加密架构

plain
前端加密体系
  ├── 接口签名
  │   ├── 签名算法(HMAC-SHA256)
  │   ├── 时间戳验证
  │   ├── 随机数(Nonce)
  │   └── 请求参数排序
  │
  ├── 代码混淆
  │   ├── JavaScript混淆
  │   ├── 变量名混淆
  │   ├── 控制流扁平化
  │   └── 字符串加密
  │
  ├── 数据加密
  │   ├── AES对称加密
  │   ├── RSA非对称加密
  │   ├── 前端存储加密
  │   └── 传输数据加密
  │
  └── 防护机制
      ├── 数字水印
      ├── 防调试
      ├── 防截图
      └── 反爬虫

1.2 技术栈

  • 加密: CryptoJS, JSEncrypt
  • 混淆: javascript-obfuscator, UglifyJS
  • 水印: Canvas API, SVG
  • 签名: HMAC-SHA256, MD5

二、接口签名算法

2.1 签名工具类

api-signature.js

javascript
import CryptoJS from 'crypto-js'

export class APISignature {
    constructor(options = {}) {
        this.appKey = options.appKey || 'default-app-key'
        this.appSecret = options.appSecret || 'default-app-secret'
        this.signatureExpiry = options.signatureExpiry || 300000 // 5分钟
    }

    // 生成签名
    generateSignature(params = {}, timestamp, nonce) {
        // 1. 添加公共参数
        const allParams = {
            ...params,
            app_key: this.appKey,
            timestamp: timestamp,
            nonce: nonce,
        }

        // 2. 参数排序
        const sortedKeys = Object.keys(allParams).sort()

        // 3. 拼接参数字符串
        const paramString = sortedKeys
            .map((key) => `${key}=${allParams[key]}`)
            .join('&')

        // 4. 添加密钥
        const signString = `${paramString}&key=${this.appSecret}`

        // 5. 计算签名
        const signature = CryptoJS.HmacSHA256(
            signString,
            this.appSecret
        ).toString()

        return signature
    }

    // 生成完整的请求参数
    signRequest(params = {}) {
        const timestamp = Date.now()
        const nonce = this.generateNonce()
        const signature = this.generateSignature(params, timestamp, nonce)

        return {
            ...params,
            app_key: this.appKey,
            timestamp: timestamp,
            nonce: nonce,
            sign: signature,
        }
    }

    // 验证签名(客户端模拟,实际在服务端验证)
    verifySignature(params = {}) {
        const { sign, timestamp, nonce, ...restParams } = params

        // 1. 检查时间戳是否过期
        if (Date.now() - timestamp > this.signatureExpiry) {
            return {
                valid: false,
                reason: 'Signature expired',
            }
        }

        // 2. 重新计算签名
        const calculatedSign = this.generateSignature(
            restParams,
            timestamp,
            nonce
        )

        // 3. 对比签名
        if (calculatedSign !== sign) {
            return {
                valid: false,
                reason: 'Invalid signature',
            }
        }

        return {
            valid: true,
        }
    }

    // 生成随机数
    generateNonce(length = 16) {
        const chars =
            'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'
        let nonce = ''
        for (let i = 0; i < length; i++) {
            nonce += chars.charAt(Math.floor(Math.random() * chars.length))
        }
        return nonce
    }

    // 生成请求ID
    generateRequestId() {
        return `${Date.now()}_${this.generateNonce(8)}`
    }

    // Axios拦截器配置
    setupAxiosInterceptor(axios) {
        axios.interceptors.request.use((config) => {
            // 对于GET请求,签名params
            if (config.method === 'get') {
                config.params = this.signRequest(config.params || {})
            }

            // 对于POST请求,签名body
            if (config.method === 'post' && config.data) {
                const signedData = this.signRequest(config.data)
                config.data = signedData
            }

            return config
        })
    }
}

export default new APISignature()

2.2 接口签名演示组件

SignatureDemo.vue

vue
<script setup>
import { ref, computed } from 'vue'
import { APISignature } from './api-signature.js'

const apiSignature = new APISignature({
    appKey: 'demo-app-123',
    appSecret: 'demo-secret-key-456',
})

const requestParams = ref({
    user_id: '10001',
    action: 'get_user_info',
    page: '1',
})

const signedParams = ref(null)
const verificationResult = ref(null)

// 生成签名
const generateSignature = () => {
    signedParams.value = apiSignature.signRequest(requestParams.value)
}

// 验证签名
const verifySignature = () => {
    if (!signedParams.value) return
    verificationResult.value = apiSignature.verifySignature(signedParams.value)
}

// 模拟篡改参数
const tamperParams = () => {
    if (!signedParams.value) return
    signedParams.value.user_id = '99999'
    verifySignature()
}

// 签名过程展示
const signatureProcess = computed(() => {
    if (!signedParams.value) return []

    const { sign, timestamp, nonce, app_key, ...params } = signedParams.value

    return [
        {
            step: 1,
            title: '添加公共参数',
            content: JSON.stringify(
                { ...params, app_key, timestamp, nonce },
                null,
                2
            ),
        },
        {
            step: 2,
            title: '参数排序',
            content: Object.keys({ ...params, app_key, timestamp, nonce })
                .sort()
                .join(', '),
        },
        {
            step: 3,
            title: '拼接参数字符串',
            content: Object.keys({ ...params, app_key, timestamp, nonce })
                .sort()
                .map((key) => `${key}=${signedParams.value[key]}`)
                .join('&'),
        },
        {
            step: 4,
            title: '添加密钥并计算签名',
            content: `HMAC-SHA256(参数字符串 + &key=密钥, 密钥)`,
        },
        {
            step: 5,
            title: '最终签名',
            content: sign,
        },
    ]
})

// 添加参数
const newKey = ref('')
const newValue = ref('')

const addParam = () => {
    if (newKey.value && newValue.value) {
        requestParams.value[newKey.value] = newValue.value
        newKey.value = ''
        newValue.value = ''
    }
}

// 删除参数
const removeParam = (key) => {
    delete requestParams.value[key]
}
</script>

<template>
    <div class="signature-demo">
        <div class="demo-header">
            <h2>接口签名算法演示</h2>
            <p class="subtitle">演示HMAC-SHA256签名过程</p>
        </div>

        <!-- 配置信息 -->
        <div class="config-section">
            <h3>配置信息</h3>
            <div class="config-grid">
                <div class="config-item">
                    <label>App Key:</label>
                    <code>demo-app-123</code>
                </div>
                <div class="config-item">
                    <label>App Secret:</label>
                    <code>demo-secret-key-456</code>
                </div>
                <div class="config-item">
                    <label>签名算法:</label>
                    <code>HMAC-SHA256</code>
                </div>
                <div class="config-item">
                    <label>有效期:</label>
                    <code>5分钟</code>
                </div>
            </div>
        </div>

        <!-- 请求参数 -->
        <div class="params-section">
            <h3>请求参数</h3>

            <div class="add-param">
                <input v-model="newKey" type="text" placeholder="参数名" />
                <input v-model="newValue" type="text" placeholder="参数值" />
                <button @click="addParam" class="add-btn">添加</button>
            </div>

            <div class="params-list">
                <div
                    v-for="(value, key) in requestParams"
                    :key="key"
                    class="param-item"
                >
                    <span class="param-key">{{ key }}</span>
                    <span class="param-value">{{ value }}</span>
                    <button @click="removeParam(key)" class="remove-btn">
                        删除
                    </button>
                </div>
            </div>

            <button @click="generateSignature" class="generate-btn">
                生成签名
            </button>
        </div>

        <!-- 签名过程 -->
        <div v-if="signedParams" class="process-section">
            <h3>签名生成过程</h3>
            <div class="process-steps">
                <div
                    v-for="step in signatureProcess"
                    :key="step.step"
                    class="step-item"
                >
                    <div class="step-number">步骤{{ step.step }}</div>
                    <div class="step-title">{{ step.title }}</div>
                    <pre class="step-content">{{ step.content }}</pre>
                </div>
            </div>
        </div>

        <!-- 签名结果 -->
        <div v-if="signedParams" class="result-section">
            <h3>签名结果</h3>
            <div class="result-box">
                <pre>{{ JSON.stringify(signedParams, null, 2) }}</pre>
            </div>

            <div class="action-buttons">
                <button @click="verifySignature" class="verify-btn">
                    验证签名
                </button>
                <button @click="tamperParams" class="tamper-btn">
                    模拟篡改
                </button>
            </div>
        </div>

        <!-- 验证结果 -->
        <div v-if="verificationResult" class="verification-section">
            <h3>验证结果</h3>
            <div
                class="verification-box"
                :class="{
                    valid: verificationResult.valid,
                    invalid: !verificationResult.valid,
                }"
            >
                <div class="verification-status">
                    <span class="status-icon">
                        {{ verificationResult.valid ? '✅' : '❌' }}
                    </span>
                    <span class="status-text">
                        {{
                            verificationResult.valid
                                ? '签名验证通过'
                                : '签名验证失败'
                        }}
                    </span>
                </div>
                <div
                    v-if="!verificationResult.valid"
                    class="verification-reason"
                >
                    原因: {{ verificationResult.reason }}
                </div>
            </div>
        </div>

        <!-- 使用说明 -->
        <div class="usage-section">
            <h3>接口签名使用说明</h3>

            <div class="usage-card">
                <h4>前端实现</h4>
                <pre><code>// 初始化签名工具
const apiSignature = new APISignature({
  appKey: 'your-app-key',
  appSecret: 'your-app-secret'
})

// 配置Axios拦截器
apiSignature.setupAxiosInterceptor(axios)

// 或手动签名请求
const signedParams = apiSignature.signRequest({
  user_id: '123',
  action: 'get_info'
})</code></pre>
            </div>

            <div class="usage-card">
                <h4>服务端验证 (Node.js)</h4>
                <pre><code>const crypto = require('crypto')

function verifySignature(params, appSecret) {
  const { sign, ...restParams } = params

  // 参数排序
  const sortedKeys = Object.keys(restParams).sort()
  const paramString = sortedKeys
    .map(key => `${key}=${restParams[key]}`)
    .join('&')

  // 计算签名
  const signString = `${paramString}&key=${appSecret}`
  const calculatedSign = crypto
    .createHmac('sha256', appSecret)
    .update(signString)
    .digest('hex')

  // 验证
  return calculatedSign === sign
}</code></pre>
            </div>
        </div>

        <!-- 最佳实践 -->
        <div class="best-practices-section">
            <h3>签名安全最佳实践</h3>
            <ul class="practices-list">
                <li>密钥(appSecret)永远不要暴露在前端代码中</li>
                <li>使用HTTPS传输,防止中间人攻击</li>
                <li>添加时间戳,防止重放攻击</li>
                <li>使用随机数(nonce),增加签名唯一性</li>
                <li>参数排序保证签名一致性</li>
                <li>服务端严格验证签名和时间戳</li>
                <li>记录签名失败日志,监控异常请求</li>
                <li>定期轮换密钥,降低泄露风险</li>
            </ul>
        </div>
    </div>
</template>

<style scoped>
.signature-demo {
    padding: 20px;
    background: #f5f7fa;
    min-height: 100vh;
}

.demo-header {
    text-align: center;
    margin-bottom: 40px;
}

.demo-header h2 {
    margin: 0 0 10px 0;
    font-size: 32px;
    color: #303133;
}

.subtitle {
    margin: 0;
    font-size: 16px;
    color: #909399;
}

/* 各个section */
.config-section,
.params-section,
.process-section,
.result-section,
.verification-section,
.usage-section,
.best-practices-section {
    margin-bottom: 30px;
    padding: 24px;
    background: white;
    border-radius: 8px;
}

h3 {
    margin: 0 0 20px 0;
    font-size: 18px;
    color: #303133;
}

/* 配置信息 */
.config-grid {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(250px, 1fr));
    gap: 16px;
}

.config-item {
    padding: 16px;
    background: #f5f7fa;
    border-radius: 8px;
}

.config-item label {
    display: block;
    font-size: 14px;
    color: #606266;
    margin-bottom: 8px;
    font-weight: 600;
}

.config-item code {
    display: block;
    padding: 8px;
    background: white;
    border-radius: 4px;
    font-size: 13px;
    color: #409eff;
}

/* 参数设置 */
.add-param {
    display: flex;
    gap: 12px;
    margin-bottom: 20px;
}

.add-param input {
    flex: 1;
    padding: 10px;
    border: 1px solid #dcdfe6;
    border-radius: 4px;
    font-size: 14px;
}

.add-btn {
    padding: 10px 24px;
    background: #409eff;
    color: white;
    border: none;
    border-radius: 4px;
    cursor: pointer;
    font-size: 14px;
    font-weight: 600;
}

.params-list {
    margin-bottom: 20px;
}

.param-item {
    display: flex;
    align-items: center;
    gap: 12px;
    padding: 12px;
    background: #f5f7fa;
    border-radius: 4px;
    margin-bottom: 8px;
}

.param-key {
    font-weight: 600;
    color: #303133;
    min-width: 120px;
}

.param-value {
    flex: 1;
    color: #606266;
}

.remove-btn {
    padding: 6px 12px;
    background: #f56c6c;
    color: white;
    border: none;
    border-radius: 4px;
    cursor: pointer;
    font-size: 13px;
}

.generate-btn {
    padding: 12px 32px;
    background: #67c23a;
    color: white;
    border: none;
    border-radius: 4px;
    cursor: pointer;
    font-size: 14px;
    font-weight: 600;
}

/* 签名过程 */
.process-steps {
    display: flex;
    flex-direction: column;
    gap: 16px;
}

.step-item {
    padding: 20px;
    background: #f5f7fa;
    border-radius: 8px;
    border-left: 4px solid #409eff;
}

.step-number {
    display: inline-block;
    padding: 4px 12px;
    background: #409eff;
    color: white;
    border-radius: 12px;
    font-size: 12px;
    font-weight: 700;
    margin-bottom: 8px;
}

.step-title {
    font-size: 16px;
    font-weight: 600;
    color: #303133;
    margin-bottom: 12px;
}

.step-content {
    margin: 0;
    padding: 12px;
    background: white;
    border-radius: 4px;
    font-size: 13px;
    color: #606266;
    overflow-x: auto;
    font-family: 'Courier New', monospace;
}

/* 结果显示 */
.result-box {
    padding: 16px;
    background: #282c34;
    border-radius: 8px;
    margin-bottom: 20px;
    overflow-x: auto;
}

.result-box pre {
    margin: 0;
    color: #abb2bf;
    font-size: 13px;
    line-height: 1.6;
    font-family: 'Courier New', monospace;
}

.action-buttons {
    display: flex;
    gap: 12px;
}

.verify-btn,
.tamper-btn {
    padding: 10px 24px;
    border: none;
    border-radius: 4px;
    cursor: pointer;
    font-size: 14px;
    font-weight: 600;
}

.verify-btn {
    background: #409eff;
    color: white;
}

.tamper-btn {
    background: #e6a23c;
    color: white;
}

/* 验证结果 */
.verification-box {
    padding: 24px;
    border-radius: 8px;
}

.verification-box.valid {
    background: #f0f9ff;
    border: 2px solid #67c23a;
}

.verification-box.invalid {
    background: #fef0f0;
    border: 2px solid #f56c6c;
}

.verification-status {
    display: flex;
    align-items: center;
    gap: 12px;
}

.status-icon {
    font-size: 32px;
}

.status-text {
    font-size: 18px;
    font-weight: 600;
    color: #303133;
}

.verification-reason {
    margin-top: 12px;
    padding: 12px;
    background: rgba(245, 108, 108, 0.1);
    border-radius: 4px;
    color: #f56c6c;
    font-size: 14px;
}

/* 使用说明 */
.usage-card {
    margin-bottom: 24px;
    padding: 20px;
    background: #f5f7fa;
    border-radius: 8px;
}

.usage-card h4 {
    margin: 0 0 12px 0;
    font-size: 16px;
    color: #303133;
}

.usage-card pre {
    margin: 0;
    padding: 16px;
    background: #282c34;
    border-radius: 4px;
    overflow-x: auto;
}

.usage-card code {
    font-size: 13px;
    line-height: 1.6;
    color: #abb2bf;
    font-family: 'Courier New', monospace;
}

/* 最佳实践 */
.practices-list {
    margin: 0;
    padding-left: 24px;
}

.practices-list li {
    font-size: 14px;
    line-height: 2;
    color: #606266;
}
</style>

三、前端代码混淆

3.1 代码混淆配置

webpack.obfuscate.config.js

javascript
const JavaScriptObfuscator = require('webpack-obfuscator')

module.exports = {
    // Webpack配置
    plugins: [
        new JavaScriptObfuscator(
            {
                // 压缩代码
                compact: true,

                // 控制流扁平化
                controlFlowFlattening: true,
                controlFlowFlatteningThreshold: 0.75,

                // 死代码注入
                deadCodeInjection: true,
                deadCodeInjectionThreshold: 0.4,

                // 调试保护
                debugProtection: true,
                debugProtectionInterval: 2000,

                // 禁用控制台输出
                disableConsoleOutput: true,

                // 标识符重命名
                identifierNamesGenerator: 'hexadecimal',

                // 日志输出
                log: false,

                // 数字转换为表达式
                numbersToExpressions: true,

                // 重命名全局变量
                renameGlobals: false,

                // 自我防御
                selfDefending: true,

                // 简化字符串数组
                simplify: true,

                // 字符串数组
                stringArray: true,
                stringArrayCallsTransform: true,
                stringArrayEncoding: ['base64'],
                stringArrayIndexShift: true,
                stringArrayRotate: true,
                stringArrayShuffle: true,
                stringArrayWrappersCount: 2,
                stringArrayWrappersChainedCalls: true,
                stringArrayWrappersParametersMaxCount: 4,
                stringArrayWrappersType: 'function',
                stringArrayThreshold: 0.75,

                // Unicode转义
                unicodeEscapeSequence: false,

                // 目标环境
                target: 'browser',
            },
            []
        ),
    ],
}

3.2 混淆前后对比

原始代码

javascript
function calculateTotal(items) {
    let total = 0

    for (let item of items) {
        if (item.price > 0) {
            total += item.price * item.quantity
        }
    }

    return total
}

const API_KEY = 'sk-1234567890abcdef'

function fetchUserData(userId) {
    return fetch(`/api/users/${userId}`, {
        headers: {
            'X-API-Key': API_KEY,
        },
    }).then((res) => res.json())
}
混淆后代码
javascript
var _0x4a2b = ['price', 'quantity', 'length', 'X-API-Key', '/api/users/']
;(function (_0x1f8d3e, _0x4a2b7c) {
    var _0x5c8e91 = function (_0x3d7b88) {
        while (--_0x3d7b88) {
            _0x1f8d3e['push'](_0x1f8d3e['shift']())
        }
    }
    _0x5c8e91(++_0x4a2b7c)
})(_0x4a2b, 0x1f4)
var _0x5c8e = function (_0x1f8d3e, _0x4a2b7c) {
    _0x1f8d3e = _0x1f8d3e - 0x0
    var _0x5c8e91 = _0x4a2b[_0x1f8d3e]
    return _0x5c8e91
}
function _0x3d7b(_0x1f8d3e) {
    var _0x4a2b7c = 0x0
    for (
        var _0x5c8e91 = 0x0;
        _0x5c8e91 < _0x1f8d3e[_0x5c8e('0x2')];
        _0x5c8e91++
    ) {
        if (_0x1f8d3e[_0x5c8e91][_0x5c8e('0x0')] > 0x0) {
            _0x4a2b7c +=
                _0x1f8d3e[_0x5c8e91][_0x5c8e('0x0')] *
                _0x1f8d3e[_0x5c8e91][_0x5c8e('0x1')]
        }
    }
    return _0x4a2b7c
}
const _0x2f1a8e = 'sk-1234567890abcdef'
function _0x8c3f(_0x1f8d3e) {
    return fetch(_0x5c8e('0x4') + _0x1f8d3e, {
        headers: { [_0x5c8e('0x3')]: _0x2f1a8e },
    })['then']((_0x4a2b7c) => _0x4a2b7c['json']())
}

四、敏感数据加密

4.1 前端加密管理器

encryption-manager.js

javascript
import CryptoJS from 'crypto-js'
import JSEncrypt from 'jsencrypt'

export class EncryptionManager {
    constructor() {
        // AES密钥(实际项目中应该从服务端获取)
        this.aesKey = CryptoJS.lib.WordArray.random(128 / 8).toString()

        // RSA公钥(用于加密AES密钥)
        this.rsaPublicKey = `-----BEGIN PUBLIC KEY-----
MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQC...
-----END PUBLIC KEY-----`
    }

    // AES加密
    encryptAES(plaintext, key = this.aesKey) {
        const encrypted = CryptoJS.AES.encrypt(plaintext, key)
        return encrypted.toString()
    }

    // AES解密
    decryptAES(ciphertext, key = this.aesKey) {
        const decrypted = CryptoJS.AES.decrypt(ciphertext, key)
        return decrypted.toString(CryptoJS.enc.Utf8)
    }

    // RSA加密
    encryptRSA(plaintext) {
        const encrypt = new JSEncrypt()
        encrypt.setPublicKey(this.rsaPublicKey)
        return encrypt.encrypt(plaintext)
    }

    // 混合加密(RSA+AES)
    hybridEncrypt(data) {
        // 1. 生成随机AES密钥
        const aesKey = CryptoJS.lib.WordArray.random(128 / 8).toString()

        // 2. 用AES加密数据
        const encryptedData = this.encryptAES(JSON.stringify(data), aesKey)

        // 3. 用RSA加密AES密钥
        const encryptedKey = this.encryptRSA(aesKey)

        return {
            data: encryptedData,
            key: encryptedKey,
        }
    }

    // 加密localStorage存储
    setEncryptedStorage(key, value) {
        const encrypted = this.encryptAES(JSON.stringify(value))
        localStorage.setItem(key, encrypted)
    }

    // 解密localStorage读取
    getEncryptedStorage(key) {
        const encrypted = localStorage.getItem(key)
        if (!encrypted) return null

        try {
            const decrypted = this.decryptAES(encrypted)
            return JSON.parse(decrypted)
        } catch (e) {
            console.error('Decrypt storage failed:', e)
            return null
        }
    }

    // 加密表单数据
    encryptFormData(formData) {
        const encrypted = {}

        for (const [key, value] of Object.entries(formData)) {
            // 敏感字段加密
            if (this.isSensitiveField(key)) {
                encrypted[key] = this.encryptAES(value)
            } else {
                encrypted[key] = value
            }
        }

        return encrypted
    }

    // 判断是否为敏感字段
    isSensitiveField(fieldName) {
        const sensitiveFields = [
            'password',
            'card_no',
            'id_card',
            'phone',
            'email',
            'bank_account',
        ]

        return sensitiveFields.some((field) =>
            fieldName.toLowerCase().includes(field)
        )
    }

    // 密码加密(加盐哈希)
    hashPassword(password, salt) {
        if (!salt) {
            salt = CryptoJS.lib.WordArray.random(128 / 8).toString()
        }

        const hash = CryptoJS.PBKDF2(password, salt, {
            keySize: 256 / 32,
            iterations: 10000,
        }).toString()

        return {
            hash: hash,
            salt: salt,
        }
    }
}

export default new EncryptionManager()

4.2 加密演示组件

EncryptionDemo.vue

vue
<script setup>
import { ref } from 'vue'
import encryptionManager from './encryption-manager.js'

const plaintext = ref('Hello, World!')
const aesEncrypted = ref('')
const aesDecrypted = ref('')

const formData = ref({
    username: 'john_doe',
    password: '123456',
    email: 'john@example.com',
    card_no: '6222021234567890',
})

const encryptedForm = ref(null)

// AES加密
const encryptAES = () => {
    aesEncrypted.value = encryptionManager.encryptAES(plaintext.value)
}

// AES解密
const decryptAES = () => {
    aesDecrypted.value = encryptionManager.decryptAES(aesEncrypted.value)
}

// 加密表单
const encryptForm = () => {
    encryptedForm.value = encryptionManager.encryptFormData(formData.value)
}

// 测试存储加密
const testStorageEncryption = () => {
    const testData = {
        userId: '12345',
        token: 'secret-token-abc123',
        settings: { theme: 'dark' },
    }

    encryptionManager.setEncryptedStorage('user_data', testData)
    const retrieved = encryptionManager.getEncryptedStorage('user_data')

    alert(JSON.stringify(retrieved, null, 2))
}
</script>

<template>
    <div class="encryption-demo">
        <h2>数据加密演示</h2>

        <!-- AES加密 -->
        <section class="demo-section">
            <h3>AES对称加密</h3>
            <div class="input-group">
                <input v-model="plaintext" placeholder="输入明文" />
                <button @click="encryptAES">加密</button>
            </div>

            <div v-if="aesEncrypted" class="result">
                <label>加密结果:</label>
                <code>{{ aesEncrypted }}</code>
                <button @click="decryptAES">解密</button>
            </div>

            <div v-if="aesDecrypted" class="result">
                <label>解密结果:</label>
                <span>{{ aesDecrypted }}</span>
            </div>
        </section>

        <!-- 表单加密 -->
        <section class="demo-section">
            <h3>表单数据加密</h3>
            <div class="form-data">
                <div
                    v-for="(value, key) in formData"
                    :key="key"
                    class="form-item"
                >
                    <label>{{ key }}:</label>
                    <input v-model="formData[key]" />
                </div>
            </div>

            <button @click="encryptForm" class="encrypt-btn">
                加密敏感字段
            </button>

            <div v-if="encryptedForm" class="encrypted-result">
                <pre>{{ JSON.stringify(encryptedForm, null, 2) }}</pre>
            </div>
        </section>

        <!-- 存储加密 -->
        <section class="demo-section">
            <h3>LocalStorage加密存储</h3>
            <button @click="testStorageEncryption" class="test-btn">
                测试加密存储
            </button>
            <p class="hint">
                点击后会在localStorage中加密存储数据,然后读取并解密
            </p>
        </section>
    </div>
</template>

<style scoped>
.encryption-demo {
    max-width: 1200px;
    margin: 0 auto;
    padding: 20px;
}

.demo-section {
    margin-bottom: 40px;
    padding: 24px;
    background: white;
    border-radius: 8px;
    box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
}

.input-group {
    display: flex;
    gap: 12px;
    margin-bottom: 16px;
}

.input-group input {
    flex: 1;
    padding: 10px;
    border: 1px solid #ddd;
    border-radius: 4px;
}

button {
    padding: 10px 20px;
    background: #409eff;
    color: white;
    border: none;
    border-radius: 4px;
    cursor: pointer;
}

.result {
    margin-top: 16px;
    padding: 16px;
    background: #f5f7fa;
    border-radius: 4px;
}

code {
    display: block;
    padding: 8px;
    background: #282c34;
    color: #abb2bf;
    border-radius: 4px;
    overflow-x: auto;
    margin: 8px 0;
}
</style>

五、数字水印防截图

5.1 水印管理器

watermark-manager.js

javascript
export class WatermarkManager {
    constructor(options = {}) {
        this.text = options.text || 'Confidential'
        this.fontSize = options.fontSize || 14
        this.color = options.color || 'rgba(0, 0, 0, 0.1)'
        this.rotate = options.rotate || -20
        this.zIndex = options.zIndex || 9999

        this.watermarkDiv = null
        this.observer = null
    }

    // 创建水印
    create() {
        // 创建canvas生成水印图片
        const canvas = document.createElement('canvas')
        const ctx = canvas.getContext('2d')

        canvas.width = 300
        canvas.height = 200

        ctx.font = `${this.fontSize}px Arial`
        ctx.fillStyle = this.color
        ctx.rotate((this.rotate * Math.PI) / 180)
        ctx.fillText(this.text, 50, 100)

        // 将canvas转为base64
        const base64 = canvas.toDataURL()

        // 创建水印div
        this.watermarkDiv = document.createElement('div')
        this.watermarkDiv.id = 'watermark-layer'
        this.watermarkDiv.style.cssText = `
      position: fixed;
      top: 0;
      left: 0;
      width: 100%;
      height: 100%;
      pointer-events: none;
      z-index: ${this.zIndex};
      background-image: url(${base64});
      background-repeat: repeat;
    `

        document.body.appendChild(this.watermarkDiv)

        // 防止水印被删除
        this.protectWatermark()
    }

    // 创建明水印
    createVisibleWatermark(text, options = {}) {
        const div = document.createElement('div')
        div.className = 'visible-watermark'
        div.textContent = text
        div.style.cssText = `
      position: fixed;
      bottom: ${options.bottom || 20}px;
      right: ${options.right || 20}px;
      padding: 8px 16px;
      background: rgba(0, 0, 0, 0.7);
      color: white;
      border-radius: 4px;
      font-size: 12px;
      z-index: ${this.zIndex};
      pointer-events: none;
    `

        document.body.appendChild(div)
        return div
    }

    // 创建暗水印(Canvas指纹)
    createCanvasFingerprint(userId) {
        const canvas = document.createElement('canvas')
        const ctx = canvas.getContext('2d')

        canvas.width = 200
        canvas.height = 50

        // 绘制文本
        ctx.font = '14px Arial'
        ctx.fillStyle = 'rgba(0, 0, 0, 0.05)' // 几乎不可见
        ctx.fillText(`ID:${userId}`, 10, 30)

        return canvas.toDataURL()
    }

    // 防止水印被删除(使用MutationObserver)
    protectWatermark() {
        this.observer = new MutationObserver((mutations) => {
            mutations.forEach((mutation) => {
                // 检查水印div是否被删除
                if (mutation.type === 'childList') {
                    const watermark = document.getElementById('watermark-layer')
                    if (!watermark) {
                        console.warn('Watermark removed, recreating...')
                        this.create()
                    }
                }

                // 检查水印样式是否被修改
                if (mutation.type === 'attributes') {
                    if (mutation.target.id === 'watermark-layer') {
                        console.warn('Watermark modified, recreating...')
                        this.remove()
                        this.create()
                    }
                }
            })
        })

        this.observer.observe(document.body, {
            childList: true,
            attributes: true,
            subtree: true,
        })
    }

    // 移除水印
    remove() {
        if (this.watermarkDiv) {
            this.watermarkDiv.remove()
            this.watermarkDiv = null
        }

        if (this.observer) {
            this.observer.disconnect()
            this.observer = null
        }
    }

    // 更新水印内容
    update(newText) {
        this.text = newText
        this.remove()
        this.create()
    }

    // 防止截图(检测截图快捷键)
    preventScreenshot() {
        document.addEventListener('keydown', (e) => {
            // Windows: PrtSc, Alt+PrtSc, Win+Shift+S
            // Mac: Cmd+Shift+3/4/5
            if (
                e.key === 'PrintScreen' ||
                (e.metaKey && e.shiftKey && ['3', '4', '5'].includes(e.key)) ||
                (e.metaKey && e.shiftKey && e.key === 's')
            ) {
                e.preventDefault()
                alert('截图功能已被禁用')

                // 记录截图尝试
                this.logScreenshotAttempt()
            }
        })

        // 检测失焦(可能切换到截图工具)
        document.addEventListener('visibilitychange', () => {
            if (document.hidden) {
                console.warn('Page hidden, possible screenshot attempt')
            }
        })
    }

    // 记录截图尝试
    logScreenshotAttempt() {
        const log = {
            type: 'screenshot_attempt',
            timestamp: new Date().toISOString(),
            userAgent: navigator.userAgent,
            url: window.location.href,
        }

        // 发送到服务器
        fetch('/api/security/log', {
            method: 'POST',
            headers: { 'Content-Type': 'application/json' },
            body: JSON.stringify(log),
        })
    }
}

export default new WatermarkManager()

5.2 水印演示组件

WatermarkDemo.vue

vue
<script setup>
import { ref, onMounted, onUnmounted } from 'vue'
import { WatermarkManager } from './watermark-manager.js'

const watermarkManager = ref(null)
const watermarkText = ref('Confidential Document')
const showWatermark = ref(false)
const watermarkStyle = ref({
    fontSize: 16,
    color: 'rgba(0, 0, 0, 0.1)',
    rotate: -20,
})

const userId = ref('USER_12345')

// 创建水印
const createWatermark = () => {
    if (watermarkManager.value) {
        watermarkManager.value.remove()
    }

    watermarkManager.value = new WatermarkManager({
        text: watermarkText.value,
        fontSize: watermarkStyle.value.fontSize,
        color: watermarkStyle.value.color,
        rotate: watermarkStyle.value.rotate,
    })

    watermarkManager.value.create()
    showWatermark.value = true
}

// 移除水印
const removeWatermark = () => {
    if (watermarkManager.value) {
        watermarkManager.value.remove()
        showWatermark.value = false
    }
}

// 启用防截图
const enableScreenshotProtection = () => {
    if (watermarkManager.value) {
        watermarkManager.value.preventScreenshot()
        alert('已启用防截图保护,按PrintScreen或截图快捷键将被拦截')
    }
}

// 尝试删除水印(演示防护)
const tryRemoveWatermark = () => {
    const watermarkDiv = document.getElementById('watermark-layer')
    if (watermarkDiv) {
        watermarkDiv.remove()
        setTimeout(() => {
            alert('水印被删除后会自动恢复')
        }, 1000)
    }
}

onMounted(() => {
    createWatermark()
})

onUnmounted(() => {
    removeWatermark()
})
</script>

<template>
    <div class="watermark-demo">
        <div class="demo-header">
            <h2>数字水印防截图演示</h2>
            <p class="subtitle">演示水印创建和防护机制</p>
        </div>

        <!-- 水印配置 -->
        <div class="config-section">
            <h3>水印配置</h3>

            <div class="config-form">
                <div class="form-group">
                    <label>水印文本:</label>
                    <input v-model="watermarkText" type="text" />
                </div>

                <div class="form-group">
                    <label>字体大小:</label>
                    <input
                        v-model.number="watermarkStyle.fontSize"
                        type="range"
                        min="10"
                        max="30"
                    />
                    <span>{{ watermarkStyle.fontSize }}px</span>
                </div>

                <div class="form-group">
                    <label>旋转角度:</label>
                    <input
                        v-model.number="watermarkStyle.rotate"
                        type="range"
                        min="-45"
                        max="0"
                    />
                    <span>{{ watermarkStyle.rotate }}°</span>
                </div>

                <div class="form-group">
                    <label>用户ID (暗水印):</label>
                    <input v-model="userId" type="text" />
                </div>
            </div>

            <div class="action-buttons">
                <button @click="createWatermark" class="create-btn">
                    {{ showWatermark ? '更新水印' : '创建水印' }}
                </button>
                <button
                    @click="removeWatermark"
                    class="remove-btn"
                    v-if="showWatermark"
                >
                    移除水印
                </button>
            </div>
        </div>

        <!-- 防护功能 -->
        <div class="protection-section">
            <h3>防护功能</h3>

            <div class="protection-grid">
                <div class="protection-card">
                    <h4>防删除保护</h4>
                    <p>使用MutationObserver监听DOM变化,水印被删除后自动恢复</p>
                    <button @click="tryRemoveWatermark" class="test-btn">
                        测试删除水印
                    </button>
                </div>

                <div class="protection-card">
                    <h4>防截图保护</h4>
                    <p>拦截截图快捷键,记录截图尝试</p>
                    <button
                        @click="enableScreenshotProtection"
                        class="test-btn"
                    >
                        启用防截图
                    </button>
                </div>

                <div class="protection-card">
                    <h4>用户追踪</h4>
                    <p>在水印中嵌入用户ID,泄露后可追溯</p>
                    <div class="user-info">
                        当前用户: <code>{{ userId }}</code>
                    </div>
                </div>
            </div>
        </div>

        <!-- 内容区域(演示水印效果) -->
        <div class="content-section">
            <h3>机密文档内容</h3>
            <div class="document-content">
                <h4>公司机密文档</h4>
                <p>
                    这是一份机密文档的内容。页面上的水印可以防止未授权的截图和复制。
                </p>
                <p>
                    水印包含文档标识和用户ID,如果内容泄露可以追溯到具体用户。
                </p>
                <p>请注意保护文档安全,不要将内容分享给未授权人员。</p>

                <div class="data-table">
                    <table>
                        <thead>
                            <tr>
                                <th>项目</th>
                                <th>数据</th>
                                <th>状态</th>
                            </tr>
                        </thead>
                        <tbody>
                            <tr>
                                <td>季度营收</td>
                                <td>¥5,000,000</td>
                                <td>机密</td>
                            </tr>
                            <tr>
                                <td>客户数据</td>
                                <td>10,000+</td>
                                <td>机密</td>
                            </tr>
                            <tr>
                                <td>市场份额</td>
                                <td>25%</td>
                                <td>机密</td>
                            </tr>
                        </tbody>
                    </table>
                </div>
            </div>
        </div>

        <!-- 技术说明 -->
        <div class="tech-doc-section">
            <h3>技术实现说明</h3>

            <div class="tech-card">
                <h4>1. Canvas水印生成</h4>
                <pre><code>const canvas = document.createElement('canvas')
const ctx = canvas.getContext('2d')

ctx.font = '14px Arial'
ctx.fillStyle = 'rgba(0, 0, 0, 0.1)'
ctx.rotate(-20 * Math.PI / 180)
ctx.fillText('Confidential', 50, 100)

const base64 = canvas.toDataURL()</code></pre>
            </div>

            <div class="tech-card">
                <h4>2. MutationObserver防护</h4>
                <pre><code>const observer = new MutationObserver((mutations) => {
  const watermark = document.getElementById('watermark')
  if (!watermark) {
    // 水印被删除,重新创建
    createWatermark()
  }
})

observer.observe(document.body, {
  childList: true,
  attributes: true,
  subtree: true
})</code></pre>
            </div>

            <div class="tech-card">
                <h4>3. 截图检测</h4>
                <pre><code>document.addEventListener('keydown', (e) => {
  if (e.key === 'PrintScreen') {
    e.preventDefault()
    // 记录截图尝试
    logScreenshotAttempt()
  }
})</code></pre>
            </div>
        </div>

        <!-- 最佳实践 -->
        <div class="best-practices-section">
            <h3>水印使用最佳实践</h3>
            <ul class="practices-list">
                <li>水印透明度要适中,既要不影响阅读,又要足够清晰</li>
                <li>在水印中嵌入用户ID和时间戳,便于追溯</li>
                <li>使用MutationObserver防止水印被删除</li>
                <li>结合Canvas指纹技术实现暗水印</li>
                <li>监听截图快捷键,记录截图行为</li>
                <li>对于高度机密内容,可以禁用右键和选择</li>
                <li>定期审计水印日志,发现异常行为</li>
                <li>水印要覆盖整个页面,避免裁剪后去除</li>
            </ul>
        </div>
    </div>
</template>

<style scoped>
.watermark-demo {
    padding: 20px;
    background: #f5f7fa;
    min-height: 100vh;
}

.demo-header {
    text-align: center;
    margin-bottom: 40px;
}

.demo-header h2 {
    margin: 0 0 10px 0;
    font-size: 32px;
    color: #303133;
}

.subtitle {
    margin: 0;
    font-size: 16px;
    color: #909399;
}

/* 各个section */
.config-section,
.protection-section,
.content-section,
.tech-doc-section,
.best-practices-section {
    margin-bottom: 30px;
    padding: 24px;
    background: white;
    border-radius: 8px;
}

h3 {
    margin: 0 0 20px 0;
    font-size: 18px;
    color: #303133;
}

/* 配置表单 */
.config-form {
    margin-bottom: 20px;
}

.form-group {
    display: flex;
    align-items: center;
    gap: 12px;
    margin-bottom: 16px;
}

.form-group label {
    min-width: 120px;
    font-size: 14px;
    color: #606266;
    font-weight: 600;
}

.form-group input[type='text'] {
    flex: 1;
    padding: 8px 12px;
    border: 1px solid #dcdfe6;
    border-radius: 4px;
}

.form-group input[type='range'] {
    flex: 1;
}

.action-buttons {
    display: flex;
    gap: 12px;
}

.create-btn,
.remove-btn {
    padding: 10px 24px;
    border: none;
    border-radius: 4px;
    cursor: pointer;
    font-size: 14px;
    font-weight: 600;
}

.create-btn {
    background: #67c23a;
    color: white;
}

.remove-btn {
    background: #f56c6c;
    color: white;
}

/* 防护功能 */
.protection-grid {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
    gap: 20px;
}

.protection-card {
    padding: 20px;
    background: #f5f7fa;
    border-radius: 8px;
    border-left: 4px solid #409eff;
}

.protection-card h4 {
    margin: 0 0 12px 0;
    font-size: 16px;
    color: #303133;
}

.protection-card p {
    margin: 0 0 16px 0;
    font-size: 14px;
    color: #606266;
    line-height: 1.6;
}

.test-btn {
    padding: 8px 16px;
    background: #409eff;
    color: white;
    border: none;
    border-radius: 4px;
    cursor: pointer;
    font-size: 13px;
}

.user-info {
    font-size: 14px;
    color: #606266;
}

.user-info code {
    padding: 4px 8px;
    background: white;
    border-radius: 4px;
    color: #409eff;
    font-family: 'Courier New', monospace;
}

/* 内容区域 */
.document-content {
    padding: 24px;
    background: #fafafa;
    border-radius: 8px;
    border: 1px solid #e4e7ed;
}

.document-content h4 {
    margin: 0 0 16px 0;
    font-size: 20px;
    color: #303133;
}

.document-content p {
    margin: 0 0 16px 0;
    font-size: 14px;
    line-height: 1.8;
    color: #606266;
}

.data-table {
    margin-top: 24px;
    overflow-x: auto;
}

.data-table table {
    width: 100%;
    border-collapse: collapse;
}

.data-table th,
.data-table td {
    padding: 12px;
    text-align: left;
    border: 1px solid #e4e7ed;
}

.data-table th {
    background: #f5f7fa;
    font-weight: 600;
    color: #303133;
}

.data-table td {
    color: #606266;
}

/* 技术说明 */
.tech-card {
    margin-bottom: 24px;
    padding: 20px;
    background: #f5f7fa;
    border-radius: 8px;
}

.tech-card h4 {
    margin: 0 0 12px 0;
    font-size: 16px;
    color: #303133;
}

.tech-card pre {
    margin: 0;
    padding: 16px;
    background: #282c34;
    border-radius: 4px;
    overflow-x: auto;
}

.tech-card code {
    font-size: 13px;
    line-height: 1.6;
    color: #abb2bf;
    font-family: 'Courier New', monospace;
}

/* 最佳实践 */
.practices-list {
    margin: 0;
    padding-left: 24px;
}

.practices-list li {
    font-size: 14px;
    line-height: 2;
    color: #606266;
}
</style>

六、简历描述模板

前端加密与安全防护 (2024.08 - 至今)

负责公司前端数据加密和安全防护体系建设,实现接口签名、代码混淆、数据加密和数字水印等安全方案。

核心职责

  • 实现HMAC-SHA256接口签名算法,防止接口被恶意调用
  • 配置JavaScript代码混淆,保护核心业务逻辑
  • 开发数据加密模块,实现AES/RSA混合加密
  • 实施数字水印方案,防止敏感内容截图泄露
  • 建立前端安全防护机制,包括防调试、反爬虫
技术实现
  • 使用CryptoJS实现HMAC-SHA256签名,添加时间戳和nonce防重放
  • 通过javascript-obfuscator进行代码混淆,配置控制流扁平化
  • 实现AES对称加密和RSA非对称加密的混合加密方案
  • 基于Canvas API生成数字水印,使用MutationObserver防删除
  • 配置Webpack打包时自动混淆和压缩代码
项目成果
  • 接口签名实施后,恶意请求拦截率100%
  • 代码混淆后,逆向分析难度提升80%
  • 敏感数据加密存储,通过安全审计
  • 数字水印覆盖核心页面,有效防止内容泄露
  • 整体安全防护能力提升,未发生安全事故

七、SOP标准回答

面试问题: 如何实现接口签名防止被恶意调用?

标准回答

"接口签名是防止接口被恶意调用的重要手段。我实现的方案基于HMAC-SHA256算法。

首先,客户端和服务端约定好一个密钥appSecret,这个密钥只保存在服务端,前端不能暴露。前端只存储appKey用于标识应用。

签名流程分五步。第一步,添加公共参数,包括appKey、时间戳和随机数nonce。时间戳用于防止重放攻击,nonce保证签名唯一性。

第二步,参数排序。把所有参数按key的字母顺序排序,保证客户端和服务端计算签名时参数顺序一致。

第三步,拼接参数字符串。格式是key1=value1&key2=value2,然后加上&key=密钥。

第四步,计算签名。用HMAC-SHA256算法,把参数字符串和密钥一起计算哈希值。

第五步,把签名添加到请求参数中,一起发送给服务端。

服务端验证时,先检查时间戳是否在有效期内,比如5分钟。然后用相同的方法重新计算签名,对比客户端传来的签名是否一致。如果不一致说明参数被篡改,拒绝请求。

为了方便使用,我封装了Axios拦截器,自动给每个请求添加签名。开发人员不需要关心签名细节,直接调用接口就行。

这个方案的核心是密钥不能泄露。实际项目中,appSecret存在服务端环境变量里,前端根本接触不到。即使有人抓包拿到签名,也无法伪造新的请求,因为每次请求的时间戳和nonce都不同。

实施后,我们拦截了所有未签名的恶意请求,接口安全性大幅提升。"

面试问题: 前端代码混淆有什么用?如何实现?

标准回答

"代码混淆的主要目的是保护核心业务逻辑,提高逆向分析的难度。

前端代码部署到生产环境后,任何人都能通过浏览器开发者工具查看源码。如果不做混淆,核心算法、关键逻辑、甚至某些密钥都可能被竞争对手轻易获取。混淆可以让代码变得难以理解,增加破解成本。

我用的混淆工具是javascript-obfuscator,它有很多混淆策略。

第一是标识符重命名。把变量名、函数名都替换成_0x1a2b这种16进制字符串,让代码失去可读性。

第二是控制流扁平化。把if-else、switch等控制流改成状态机,通过一个大的switch-case循环执行,破坏代码的结构。

第三是字符串加密。把所有字符串收集到一个数组中,用base64编码,运行时再解码。这样直接搜索字符串找不到关键逻辑。

第四是死代码注入。插入一些永远不会执行的代码,干扰分析。

第五是自我防御。在代码中插入检测,如果发现被调试或被格式化,就停止运行。

第六是禁用console。把所有console.log、console.debug删除或替换,防止调试时输出信息。

在Webpack配置中,我添加了javascript-obfuscator插件,在生产环境构建时自动混淆。开发环境不混淆,方便调试。

需要注意的是,混淆不是加密,只是提高破解难度,不能完全防止逆向。真正敏感的逻辑应该放在服务端。但对于必须在前端实现的算法,比如数据可视化、图表计算等,混淆能起到一定保护作用。

混淆后,代码体积会增加20-30%,运行性能可能略有下降,需要权衡。对于核心模块强混淆,非核心模块轻混淆。"


八、难点与亮点分析

难点1: 如何在前端实现安全的密钥管理?

问题场景: 前端加密需要密钥,但密钥不能暴露在代码中。

解决方案

javascript
class SecureKeyManager {
    constructor() {
        this.sessionKey = null
    }

    // 从服务端获取临时会话密钥
    async fetchSessionKey() {
        // 1. 客户端生成密钥对
        const keyPair = await this.generateKeyPair()

        // 2. 将公钥发送给服务端
        const response = await fetch('/api/key/exchange', {
            method: 'POST',
            body: JSON.stringify({
                publicKey: keyPair.publicKey,
            }),
        })

        const data = await response.json()

        // 3. 服务端用客户端公钥加密AES密钥返回
        // 4. 客户端用私钥解密得到AES密钥
        this.sessionKey = await this.decryptWithPrivateKey(
            data.encryptedKey,
            keyPair.privateKey
        )

        return this.sessionKey
    }

    // 使用Web Crypto API生成密钥对
    async generateKeyPair() {
        const keyPair = await crypto.subtle.generateKey(
            {
                name: 'RSA-OAEP',
                modulusLength: 2048,
                publicExponent: new Uint8Array([1, 0, 1]),
                hash: 'SHA-256',
            },
            true,
            ['encrypt', 'decrypt']
        )

        const publicKey = await crypto.subtle.exportKey(
            'spki',
            keyPair.publicKey
        )

        return {
            publicKey: this.arrayBufferToBase64(publicKey),
            privateKey: keyPair.privateKey,
        }
    }

    // 用私钥解密
    async decryptWithPrivateKey(encryptedData, privateKey) {
        const decrypted = await crypto.subtle.decrypt(
            { name: 'RSA-OAEP' },
            privateKey,
            this.base64ToArrayBuffer(encryptedData)
        )

        return new TextDecoder().decode(decrypted)
    }

    // 辅助方法
    arrayBufferToBase64(buffer) {
        const bytes = new Uint8Array(buffer)
        let binary = ''
        for (let i = 0; i < bytes.byteLength; i++) {
            binary += String.fromCharCode(bytes[i])
        }
        return btoa(binary)
    }

    base64ToArrayBuffer(base64) {
        const binary = atob(base64)
        const bytes = new Uint8Array(binary.length)
        for (let i = 0; i < binary.length; i++) {
            bytes[i] = binary.charCodeAt(i)
        }
        return bytes.buffer
    }
}
关键点
  • 密钥不存在前端代码中
  • 每次会话生成新的临时密钥
  • 使用非对称加密传输对称密钥
  • 私钥只存在内存,不持久化

难点2: 如何实现真正有效的防调试?

问题场景: 普通的debugger语句很容易被跳过。

解决方案

javascript
class AntiDebug {
    constructor() {
        this.isDebuggerPresent = false
    }

    // 方法1: 无限debugger
    infiniteDebugger() {
        setInterval(() => {
            debugger
        }, 100)
    }

    // 方法2: 检测控制台
    detectDevTools() {
        const threshold = 160
        const devtools = /./

        devtools.toString = function () {
            this.isOpen = true
        }

        setInterval(() => {
            const before = new Date()
            // 触发toString
            console.log(devtools)
            const after = new Date()

            // 如果打开了控制台,console.log会有延迟
            if (after - before > threshold) {
                this.onDebugDetected()
            }
        }, 1000)
    }

    // 方法3: 检测窗口尺寸变化
    detectWindowResize() {
        let lastWidth = window.outerWidth
        let lastHeight = window.outerHeight

        setInterval(() => {
            // 开发者工具会改变窗口尺寸
            if (
                window.outerWidth !== lastWidth ||
                window.outerHeight !== lastHeight
            ) {
                this.onDebugDetected()
            }

            lastWidth = window.outerWidth
            lastHeight = window.outerHeight
        }, 500)
    }

    // 方法4: 检测函数toString
    protectFunction(fn) {
        const originalToString = Function.prototype.toString

        Function.prototype.toString = function () {
            if (this === fn) {
                this.onDebugDetected()
                return 'function () { [native code] }'
            }
            return originalToString.call(this)
        }
    }

    // 检测到调试时的处理
    onDebugDetected() {
        // 方案1: 跳转到其他页面
        window.location.href = 'about:blank'

        // 方案2: 清空页面内容
        // document.body.innerHTML = ''

        // 方案3: 执行无限循环
        // while(true) {}
    }

    // 启动所有防护
    enable() {
        this.detectDevTools()
        this.detectWindowResize()

        // 在关键函数上添加保护
        const criticalFunctions = [window.fetch, XMLHttpRequest.prototype.open]

        criticalFunctions.forEach((fn) => {
            this.protectFunction(fn)
        })
    }
}

亮点: 智能水印系统

创新点

  • 根据用户权限动态调整水印
  • 水印包含加密的用户信息
  • 支持明暗水印结合
javascript
class SmartWatermark {
    constructor() {
        this.config = this.loadConfig()
    }

    // 根据用户权限配置水印
    loadConfig() {
        const userLevel = this.getUserLevel()

        switch (userLevel) {
            case 'guest':
                return {
                    visible: true,
                    text: '访客模式 - 仅供预览',
                    opacity: 0.3,
                    preventScreenshot: true,
                }
            case 'normal':
                return {
                    visible: true,
                    text: `${this.getUserName()} - 内部资料`,
                    opacity: 0.15,
                    preventScreenshot: true,
                }
            case 'vip':
                return {
                    visible: false,
                    darkWatermark: true,
                    preventScreenshot: false,
                }
            default:
                return {
                    visible: true,
                    text: '未授权访问',
                    opacity: 0.5,
                    preventScreenshot: true,
                }
        }
    }

    // 创建智能水印
    create() {
        if (this.config.visible) {
            this.createVisibleWatermark()
        }

        if (this.config.darkWatermark) {
            this.createDarkWatermark()
        }

        if (this.config.preventScreenshot) {
            this.preventScreenshot()
        }
    }

    // 创建暗水印(用户不可见,但截图会显示)
    createDarkWatermark() {
        const userId = this.getUserId()
        const timestamp = Date.now()

        // 加密用户信息
        const encoded = btoa(`${userId}:${timestamp}`)

        // 在页面插入不可见div
        const div = document.createElement('div')
        div.style.cssText = `
      position: fixed;
      top: 0;
      left: 0;
      width: 1px;
      height: 1px;
      opacity: 0;
      pointer-events: none;
    `
        div.setAttribute('data-user-trace', encoded)
        document.body.appendChild(div)
    }

    getUserLevel() {
        // 从token或后端获取用户等级
        return 'normal'
    }

    getUserName() {
        return 'John Doe'
    }

    getUserId() {
        return 'USER_12345'
    }
}