在 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 净化 HTML
return 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 headers
add_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 app
location / {
root /usr/share/nginx/html;
index index.html;
try_files $uri $uri/ /index.html;
}
# API proxy
location /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 并严格配置白名单
- 设置速率限制防止暴力破解
项目流程图
实际项目中应根据具体业务需求和安全等级要求进行调整。