前端安全实践指南 🔒
引言
前端安全是Web应用开发中的关键环节。本文将深入探讨前端安全实践,包括XSS防护、CSRF防御、内容安全策略等,帮助开发者构建更安全的前端应用。
安全概述
前端安全主要包括以下方面:
- XSS防护:跨站脚本攻击防御
- CSRF防御:跨站请求伪造防护
- CSP配置:内容安全策略
- 数据加密:敏感信息保护
- 访问控制:权限管理
安全工具实现
安全管理器
// 安全管理器类
class SecurityManager {
private static instance: SecurityManager;
private config: SecurityConfig;
private constructor() {
this.config = {
xssFilter: true,
csrfProtection: true,
contentSecurityPolicy: true,
httpsOnly: true,
maxTokenAge: 3600
};
this.initialize();
}
// 获取单例实例
static getInstance(): SecurityManager {
if (!SecurityManager.instance) {
SecurityManager.instance = new SecurityManager();
}
return SecurityManager.instance;
}
// 初始化安全管理器
private initialize(): void {
// 配置CSP
if (this.config.contentSecurityPolicy) {
this.setupCSP();
}
// 配置HTTPS
if (this.config.httpsOnly) {
this.enforceHTTPS();
}
// 初始化CSRF保护
if (this.config.csrfProtection) {
this.initializeCsrfProtection();
}
}
// 设置CSP
private setupCSP(): void {
const csp = [
"default-src 'self'",
"script-src 'self' 'unsafe-inline' 'unsafe-eval'",
"style-src 'self' 'unsafe-inline'",
"img-src 'self' data: https:",
"font-src 'self'",
"connect-src 'self'",
"media-src 'self'",
"object-src 'none'",
"frame-src 'self'",
"worker-src 'self' blob:"
].join('; ');
const meta = document.createElement('meta');
meta.httpEquiv = 'Content-Security-Policy';
meta.content = csp;
document.head.appendChild(meta);
}
// 强制HTTPS
private enforceHTTPS(): void {
if (window.location.protocol === 'http:' &&
window.location.hostname !== 'localhost') {
window.location.href = window.location.href.replace(
'http:', 'https:'
);
}
}
// 初始化CSRF保护
private initializeCsrfProtection(): void {
this.setupCsrfToken();
this.interceptXHR();
this.interceptFetch();
}
// 设置CSRF Token
private setupCsrfToken(): void {
const token = this.generateToken();
document.cookie = `XSRF-TOKEN=${token}; path=/; secure; samesite=strict`;
const meta = document.createElement('meta');
meta.name = 'csrf-token';
meta.content = token;
document.head.appendChild(meta);
}
// 拦截XHR请求
private interceptXHR(): void {
const originalOpen = XMLHttpRequest.prototype.open;
const originalSend = XMLHttpRequest.prototype.send;
const manager = this;
XMLHttpRequest.prototype.open = function(
method: string,
url: string,
...args: any[]
) {
this._method = method;
this._url = url;
originalOpen.apply(this, [method, url, ...args]);
};
XMLHttpRequest.prototype.send = function(data?: any) {
if (this._method?.toUpperCase() !== 'GET') {
const token = manager.getCsrfToken();
this.setRequestHeader('X-XSRF-TOKEN', token);
}
originalSend.call(this, data);
};
}
// 拦截Fetch请求
private interceptFetch(): void {
const originalFetch = window.fetch;
const manager = this;
window.fetch = function(
input: RequestInfo,
init?: RequestInit
): Promise<Response> {
if (init?.method && init.method.toUpperCase() !== 'GET') {
const token = manager.getCsrfToken();
init.headers = {
...init.headers,
'X-XSRF-TOKEN': token
};
}
return originalFetch.call(window, input, init);
};
}
// 生成Token
private generateToken(): string {
const array = new Uint8Array(32);
crypto.getRandomValues(array);
return Array.from(array, byte =>
byte.toString(16).padStart(2, '0')
).join('');
}
// 获取CSRF Token
private getCsrfToken(): string {
const meta = document.querySelector('meta[name="csrf-token"]');
return meta?.getAttribute('content') || '';
}
// XSS过滤
sanitizeHTML(html: string): string {
const div = document.createElement('div');
div.textContent = html;
return div.innerHTML;
}
// 验证输入
validateInput(
input: string,
pattern: RegExp | string
): boolean {
const regex = typeof pattern === 'string'
? new RegExp(pattern)
: pattern;
return regex.test(input);
}
// 加密数据
async encryptData(
data: string,
password: string
): Promise<string> {
const encoder = new TextEncoder();
const salt = crypto.getRandomValues(new Uint8Array(16));
const keyMaterial = await crypto.subtle.importKey(
'raw',
encoder.encode(password),
'PBKDF2',
false,
['deriveBits', 'deriveKey']
);
const key = await crypto.subtle.deriveKey(
{
name: 'PBKDF2',
salt,
iterations: 100000,
hash: 'SHA-256'
},
keyMaterial,
{
name: 'AES-GCM',
length: 256
},
false,
['encrypt']
);
const iv = crypto.getRandomValues(new Uint8Array(12));
const encryptedContent = await crypto.subtle.encrypt(
{
name: 'AES-GCM',
iv
},
key,
encoder.encode(data)
);
const encryptedArray = new Uint8Array(encryptedContent);
const resultArray = new Uint8Array(
salt.length + iv.length + encryptedArray.length
);
resultArray.set(salt, 0);
resultArray.set(iv, salt.length);
resultArray.set(encryptedArray, salt.length + iv.length);
return btoa(String.fromCharCode(...resultArray));
}
// 解密数据
async decryptData(
encryptedData: string,
password: string
): Promise<string> {
const decoder = new TextDecoder();
const encoder = new TextEncoder();
const data = Uint8Array.from(
atob(encryptedData),
c => c.charCodeAt(0)
);
const salt = data.slice(0, 16);
const iv = data.slice(16, 28);
const content = data.slice(28);
const keyMaterial = await crypto.subtle.importKey(
'raw',
encoder.encode(password),
'PBKDF2',
false,
['deriveBits', 'deriveKey']
);
const key = await crypto.subtle.deriveKey(
{
name: 'PBKDF2',
salt,
iterations: 100000,
hash: 'SHA-256'
},
keyMaterial,
{
name: 'AES-GCM',
length: 256
},
false,
['decrypt']
);
const decryptedContent = await crypto.subtle.decrypt(
{
name: 'AES-GCM',
iv
},
key,
content
);
return decoder.decode(decryptedContent);
}
// 生成安全的随机值
generateSecureRandom(length: number): string {
const array = new Uint8Array(length);
crypto.getRandomValues(array);
return Array.from(array, byte =>
byte.toString(16).padStart(2, '0')
).join('');
}
// 哈希密码
async hashPassword(
password: string,
salt?: string
): Promise<{ hash: string; salt: string }> {
const encoder = new TextEncoder();
salt = salt || this.generateSecureRandom(16);
const keyMaterial = await crypto.subtle.importKey(
'raw',
encoder.encode(password),
'PBKDF2',
false,
['deriveBits']
);
const bits = await crypto.subtle.deriveBits(
{
name: 'PBKDF2',
salt: encoder.encode(salt),
iterations: 100000,
hash: 'SHA-256'
},
keyMaterial,
256
);
const hash = Array.from(new Uint8Array(bits), byte =>
byte.toString(16).padStart(2, '0')
).join('');
return { hash, salt };
}
// 验证密码
async verifyPassword(
password: string,
hash: string,
salt: string
): Promise<boolean> {
const result = await this.hashPassword(password, salt);
return result.hash === hash;
}
}
// 接口定义
interface SecurityConfig {
xssFilter: boolean;
csrfProtection: boolean;
contentSecurityPolicy: boolean;
httpsOnly: boolean;
maxTokenAge: number;
}
// 使用示例
const security = SecurityManager.getInstance();
// XSS防护
const userInput = '<script>alert("xss")</script>';
const safeHtml = security.sanitizeHTML(userInput);
// 输入验证
const email = 'user@example.com';
const isValidEmail = security.validateInput(
email,
/^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/
);
// 数据加密
const sensitiveData = 'secret information';
security.encryptData(sensitiveData, 'password123')
.then(encrypted => {
console.log('Encrypted:', encrypted);
return security.decryptData(encrypted, 'password123');
})
.then(decrypted => {
console.log('Decrypted:', decrypted);
});
// 密码哈希
security.hashPassword('myPassword123')
.then(({ hash, salt }) => {
console.log('Hash:', hash);
console.log('Salt:', salt);
return security.verifyPassword('myPassword123', hash, salt);
})
.then(isValid => {
console.log('Password valid:', isValid);
});
安全HTTP客户端
// 安全HTTP客户端类
class SecureHttpClient {
private baseUrl: string;
private security: SecurityManager;
constructor(config: SecureHttpConfig) {
this.baseUrl = config.baseUrl || '';
this.security = SecurityManager.getInstance();
}
// 发送GET请求
async get<T>(
url: string,
config?: RequestConfig
): Promise<T> {
return this.request<T>({
method: 'GET',
url,
...config
});
}
// 发送POST请求
async post<T>(
url: string,
data?: any,
config?: RequestConfig
): Promise<T> {
return this.request<T>({
method: 'POST',
url,
data,
...config
});
}
// 发送PUT请求
async put<T>(
url: string,
data?: any,
config?: RequestConfig
): Promise<T> {
return this.request<T>({
method: 'PUT',
url,
data,
...config
});
}
// 发送DELETE请求
async delete<T>(
url: string,
config?: RequestConfig
): Promise<T> {
return this.request<T>({
method: 'DELETE',
url,
...config
});
}
// 发送请求
private async request<T>(config: RequestConfig): Promise<T> {
const url = this.baseUrl + config.url;
// 添加CSRF Token
const headers = {
'Content-Type': 'application/json',
...config.headers
};
if (config.method !== 'GET') {
const token = document.querySelector(
'meta[name="csrf-token"]'
)?.getAttribute('content');
if (token) {
headers['X-XSRF-TOKEN'] = token;
}
}
// 加密敏感数据
let data = config.data;
if (config.encrypt && data) {
data = await this.security.encryptData(
JSON.stringify(data),
config.encryptKey || ''
);
}
try {
const response = await fetch(url, {
method: config.method,
headers,
body: data ? JSON.stringify(data) : undefined,
credentials: 'same-origin'
});
if (!response.ok) {
throw new Error(response.statusText);
}
// 解密响应数据
let result = await response.json();
if (config.encrypt && result.encrypted) {
result = JSON.parse(
await this.security.decryptData(
result.data,
config.encryptKey || ''
)
);
}
return result;
} catch (error) {
console.error('Request failed:', error);
throw error;
}
}
}
// 接口定义
interface SecureHttpConfig {
baseUrl?: string;
}
interface RequestConfig {
method: string;
url: string;
data?: any;
headers?: Record<string, string>;
encrypt?: boolean;
encryptKey?: string;
}
// 使用示例
const http = new SecureHttpClient({
baseUrl: 'https://api.example.com'
});
// 发送普通请求
http.get('/users')
.then(users => {
console.log('Users:', users);
});
// 发送加密请求
http.post(
'/sensitive-data',
{ secret: 'value' },
{
encrypt: true,
encryptKey: 'secret-key-123'
}
).then(response => {
console.log('Response:', response);
});
最佳实践与建议
输入验证
- 客户端验证
- 服务端验证
- 特殊字符过滤
- 长度限制
数据加密
- 使用HTTPS
- 敏感数据加密
- 安全密钥管理
- 哈希算法选择
访问控制
- 身份认证
- 权限验证
- 会话管理
- 令牌验证
安全配置
- CSP策略
- Cookie设置
- 错误处理
- 日志记录
总结
前端安全需要考虑以下方面:
- XSS和CSRF防护
- 数据加密和传输
- 访问控制和认证
- 安全配置和监控
- 漏洞修复和更新
通过全面的安全实践,可以有效降低安全风险。
学习资源
- OWASP安全指南
- Web安全最佳实践
- 加密算法教程
- 安全测试工具
- 漏洞数据库
如果你觉得这篇文章有帮助,欢迎点赞收藏,也期待在评论区看到你的想法和建议!👇
终身学习,共同成长。
咱们下一期见
💻