# 健身房管理系统前端安全规范文档 > 文档编号: GYM-FE-SEC-001 > 版本: v1.0 > 日期: 2026-03-04 > 作者: 张翔 > 状态: 初稿 --- ## 文档修订历史 | 版本 | 日期 | 作者 | 修订内容 | | ---- | ---------- | ---- | -------- | | v1.0 | 2026-03-04 | 张翔 | 创建前端安全规范 | --- ## 参考文档 - 《健身房管理系统前端技术架构详细设计》 GYM-FE-ARCH-001 - OWASP Top 10 Web Application Security Risks - Content Security Policy (CSP) Level 3 - Web Content Accessibility Guidelines (WCAG) 2.1 --- ## 一、安全概述 ### 1.1 安全目标 - **数据安全**:保护用户敏感数据(手机号、身份证号、银行卡号等) - **交易安全**:确保支付和充值操作的安全性 - **身份安全**:防止未授权访问和身份冒用 - **隐私保护**:符合GDPR等隐私法规要求 - **合规性**:符合金融行业安全标准和监管要求 ### 1.2 安全原则 | 原则 | 描述 | 实施方式 | |------|------|----------| | **最小权限** | 只授予必要的权限 | 基于角色的访问控制(RBAC) | | **纵深防御** | 多层安全防护 | 输入验证、输出转义、加密传输 | | **默认安全** | 默认配置安全 | CSP策略、安全Headers | | **审计追踪** | 记录关键操作 | 操作日志、异常日志 | | **持续监控** | 实时安全监控 | 错误监控、性能监控 | ### 1.3 安全威胁 | 威胁类型 | 风险等级 | 防护措施 | |---------|---------|----------| | **XSS(跨站脚本攻击)** | 高 | 输入过滤、输出转义、CSP | | **CSRF(跨站请求伪造)** | 高 | Token验证、SameSite Cookie | | **点击劫持** | 中 | X-Frame-Options、CSP | | **中间人攻击** | 高 | HTTPS、HSTS | | **敏感信息泄露** | 高 | 数据加密、脱敏显示 | | **暴力破解** | 中 | 验证码、登录限制 | | **会话劫持** | 高 | 安全Cookie、会话超时 | --- ## 二、XSS防护 ### 2.1 输入验证 #### 2.1.1 白名单验证 ```typescript // utils/validator.ts export function sanitizeInput(input: string, allowedChars: RegExp): string { return input.replace(allowedChars, '') } export function validatePhoneNumber(phone: string): boolean { const phoneRegex = /^1[3-9]\d{9}$/ return phoneRegex.test(phone) } export function validateIdCard(idCard: string): boolean { const idCardRegex = /^[1-9]\d{5}(18|19|20)\d{2}(0[1-9]|1[0-2])(0[1-9]|[12]\d|3[01])\d{3}[\dXx]$/ return idCardRegex.test(idCard) } export function validateEmail(email: string): boolean { const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/ return emailRegex.test(email) } ``` #### 2.1.2 输入长度限制 ```typescript // utils/validator.ts export const MAX_INPUT_LENGTH = { name: 64, phone: 11, idCard: 18, address: 256, remark: 512 } export function validateLength(input: string, maxLength: number): boolean { return input.length <= maxLength } ``` ### 2.2 输出转义 #### 2.2.1 HTML转义 ```typescript // utils/sanitize.ts import DOMPurify from 'dompurify' export function sanitizeHtml(html: string): string { return DOMPurify.sanitize(html, { ALLOWED_TAGS: ['p', 'br', 'strong', 'em', 'u'], ALLOWED_ATTR: [] }) } export function escapeHtml(text: string): string { const map: Record = { '&': '&', '<': '<', '>': '>', '"': '"', "'": ''' } return text.replace(/[&<>"']/g, (m) => map[m]) } ``` #### 2.2.2 URL转义 ```typescript // utils/sanitize.ts export function sanitizeUrl(url: string): string { try { const parsed = new URL(url) if (!['http:', 'https:'].includes(parsed.protocol)) { return '#' } return url } catch { return '#' } } ``` ### 2.3 CSP策略 #### 2.3.1 基础CSP配置 ```html ``` #### 2.3.2 动态CSP配置 ```typescript // utils/csp.ts export function generateCSP(config: CSPConfig): string { const directives = [ `default-src ${config.defaultSrc.join(' ')}`, `script-src ${config.scriptSrc.join(' ')}`, `style-src ${config.styleSrc.join(' ')}`, `img-src ${config.imgSrc.join(' ')}`, `connect-src ${config.connectSrc.join(' ')}` ] return directives.join('; ') } // 使用 const csp = generateCSP({ defaultSrc: ["'self'"], scriptSrc: ["'self'", "https://cdn.jsdelivr.net"], styleSrc: ["'self'", "'unsafe-inline'"], imgSrc: ["'self'", "data:", "https:"], connectSrc: ["'self'", "https://api.example.com"] }) document.querySelector('meta[http-equiv="Content-Security-Policy"]') ?.setAttribute('content', csp) ``` --- ## 三、CSRF防护 ### 3.1 Token验证 #### 3.1.1 Token生成与存储 ```typescript // utils/csrf.ts export function generateCSRFToken(): string { const array = new Uint8Array(32) crypto.getRandomValues(array) return Array.from(array, (byte) => byte.toString(16).padStart(2, '0')).join('') } export function setCSRFToken(token: string): void { localStorage.setItem('csrf_token', token) } export function getCSRFToken(): string { return localStorage.getItem('csrf_token') || '' } ``` #### 3.1.2 Token注入请求 ```typescript // api/request.ts import { getCSRFToken } from '@/utils/csrf' instance.interceptors.request.use((config) => { const csrfToken = getCSRFToken() if (csrfToken) { config.headers['X-CSRF-Token'] = csrfToken } return config }) ``` ### 3.2 SameSite Cookie ```typescript // api/request.ts instance.interceptors.request.use((config) => { config.withCredentials = true return config }) ``` ### 3.3 双重Cookie提交 ```typescript // utils/csrf.ts export function setDoubleSubmitCookie(token: string): void { document.cookie = `csrf_token=${token}; path=/; SameSite=Strict; Secure` } export function getDoubleSubmitCookie(): string { const match = document.cookie.match(/csrf_token=([^;]+)/) return match ? match[1] : '' } ``` --- ## 四、数据安全 ### 4.1 数据加密 #### 4.1.1 AES加密 ```typescript // utils/crypto.ts import CryptoJS from 'crypto-js' const SECRET_KEY = import.meta.env.VITE_CRYPTO_SECRET_KEY export function encrypt(text: string): string { const key = CryptoJS.enc.Utf8.parse(SECRET_KEY) const iv = CryptoJS.lib.WordArray.random(16) const encrypted = CryptoJS.AES.encrypt(text, key, { iv: iv, mode: CryptoJS.mode.CBC, padding: CryptoJS.pad.Pkcs7 }) return iv.toString() + ':' + encrypted.toString() } export function decrypt(ciphertext: string): string { const key = CryptoJS.enc.Utf8.parse(SECRET_KEY) const [ivHex, encrypted] = ciphertext.split(':') const iv = CryptoJS.enc.Hex.parse(ivHex) const decrypted = CryptoJS.AES.decrypt(encrypted, key, { iv: iv, mode: CryptoJS.mode.CBC, padding: CryptoJS.pad.Pkcs7 }) return decrypted.toString(CryptoJS.enc.Utf8) } ``` #### 4.1.2 RSA加密(用于敏感数据) ```typescript // utils/crypto.ts import JSEncrypt from 'jsencrypt' const PUBLIC_KEY = import.meta.env.VITE_RSA_PUBLIC_KEY export function encryptWithRSA(text: string): string { const encrypt = new JSEncrypt() encrypt.setPublicKey(PUBLIC_KEY) return encrypt.encrypt(text) || '' } ``` ### 4.2 数据脱敏 #### 4.2.1 手机号脱敏 ```typescript // utils/mask.ts export function maskPhone(phone: string): string { if (!phone || phone.length !== 11) { return phone } return phone.substring(0, 3) + '****' + phone.substring(7) } ``` #### 4.2.2 身份证号脱敏 ```typescript // utils/mask.ts export function maskIdCard(idCard: string): string { if (!idCard || idCard.length !== 18) { return idCard } return idCard.substring(0, 6) + '********' + idCard.substring(14) } ``` #### 4.2.3 银行卡号脱敏 ```typescript // utils/mask.ts export function maskBankCard(bankCard: string): string { if (!bankCard || bankCard.length < 16) { return bankCard } return bankCard.substring(0, 4) + ' **** **** ' + bankCard.substring(bankCard.length - 4) } ``` ### 4.3 敏感信息存储 #### 4.3.1 安全存储 ```typescript // utils/storage.ts import { encrypt, decrypt } from './crypto' export const secureStorage = { setItem(key: string, value: any): void { const encrypted = encrypt(JSON.stringify(value)) localStorage.setItem(key, encrypted) }, getItem(key: string): T | null { const encrypted = localStorage.getItem(key) if (!encrypted) return null try { return JSON.parse(decrypt(encrypted)) as T } catch { return null } }, removeItem(key: string): void { localStorage.removeItem(key) }, clear(): void { localStorage.clear() } } ``` #### 4.3.2 会话存储 ```typescript // utils/storage.ts export const sessionStorage = { setItem(key: string, value: any): void { const encrypted = encrypt(JSON.stringify(value)) window.sessionStorage.setItem(key, encrypted) }, getItem(key: string): T | null { const encrypted = window.sessionStorage.getItem(key) if (!encrypted) return null try { return JSON.parse(decrypt(encrypted)) as T } catch { return null } }, removeItem(key: string): void { window.sessionStorage.removeItem(key) }, clear(): void { window.sessionStorage.clear() } } ``` --- ## 五、身份认证与授权 ### 5.1 认证安全 #### 5.1.1 密码安全 ```typescript // utils/password.ts export function validatePassword(password: string): { valid: boolean; message?: string } { if (password.length < 8) { return { valid: false, message: '密码长度至少8位' } } if (!/[A-Z]/.test(password)) { return { valid: false, message: '密码必须包含大写字母' } } if (!/[a-z]/.test(password)) { return { valid: false, message: '密码必须包含小写字母' } } if (!/[0-9]/.test(password)) { return { valid: false, message: '密码必须包含数字' } } if (!/[!@#$%^&*]/.test(password)) { return { valid: false, message: '密码必须包含特殊字符' } } return { valid: true } } ``` #### 5.1.2 Token管理 ```typescript // stores/auth.ts import { defineStore } from 'pinia' import { ref } from 'vue' export const useAuthStore = defineStore('auth', () => { const token = ref('') const refreshToken = ref('') const tokenExpireTime = ref(0) const setToken = (newToken: string, expireIn: number) => { token.value = newToken tokenExpireTime.value = Date.now() + expireIn * 1000 secureStorage.setItem('auth_token', newToken) secureStorage.setItem('token_expire_time', tokenExpireTime.value) } const setRefreshToken = (newRefreshToken: string) => { refreshToken.value = newRefreshToken secureStorage.setItem('refresh_token', newRefreshToken) } const isTokenExpired = (): boolean => { return Date.now() >= tokenExpireTime.value } const clearAuth = () => { token.value = '' refreshToken.value = '' tokenExpireTime.value = 0 secureStorage.removeItem('auth_token') secureStorage.removeItem('refresh_token') secureStorage.removeItem('token_expire_time') } return { token, refreshToken, tokenExpireTime, setToken, setRefreshToken, isTokenExpired, clearAuth } }) ``` #### 5.1.3 Token刷新 ```typescript // api/request.ts import { useAuthStore } from '@/stores/auth' instance.interceptors.request.use(async (config) => { const authStore = useAuthStore() if (authStore.isTokenExpired()) { try { const response = await instance.post('/auth/refresh', { refreshToken: authStore.refreshToken }) authStore.setToken(response.token, response.expireIn) authStore.setRefreshToken(response.refreshToken) config.headers.Authorization = `Bearer ${response.token}` } catch (error) { authStore.clearAuth() window.location.href = '/login' return Promise.reject(error) } } else { config.headers.Authorization = `Bearer ${authStore.token}` } return config }) ``` ### 5.2 授权安全 #### 5.2.1 权限验证 ```typescript // utils/permission.ts export interface Permission { resource: string action: string } export function hasPermission(userPermissions: string[], required: Permission): boolean { const permissionString = `${required.resource}:${required.action}` return userPermissions.includes(permissionString) } export function hasAnyPermission(userPermissions: string[], required: Permission[]): boolean { return required.some(p => hasPermission(userPermissions, p)) } export function hasAllPermissions(userPermissions: string[], required: Permission[]): boolean { return required.every(p => hasPermission(userPermissions, p)) } ``` #### 5.2.2 路由权限守卫 ```typescript // router/guards/permission.ts import { useAuthStore } from '@/stores/auth' import { usePermissionStore } from '@/stores/permission' router.beforeEach(async (to, from, next) => { const authStore = useAuthStore() const permissionStore = usePermissionStore() if (to.meta.requiresAuth && !authStore.token) { next('/login') return } if (to.meta.permission) { const required = to.meta.permission as Permission if (!hasPermission(permissionStore.permissions, required)) { next('/403') return } } next() }) ``` --- ## 六、安全Headers ### 6.1 基础安全Headers ```typescript // utils/headers.ts export const securityHeaders = { 'X-Content-Type-Options': 'nosniff', 'X-Frame-Options': 'DENY', 'X-XSS-Protection': '1; mode=block', 'Strict-Transport-Security': 'max-age=31536000; includeSubDomains', 'Referrer-Policy': 'strict-origin-when-cross-origin', 'Permissions-Policy': 'geolocation=(), microphone=(), camera=()' } ``` ### 6.2 动态设置Headers ```typescript // api/request.ts instance.interceptors.request.use((config) => { Object.assign(config.headers, securityHeaders) return config }) ``` --- ## 七、点击劫持防护 ### 7.1 X-Frame-Options ```html ``` ### 7.2 CSP frame-ancestors ```html ``` ### 7.3 JavaScript防护 ```typescript // utils/clickjacking.ts export function preventClickjacking(): void { if (window.self !== window.top) { window.top.location = window.self.location } } // 在应用初始化时调用 preventClickjacking() ``` --- ## 八、安全键盘 ### 8.1 安全键盘组件 ```vue ``` --- ## 九、安全日志与监控 ### 9.1 操作日志 ```typescript // utils/logger.ts interface LogEntry { timestamp: number level: 'info' | 'warn' | 'error' action: string userId?: number details?: any } export class SecurityLogger { private logs: LogEntry[] = [] log(action: string, details?: any, level: 'info' | 'warn' | 'error' = 'info') { const entry: LogEntry = { timestamp: Date.now(), level, action, userId: this.getUserId(), details } this.logs.push(entry) this.sendToServer(entry) } private getUserId(): number | undefined { const authStore = useAuthStore() return authStore.user?.id } private async sendToServer(entry: LogEntry) { try { await api.post('/security/log', entry) } catch (error) { console.error('Failed to send security log:', error) } } } export const securityLogger = new SecurityLogger() ``` ### 9.2 异常监控 ```typescript // utils/sentry.ts import * as Sentry from '@sentry/vue' export function setupSentry(app: App) { Sentry.init({ app, dsn: import.meta.env.VITE_SENTRY_DSN, environment: import.meta.env.MODE, tracesSampleRate: 1.0, beforeSend(event) { if (event.request) { delete event.request.cookies delete event.request.headers } return event } }) } // 全局错误处理 window.addEventListener('error', (event) => { securityLogger.log('window.error', { message: event.message, filename: event.filename, lineno: event.lineno }, 'error') }) window.addEventListener('unhandledrejection', (event) => { securityLogger.log('unhandledrejection', { reason: event.reason }, 'error') }) ``` --- ## 十、安全最佳实践 ### 10.1 开发阶段 1. **代码审查** - 所有代码必须经过安全审查 - 使用ESLint安全规则 - 使用npm audit检查依赖漏洞 2. **依赖管理** - 定期更新依赖包 - 使用npm audit fix修复漏洞 - 使用Snyk等工具监控依赖安全 3. **环境变量** - 敏感信息使用环境变量 - 不要将密钥提交到代码仓库 - 使用.env文件管理配置 ### 10.2 测试阶段 1. **安全测试** - 使用OWASP ZAP进行安全扫描 - 进行渗透测试 - 测试XSS、CSRF等漏洞 2. **代码扫描** - 使用SonarQube进行代码质量检查 - 使用ESLint进行代码规范检查 - 使用Prettier进行代码格式化 ### 10.3 部署阶段 1. **HTTPS强制** - 使用SSL证书 - 配置HSTS - 禁用HTTP访问 2. **安全配置** - 配置CSP策略 - 配置安全Headers - 配置防火墙规则 3. **监控告警** - 配置错误监控 - 配置性能监控 - 配置安全告警 --- ## 十一、合规性要求 ### 11.1 GDPR合规 1. **数据最小化** - 只收集必要的用户数据 - 提供数据删除功能 - 提供数据导出功能 2. **用户同意** - 明确告知数据使用目的 - 获取用户明确同意 - 提供撤回同意的选项 3. **数据保护** - 加密存储敏感数据 - 限制数据访问权限 - 定期进行安全审计 ### 11.2 无障碍合规 1. **WCAG 2.1 AA级标准** - 键盘导航支持 - 屏幕阅读器支持 - 颜色对比度符合标准 2. **ARIA标签** - 为交互元素添加ARIA标签 - 为动态内容添加ARIA标签 - 为表单元素添加ARIA标签 --- ## 十二、安全检查清单 ### 12.1 代码提交前检查 - [ ] 所有用户输入都经过验证和过滤 - [ ] 所有输出都经过转义 - [ ] 敏感数据都经过加密存储 - [ ] 敏感信息都经过脱敏显示 - [ ] 所有API请求都包含CSRF Token - [ ] 所有页面都配置了CSP策略 - [ ] 所有页面都配置了安全Headers - [ ] 所有密码都符合复杂度要求 - [ ] 所有权限都经过验证 - [ ] 所有操作都记录了日志 ### 12.2 部署前检查 - [ ] 所有依赖包都是最新版本 - [ ] 所有依赖包都没有已知漏洞 - [ ] 所有环境变量都正确配置 - [ ] 所有HTTPS证书都有效 - [ ] 所有监控和告警都正常工作 - [ ] 所有安全策略都正确配置 - [ ] 所有安全测试都通过 - [ ] 所有安全扫描都通过 - [ ] 所有安全文档都完整 - [ ] 所有安全培训都完成 --- ## 十三、总结 本文档详细描述了健身房管理系统前端的安全规范,包括: 1. **安全概述**:安全目标、安全原则、安全威胁 2. **XSS防护**:输入验证、输出转义、CSP策略 3. **CSRF防护**:Token验证、SameSite Cookie、双重Cookie提交 4. **数据安全**:数据加密、数据脱敏、敏感信息存储 5. **身份认证与授权**:认证安全、授权安全 6. **安全Headers**:基础安全Headers、动态设置Headers 7. **点击劫持防护**:X-Frame-Options、CSP frame-ancestors 8. **安全键盘**:安全键盘组件 9. **安全日志与监控**:操作日志、异常监控 10. **安全最佳实践**:开发阶段、测试阶段、部署阶段 11. **合规性要求**:GDPR合规、无障碍合规 12. **安全检查清单**:代码提交前检查、部署前检查 通过遵循本文档的安全规范,可以确保健身房管理系统前端的安全性,符合金融级安全标准和监管要求。