Vue 项目安全设置方案:XSS/CSRF 防护指南
在 Vue 项目中实施全面的 XSS 和 CSRF 防护需要前后端协同配合。以下是完整的实现方案:
Vue 前端安全配置方案
1. XSS 防御策略
安全数据渲染方案
<template><div><!-- 安全文本渲染(自动转义) --><p>{{ userInput }}</p><!-- HTML 内容渲染(使用 DOMPurify 净化) --><div v-if="trustedContent" v-html="sanitizedHtml"></div></div>
</template><script>
import DOMPurify from 'dompurify';export default {data() {return {userInput: '<script>alert("XSS")<\/script>',trustedContent: '<b>安全内容</b>'};},computed: {sanitizedHtml() {// 使用 DOMPurify 净化 HTMLreturn DOMPurify.sanitize(this.trustedContent, {ALLOWED_TAGS: ['b', 'i', 'em', 'strong', 'a'], // 仅允许安全标签ALLOWED_ATTR: ['href', 'title'] // 仅允许安全属性});}}
};
</script>
URL 参数安全处理
// 在请求 URL 参数时进行编码
const unsafeValue = "<script>malicious</script>";
const safeParam = encodeURIComponent(unsafeValue);this.$http.get(`/api/data?param=${safeParam}`);
内容安全策略 (CSP) 设置
// 在 public/index.html 的 head 中添加 CSP meta 标签
<meta http-equiv="Content-Security-Policy" content="default-src 'self'; script-src 'self' 'nonce-random123' https://trusted.cdn.com;style-src 'self' 'unsafe-inline';img-src 'self' data:; connect-src 'self';font-src 'self';object-src 'none';frame-src 'none';
">
2. CSRF 防护策略
Axios 全局配置
// src/utils/http.js
import axios from 'axios';
import store from '@/store';const http = axios.create({baseURL: process.env.VUE_APP_API_BASE_URL,timeout: 10000,
});// 请求拦截器:自动添加 CSRF Token
http.interceptors.request.use(config => {if (['post', 'put', 'patch', 'delete'].includes(config.method.toLowerCase())) {config.headers['X-CSRF-Token'] = store.state.csrfToken;}return config;
});// 响应拦截器:处理 403 错误(CSRF 失效)
http.interceptors.response.use(response => response,error => {if (error.response?.status === 403) {store.dispatch('refreshCSRF'); // 刷新 CSRF Token// 可选:自动重新发送原始请求return http.request(error.config);}return Promise.reject(error);}
);export default http;
CSRF Token 存储与刷新
// Vuex store 模块
const securityModule = {namespaced: true,state: {csrfToken: null},mutations: {SET_CSRF_TOKEN(state, token) {// 基本验证if (typeof token === 'string' && token.length >= 32) {state.csrfToken = token;}}},actions: {async fetchCSRFToken({ commit }) {try {const response = await http.get('/api/csrf-token');commit('SET_CSRF_TOKEN', response.data.token);// 设置 token 到 meta 标签(可选,SSR 场景下)let meta = document.querySelector('meta[name="csrf-token"]');if (!meta) {meta = document.createElement('meta');meta.name = 'csrf-token';document.head.appendChild(meta);}meta.content = response.data.token;} catch (error) {console.error('获取 CSRF Token 失败', error);}}}
};
3. 路由安全防护
// src/router/index.js
const router = new VueRouter({routes: [// ...{path: '/admin',component: AdminPanel,meta: { requiresAuth: true,capabilities: ['admin']}}]
});router.beforeEach((to, from, next) => {// 检查认证需求if (to.matched.some(record => record.meta.requiresAuth)) {if (!store.getters.isAuthenticated) {next({path: '/login',query: { redirect: to.fullPath }});return;}// 检查权限const requiredCapabilities = to.meta.capabilities || [];if (requiredCapabilities.length > 0 && !store.getters.hasCapabilities(requiredCapabilities)) {next('/forbidden'); // 无权限页面return;}}// 每次路由切换滚动到顶部window.scrollTo(0, 0);next();
});
4. Vuex 安全实践
// 增强安全性的 Vuex store
export default new Vuex.Store({state: {userData: null},mutations: {// 数据存储前的净化处理SET_USER_DATA(state, rawData) {// 执行深层次的净化state.userData = deepSanitize(rawData);}},actions: {// 安全获取用户数据async fetchUserData({ commit }, userId) {try {const response = await http.get(`/api/users/${userId}`);commit('SET_USER_DATA', response.data);} catch (error) {handleSecurityError(error);}}}
});// 深层数据净化函数
function deepSanitize(data) {if (typeof data === 'string') {// 移除 HTML 标签return data.replace(/<[^>]*>/g, '');} if (Array.isArray(data)) {return data.map(item => deepSanitize(item));}if (typeof data === 'object' && data !== null) {return Object.keys(data).reduce((acc, key) => {acc[key] = deepSanitize(data[key]);return acc;}, {});}return data;
}// 安全错误处理
function handleSecurityError(error) {if (error.response) {switch (error.response.status) {case 401:// 处理未认证错误router.push('/login');break;case 403:// 处理未授权错误store.dispatch('handleForbidden');break;case 419:// CSRF Token 失效store.dispatch('refreshCSRF');break;default:console.error('API Error:', error);}}
}
服务端配合实现(Node.js 示例)
1. CSRF Token 生成与验证
// 使用 Express + csurf 中间件
const express = require('express');
const csrf = require('csurf');
const cookieParser = require('cookie-parser');
const helmet = require('helmet');const app = express();
app.use(cookieParser());
app.use(express.json());// 设置 CSRF 保护
const csrfProtection = csrf({cookie: {httpOnly: true,secure: process.env.NODE_ENV === 'production',sameSite: 'strict',maxAge: 86400 // 1 天}
});// 提供 CSRF Token 端点
app.get('/api/csrf-token', csrfProtection, (req, res) => {res.json({ token: req.csrfToken() });
});// 受保护的 API 端点
app.post('/api/update-profile', csrfProtection, (req, res) => {try {// 1. 验证输入(使用验证库)// 2. 处理业务逻辑// 3. 返回响应res.json({ success: true });} catch (error) {handleSecurityError(res, error);}
});// 错误处理中间件
function handleSecurityError(res, error) {if (error.name === 'ValidationError') {res.status(400).json({ error: '无效请求' });} else {res.status(500).json({ error: '服务器错误' });}
}
2. HTTP 安全头设置
// 使用 Helmet 设置安全头
app.use(helmet({contentSecurityPolicy: {directives: {defaultSrc: ["'self'"],scriptSrc: ["'self'", "'nonce-random123'", "trusted-cdn.example.com"],styleSrc: ["'self'", "'unsafe-inline'"],imgSrc: ["'self'", "data:"],connectSrc: ["'self'"],fontSrc: ["'self'"],objectSrc: ["'none'"],frameSrc: ["'none'"]}},xssFilter: true,noSniff: true,frameguard: { action: 'deny' },hsts: {maxAge: 31536000, // 1 年includeSubDomains: true},referrerPolicy: { policy: 'same-origin' }
}));
3. 输入验证与净化
// 使用验证库,例如 Joi
const Joi = require('joi');const userSchema = Joi.object({username: Joi.string().alphanum().min(3).max(30).required(),email: Joi.string().email({minDomainSegments: 2,tlds: { allow: ['com', 'net', 'org'] }}).required(),bio: Joi.string().max(500).optional().escapeHTML() // 清除 HTML
});app.post('/api/users', csrfProtection, (req, res) => {const { error, value } = userSchema.validate(req.body);if (error) {return res.status(400).json({ error: error.details[0].message });}// value 现在是安全、净化后的数据createUser(value);
});
项目部署安全配置
Nginx 示例配置
server {listen 443 ssl;server_name yourdomain.com;ssl_certificate /path/to/cert.pem;ssl_certificate_key /path/to/privkey.pem;# Security headersadd_header Content-Security-Policy "default-src 'self'; script-src 'self' 'nonce-random123' trusted-cdn.com; style-src 'self' 'unsafe-inline'; img-src 'self' data:; font-src 'self'; connect-src 'self'; frame-src 'none';";add_header X-Content-Type-Options "nosniff";add_header X-Frame-Options "DENY";add_header X-XSS-Protection "1; mode=block";add_header Referrer-Policy "same-origin";add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;# Vue applocation / {root /usr/share/nginx/html;index index.html;try_files $uri $uri/ /index.html;}# API proxylocation /api/ {proxy_pass http://backend-service:3000/api/;proxy_http_version 1.1;proxy_set_header Host $host;proxy_set_header X-Real-IP $remote_addr;proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;proxy_set_header Upgrade $http_upgrade;proxy_set_header Connection 'upgrade';}
}
最佳实践检查清单
XSS 防护
- 所有动态 HTML 渲染使用
DOMPurify.sanitize()
- 严格限制
v-html
指令的使用 - 内容安全策略 (CSP) 正确配置并启用
- URL 参数值使用
encodeURIComponent
处理 - 禁用内联事件处理(如:
onclick="..."
) - 设置 Cookie 的
HttpOnly
和Secure
标志
CSRF 防护
- 所有状态变更请求(POST/PUT/PATCH/DELETE)均包含 CSRF Token
- Token 生成、传输和存储安全
- Token 随每个请求变化(一次性使用最佳)
- Cookie 设置
SameSite=Strict
或Lax
- API 端点验证 Origin/Referer
框架级防护
- Vue 生产环境模式启用
- 定期使用
npm audit
检查依赖漏洞 - Vue Router 路由守卫实现权限控制
- 禁用 Vue 配置中的 Devtools 在生产环境(
Vue.config.devtools = false
)
服务器协同
- 所有响应设置安全头(X-Content-Type-Options, X-Frame-Options 等)
- API 端点实施严格的输入验证
- 敏感操作(如密码更改)增加二次认证
- 使用 HTTPS 加密所有通信
- API 启用 CORS 并严格配置白名单
- 设置速率限制防止暴力破解
项目流程图
实际项目中应根据具体业务需求和安全等级要求进行调整。