一、技术实现方案
1.1 前端安全架构
前端安全防护体系
├── XSS防御层
│ ├── 输入过滤
│ ├── 输出转义
│ ├── CSP策略
│ └── HttpOnly Cookie
│
├── CSRF防御层
│ ├── Token验证
│ ├── SameSite Cookie
│ ├── Referer检查
│ └── 双重Cookie验证
│
├── 点击劫持防护
│ ├── X-Frame-Options
│ ├── Frame Busting
│ └── CSP frame-ancestors
│
├── 注入防护
│ ├── 参数化查询
│ ├── 输入验证
│ └── 白名单过滤
│
└── 数据加密层
├── 传输加密(HTTPS)
├── 存储加密
├── 敏感字段脱敏
└── 密钥管理
五、SQL注入防护
5.1 前端输入验证
sql-injection-defense.js
export class SQLInjectionDefense {
constructor() {
this.sqlKeywords = [
'SELECT', 'INSERT', 'UPDATE', 'DELETE', 'DROP', 'CREATE',
'ALTER', 'EXEC', 'EXECUTE', 'UNION', 'DECLARE', 'CAST'
]
this.dangerousPatterns = [
/(\b(SELECT|INSERT|UPDATE|DELETE|DROP|CREATE|ALTER|EXEC|EXECUTE|UNION|DECLARE)\b)/gi,
/(--|#|\/\*|\*\/)/g, // SQL注释
/(\bOR\b.*=.*)/gi, // OR 1=1
/(\bAND\b.*=.*)/gi, // AND 1=1
/(;)/g, // 分号
/('|")/g // 引号
]
}
// 检测SQL注入
detectSQLInjection(input) {
for (const pattern of this.dangerousPatterns) {
if (pattern.test(input)) {
return {
detected: true,
pattern: pattern.source,
input: input
}
}
}
return { detected: false }
}
// 过滤危险字符
sanitizeInput(input) {
// 移除SQL关键字
let sanitized = input
this.sqlKeywords.forEach(keyword => {
const regex = new RegExp(`\\b${keyword}\\b`, 'gi')
sanitized = sanitized.replace(regex, '')
})
// 移除特殊字符
sanitized = sanitized
.replace(/--/g, '')
.replace(/#/g, '')
.replace(/;/g, '')
.replace(/'/g, "''") // 转义单引号
.replace(/"/g, '""') // 转义双引号
return sanitized.trim()
}
// 参数化查询示例(前端不直接执行SQL,这里仅作演示)
buildParameterizedQuery(params) {
return {
query: 'SELECT * FROM users WHERE username = ? AND age > ?',
params: params
}
}
// 白名单验证
validateWithWhitelist(input, whitelist) {
return whitelist.includes(input)
}
// 类型验证
validateType(input, expectedType) {
switch (expectedType) {
case 'number':
return !isNaN(input) && isFinite(input)
case 'email':
return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(input)
case 'alphanumeric':
return /^[a-zA-Z0-9]+$/.test(input)
case 'string':
return typeof input === 'string' && input.length > 0
default:
return false
}
}
// 长度限制
validateLength(input, min, max) {
const length = input.length
return length >= min && length <= max
}
}
export default new SQLInjectionDefense()
六、敏感信息加密
6.1 加密工具类
crypto-utils.js
import CryptoJS from 'crypto-js'
export class CryptoUtils {
constructor() {
// 密钥应该从环境变量或配置中读取,不要硬编码
this.secretKey = process.env.VUE_APP_SECRET_KEY || 'default-secret-key'
}
// AES加密
encryptAES(text) {
return CryptoJS.AES.encrypt(text, this.secretKey).toString()
}
// AES解密
decryptAES(ciphertext) {
const bytes = CryptoJS.AES.decrypt(ciphertext, this.secretKey)
return bytes.toString(CryptoJS.enc.Utf8)
}
// Base64编码
encodeBase64(text) {
return CryptoJS.enc.Base64.stringify(CryptoJS.enc.Utf8.parse(text))
}
// Base64解码
decodeBase64(base64) {
return CryptoJS.enc.Base64.parse(base64).toString(CryptoJS.enc.Utf8)
}
// MD5哈希
hashMD5(text) {
return CryptoJS.MD5(text).toString()
}
// SHA256哈希
hashSHA256(text) {
return CryptoJS.SHA256(text).toString()
}
// 生成随机盐
generateSalt(length = 16) {
const array = new Uint8Array(length)
crypto.getRandomValues(array)
return Array.from(array, byte => byte.toString(16).padStart(2, '0')).join('')
}
// 加盐哈希
hashWithSalt(text, salt) {
return this.hashSHA256(text + salt)
}
// 手机号脱敏
maskPhone(phone) {
return phone.replace(/(\d{3})\d{4}(\d{4})/, '$1****$2')
}
// 身份证号脱敏
maskIdCard(idCard) {
return idCard.replace(/(\d{6})\d{8}(\d{4})/, '$1********$2')
}
// 邮箱脱敏
maskEmail(email) {
return email.replace(/(.{2}).*@/, '$1***@')
}
// 银行卡号脱敏
maskBankCard(cardNo) {
return cardNo.replace(/(\d{4})\d+(\d{4})/, '$1 **** **** $2')
}
// 密码强度检测
checkPasswordStrength(password) {
const checks = {
length: password.length >= 8,
lowercase: /[a-z]/.test(password),
uppercase: /[A-Z]/.test(password),
number: /[0-9]/.test(password),
special: /[!@#$%^&*(),.?":{}|<>]/.test(password)
}
const score = Object.values(checks).filter(Boolean).length
if (score <= 2) return { strength: 'weak', score }
if (score === 3) return { strength: 'medium', score }
if (score === 4) return { strength: 'strong', score }
return { strength: 'very-strong', score }
}
}
export default new CryptoUtils()
七、综合安全演示组件
SecurityDemo.vue
<script setup>
import { ref } from 'vue'
import xssDefense from './xss-defense.js'
import csrfDefense from './csrf-defense.js'
import sqlInjectionDefense from './sql-injection-defense.js'
import cryptoUtils from './crypto-utils.js'
// XSS测试
const xssInput = ref('')
const xssResult = ref(null)
const testXSS = () => {
xssResult.value = xssDefense.detectXss(xssInput.value)
}
// SQL注入测试
const sqlInput = ref('')
const sqlResult = ref(null)
const testSQLInjection = () => {
sqlResult.value = sqlInjectionDefense.detectSQLInjection(sqlInput.value)
}
// 数据加密测试
const plainText = ref('')
const encrypted = ref('')
const decrypted = ref('')
const encrypt = () => {
encrypted.value = cryptoUtils.encryptAES(plainText.value)
}
const decrypt = () => {
decrypted.value = cryptoUtils.decryptAES(encrypted.value)
}
// 敏感信息脱敏
const sensitiveData = ref({
phone: '13800138000',
idCard: '110101199001011234',
email: 'user@example.com',
bankCard: '6222021234567890123'
})
const maskedData = ref({})
const maskSensitiveInfo = () => {
maskedData.value = {
phone: cryptoUtils.maskPhone(sensitiveData.value.phone),
idCard: cryptoUtils.maskIdCard(sensitiveData.value.idCard),
email: cryptoUtils.maskEmail(sensitiveData.value.email),
bankCard: cryptoUtils.maskBankCard(sensitiveData.value.bankCard)
}
}
</script>
<template>
<div class="security-demo">
<h1>前端安全综合演示</h1>
<!-- XSS防御 -->
<section class="demo-section">
<h2>XSS攻击防御</h2>
<div class="input-group">
<input v-model="xssInput" placeholder="输入可能的XSS代码" />
<button @click="testXSS">检测XSS</button>
</div>
<div v-if="xssResult" class="result">
<div :class="['status', xssResult.detected ? 'danger' : 'safe']">
{{ xssResult.detected ? '检测到XSS攻击' : '安全' }}
</div>
</div>
</section>
<!-- SQL注入防护 -->
<section class="demo-section">
<h2>SQL注入防护</h2>
<div class="input-group">
<input v-model="sqlInput" placeholder="输入可能的SQL注入" />
<button @click="testSQLInjection">检测注入</button>
</div>
<div v-if="sqlResult" class="result">
<div :class="['status', sqlResult.detected ? 'danger' : 'safe']">
{{ sqlResult.detected ? '检测到SQL注入' : '安全' }}
</div>
</div>
</section>
<!-- 数据加密 -->
<section class="demo-section">
<h2>数据加密</h2>
<div class="input-group">
<input v-model="plainText" placeholder="输入要加密的文本" />
<button @click="encrypt">加密</button>
</div>
<div v-if="encrypted" class="result">
<div class="label">加密结果:</div>
<code>{{ encrypted }}</code>
<button @click="decrypt">解密</button>
</div>
<div v-if="decrypted" class="result">
<div class="label">解密结果:</div>
<span>{{ decrypted }}</span>
</div>
</section>
<!-- 敏感信息脱敏 -->
<section class="demo-section">
<h2>敏感信息脱敏</h2>
<button @click="maskSensitiveInfo">脱敏处理</button>
<div v-if="Object.keys(maskedData).length" class="mask-result">
<div class="mask-item">
<span class="label">手机号:</span>
<span class="original">{{ sensitiveData.phone }}</span>
<span class="arrow">→</span>
<span class="masked">{{ maskedData.phone }}</span>
</div>
<div class="mask-item">
<span class="label">身份证:</span>
<span class="original">{{ sensitiveData.idCard }}</span>
<span class="arrow">→</span>
<span class="masked">{{ maskedData.idCard }}</span>
</div>
<div class="mask-item">
<span class="label">邮箱:</span>
<span class="original">{{ sensitiveData.email }}</span>
<span class="arrow">→</span>
<span class="masked">{{ maskedData.email }}</span>
</div>
<div class="mask-item">
<span class="label">银行卡:</span>
<span class="original">{{ sensitiveData.bankCard }}</span>
<span class="arrow">→</span>
<span class="masked">{{ maskedData.bankCard }}</span>
</div>
</div>
</section>
</div>
</template>
<style scoped>
.security-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;
}
.status.danger {
color: #f56c6c;
font-weight: bold;
}
.status.safe {
color: #67c23a;
font-weight: bold;
}
.mask-result {
margin-top: 16px;
}
.mask-item {
padding: 12px;
background: #f5f7fa;
border-radius: 4px;
margin-bottom: 8px;
display: flex;
align-items: center;
gap: 12px;
}
.mask-item .label {
font-weight: 600;
min-width: 80px;
}
.mask-item .original {
color: #909399;
}
.mask-item .arrow {
color: #409eff;
}
.mask-item .masked {
color: #303133;
font-weight: 600;
}
</style>
八、简历描述模板
前端安全体系建设 (2023.08 - 2024.02)
负责公司前端安全防护体系搭建,实现XSS、CSRF、点击劫持等常见攻击的全面防御,覆盖15+核心业务系统。
核心职责
- 开发XSS防御工具库,实现HTML转义、内容清理和URL过滤
- 实现CSRF Token验证机制,采用双重Cookie方案
- 部署点击劫持防护,配置X-Frame-Options和CSP策略
- 建立SQL注入防护机制,实现输入验证和参数化查询
- 开发敏感信息加密模块,实现AES加密和数据脱敏
技术实现
- 使用DOMPurify实现HTML清理,支持自定义白名单
- 实现CSRF Token自动添加的Axios拦截器
- 配置Frame Busting防止页面被恶意嵌套
- 使用CryptoJS实现AES/SHA256加密
- 开发正则表达式规则引擎检测SQL注入
项目成果
- XSS攻击拦截率99.9%,有效防御500+次攻击尝试
- CSRF防护覆盖所有敏感接口,零安全事故
- 敏感数据加密存储,通过等保三级认证
- 建立安全编码规范,开发人员安全意识提升40%
九、SOP标准回答
面试问题: 如何防御XSS攻击?
标准回答
"XSS防御主要从三个方面入手。
第一是输入过滤。用户输入的数据要经过严格验证,使用白名单机制只允许安全的字符。比如富文本编辑器的内容,我用DOMPurify库清理,只保留b、i、strong这些安全标签,移除script、iframe等危险标签。对于URL,只允许http、https、mailto协议,javascript伪协议会被过滤掉。
第二是输出转义。数据输出到页面时要根据上下文选择合适的转义方式。输出到HTML内容用HTML转义,把<>转成<>。输出到HTML属性用属性转义,转义引号和特殊字符。输出到JavaScript字符串用JS转义,转义反斜杠和引号。Vue框架默认会对插值进行转义,但如果用v-html就要格外小心。
第三是使用CSP。配置Content-Security-Policy响应头,限制脚本来源。比如设置script-src 'self'只允许加载同源脚本,禁用eval和inline script。这样即使有XSS漏洞,攻击者注入的脚本也无法执行。
另外还要设置HttpOnly Cookie,防止Cookie被脚本读取。这样即使有XSS漏洞,攻击者也拿不到用户的session。
实际项目中我封装了XSS防御工具类,包括HTML转义、URL清理、属性转义等方法,开发人员直接调用就行。还在构建时集成了XSS检测工具,自动扫描代码中的危险用法。上线后通过安全测试,成功拦截了500多次XSS攻击尝试,防御率99.9%。"
面试问题: CSRF和XSS有什么区别?如何防御CSRF?
标准回答
"CSRF和XSS虽然都是前端常见攻击,但原理完全不同。
XSS是跨站脚本攻击,攻击者在网站注入恶意脚本,利用用户的权限执行操作。比如在评论框注入