// Token存儲和安全處理
export class TokenManager {
static getToken() {
try {
// 優(yōu)先從sessionStorage獲取
let token = sessionStorage.getItem('auth_token');
if (!token) {
// 從localStorage獲?。ㄈ绻校? token = localStorage.getItem('auth_token');
}
return token;
} catch (error) {
console.error('獲取Token失敗:', error);
return null;
}
}
static setToken(token, rememberMe = false) {
try {
// 清除舊的token
this.clearToken();
if (rememberMe) {
localStorage.setItem('auth_token', token);
} else {
sessionStorage.setItem('auth_token', token);
}
} catch (error) {
console.error('設置Token失敗:', error);
}
}
static clearToken() {
try {
sessionStorage.removeItem('auth_token');
localStorage.removeItem('auth_token');
} catch (error) {
console.error('清除Token失敗:', error);
}
}
static validateToken(token) {
if (!token) return false;
try {
// 解析JWT token(如果使用JWT)
const payload = JSON.parse(atob(token.split('.')[1]));
const expirationTime = payload.exp * 1000; // 轉換為毫秒
// 檢查token是否過(guò)期
return Date.now() < expirationTime;
} catch (error) {
console.error('Token驗證失敗:', error);
return false;
}
}
static refreshToken() {
const token = this.getToken();
if (!token) return Promise.reject(new Error('無(wú)有效Token'));
return post('/api/auth/refresh', { token })
.then(response => {
this.setToken(response.newToken);
return response.newToken;
})
.catch(error => {
this.clearToken();
throw error;
});
}
}
// 認證攔截器
export const setupAuthInterceptor = () => {
// 請求攔截器 - 添加認證頭
requestInterceptors.use(
(config) => {
const token = TokenManager.getToken();
if (token && TokenManager.validateToken(token)) {
config.headers = {
...config.headers,
'Authorization': `Bearer ${token}`
};
}
return config;
},
(error) => Promise.reject(error)
);
// 響應攔截器 - 處理認證錯誤
responseInterceptors.use(
(response) => response,
async (error) => {
if (error.response?.status === 401) {
// Token過(guò)期,嘗試刷新
try {
const newToken = await TokenManager.refreshToken();
// 重試原始請求
const originalRequest = error.config;
originalRequest.headers.Authorization = `Bearer ${newToken}`;
return request(originalRequest);
} catch (refreshError) {
// 刷新失敗,跳轉到登錄頁(yè)
TokenManager.clearToken();
window.location.href = '/login';
return Promise.reject(refreshError);
}
}
return Promise.reject(error);
}
);
};
// 輸入驗證工具
export class InputValidator {
static validateEmail(email) {
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
return emailRegex.test(email);
}
static validatePhone(phone) {
const phoneRegex = /^1[3-9]\d{9}$/;
return phoneRegex.test(phone);
}
static validatePassword(password) {
// 密碼強度驗證
const minLength = 8;
const hasUpperCase = /[A-Z]/.test(password);
const hasLowerCase = /[a-z]/.test(password);
const hasNumbers = /\d/.test(password);
const hasSpecialChar = /[!@#$%^&*(),.?":{}|<>]/.test(password);
return password.length >= minLength &&
hasUpperCase &&
hasLowerCase &&
hasNumbers &&
hasSpecialChar;
}
static sanitizeInput(input) {
if (typeof input !== 'string') return input;
// 移除潛在的XSS攻擊代碼
return input
.replace(/</g, '<')
.replace(/>/g, '>')
.replace(/"/g, '"')
.replace(/'/g, ''')
.replace(/\//g, '/');
}
static validateFile(file, options = {}) {
const {
maxSize = 10 * 1024 * 1024, // 10MB
allowedTypes = ['image/jpeg', 'image/png', 'application/pdf'],
allowedExtensions = ['jpg', 'jpeg', 'png', 'pdf']
} = options;
// 檢查文件大小
if (file.size > maxSize) {
throw new Error(`文件大小不能超過(guò) ${maxSize / 1024 / 1024}MB`);
}
// 檢查文件類(lèi)型
if (!allowedTypes.includes(file.type)) {
throw new Error('不支持的文件類(lèi)型');
}
// 檢查文件擴展名
const extension = file.name.split('.').pop().toLowerCase();
if (!allowedExtensions.includes(extension)) {
throw new Error('不支持的文件格式');
}
return true;
}
}
// 簡(jiǎn)單的數據加密(注意:前端加密不是絕對安全的)
export class SimpleEncryption {
static encrypt(data, key) {
try {
// 使用Base64編碼進(jìn)行簡(jiǎn)單加密
const encoded = btoa(JSON.stringify(data));
return encoded;
} catch (error) {
console.error('加密失敗:', error);
return data;
}
}
static decrypt(encryptedData, key) {
try {
// 解密Base64編碼的數據
const decoded = atob(encryptedData);
return JSON.parse(decoded);
} catch (error) {
console.error('解密失敗:', error);
return encryptedData;
}
}
// 敏感數據存儲
static storeSensitiveData(key, data) {
try {
const encrypted = this.encrypt(data, 'sensitive-key');
sessionStorage.setItem(key, encrypted);
} catch (error) {
console.error('存儲敏感數據失敗:', error);
}
}
static getSensitiveData(key) {
try {
const encrypted = sessionStorage.getItem(key);
return encrypted ? this.decrypt(encrypted, 'sensitive-key') : null;
} catch (error) {
console.error('獲取敏感數據失敗:', error);
return null;
}
}
}
// 設置CSP(Content Security Policy)
export const setupCSP = () => {
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' https://api.example.com;
frame-ancestors 'none';
base-uri 'self';
form-action 'self';
`.replace(/\s+/g, ' ').trim();
// 通過(guò)meta標簽設置CSP
const meta = document.createElement('meta');
meta.httpEquiv = 'Content-Security-Policy';
meta.content = csp;
document.head.appendChild(meta);
};
// 安全的HTML渲染
export const safeHtml = (html) => {
// 使用DOMPurify或其他庫來(lái)凈化HTML
if (window.DOMPurify) {
return DOMPurify.sanitize(html);
}
// 簡(jiǎn)單的凈化實(shí)現
const div = document.createElement('div');
div.textContent = html;
return div.innerHTML;
};
// 安全的動(dòng)態(tài)內容插入
export const safeInsertHtml = (element, html) => {
element.innerHTML = safeHtml(html);
};
// CSRF Token管理
export class CSRFProtection {
static getCSRFToken() {
// 從cookie中獲取CSRF token
const name = 'XSRF-TOKEN=';
const decodedCookie = decodeURIComponent(document.cookie);
const ca = decodedCookie.split(';');
for (let i = 0; i < ca.length; i++) {
let c = ca[i];
while (c.charAt(0) === ' ') {
c = c.substring(1);
}
if (c.indexOf(name) === 0) {
return c.substring(name.length, c.length);
}
}
return '';
}
static setupCSRFProtection() {
// 為所有請求添加CSRF token
requestInterceptors.use(
(config) => {
const token = this.getCSRFToken();
if (token) {
config.headers['X-XSRF-TOKEN'] = token;
}
return config;
},
(error) => Promise.reject(error)
);
}
static validateCSRFToken(token) {
const storedToken = this.getCSRFToken();
return token === storedToken;
}
}
// 安全的會(huì )話(huà)管理
export class SessionManager {
static startSession(userData) {
try {
// 設置會(huì )話(huà)超時(shí)時(shí)間(30分鐘)
const sessionData = {
user: userData,
createdAt: Date.now(),
expiresIn: 30 * 60 * 1000 // 30分鐘
};
sessionStorage.setItem('user_session', JSON.stringify(sessionData));
// 設置自動(dòng)登出定時(shí)器
this.setupAutoLogout();
} catch (error) {
console.error('啟動(dòng)會(huì )話(huà)失敗:', error);
}
}
static getSession() {
try {
const sessionStr = sessionStorage.getItem('user_session');
if (!sessionStr) return null;
const session = JSON.parse(sessionStr);
// 檢查會(huì )話(huà)是否過(guò)期
if (Date.now() - session.createdAt > session.expiresIn) {
this.endSession();
return null;
}
return session;
} catch (error) {
console.error('獲取會(huì )話(huà)失敗:', error);
return null;
}
}
static endSession() {
try {
sessionStorage.removeItem('user_session');
TokenManager.clearToken();
// 清除所有相關(guān)數據
localStorage.removeItem('user_preferences');
// 重定向到登錄頁(yè)
window.location.href = '/login';
} catch (error) {
console.error('結束會(huì )話(huà)失敗:', error);
}
}
static setupAutoLogout() {
// 設置 inactivity 監聽(tīng)
let inactivityTimer;
const resetTimer = () => {
clearTimeout(inactivityTimer);
inactivityTimer = setTimeout(() => {
this.endSession();
}, 30 * 60 * 1000); // 30分鐘無(wú)操作自動(dòng)登出
};
// 監聽(tīng)用戶(hù)活動(dòng)
['mousedown', 'mousemove', 'keypress', 'scroll', 'touchstart'].forEach(event => {
document.addEventListener(event, resetTimer, true);
});
resetTimer();
}
static extendSession() {
const session = this.getSession();
if (session) {
session.createdAt = Date.now();
sessionStorage.setItem('user_session', JSON.stringify(session));
}
}
}
// 設置安全HTTP頭
export const setupSecurityHeaders = () => {
// 如果使用Service Worker或后端設置,這些應該在服務(wù)器端配置
// 這里提供前端可設置的安全頭示例
// 防止點(diǎn)擊劫持
if (window !== window.top) {
window.top.location.href = window.location.href;
}
// 設置Referrer Policy
const metaReferrer = document.createElement('meta');
metaReferrer.name = 'referrer';
metaReferrer.content = 'strict-origin-when-cross-origin';
document.head.appendChild(metaReferrer);
};
// HSTS設置(應在服務(wù)器端配置)
// Strict-Transport-Security: max-age=31536000; includeSubDomains
// 安全事件記錄
export class SecurityLogger {
static logSecurityEvent(eventType, details) {
const logEntry = {
timestamp: new Date().toISOString(),
eventType,
details,
userAgent: navigator.userAgent,
url: window.location.href
};
// 發(fā)送到安全日志服務(wù)
this.sendToSecurityService(logEntry);
// 本地記錄(開(kāi)發(fā)環(huán)境)
if (process.env.NODE_ENV === 'development') {
console.warn('安全事件:', logEntry);
}
}
static sendToSecurityService(logEntry) {
// 實(shí)現發(fā)送到安全監控服務(wù)的邏輯
// 注意:不要記錄敏感信息
post('/api/security/log', logEntry)
.catch(error => console.error('安全日志發(fā)送失敗:', error));
}
// 記錄常見(jiàn)安全事件
static logAuthenticationEvent(action, success, additionalInfo = {}) {
this.logSecurityEvent('AUTHENTICATION', {
action,
success,
...additionalInfo
});
}
static logAuthorizationEvent(action, resource, success) {
this.logSecurityEvent('AUTHORIZATION', {
action,
resource,
success
});
}
static logDataAccessEvent(operation, resource, details) {
this.logSecurityEvent('DATA_ACCESS', {
operation,
resource,
...details
});
}
}
這些安全考慮和實(shí)現可以幫助保護應用免受常見(jiàn)的安全威脅。