前言
在前端开发领域,安全问题如同隐藏在代码深处的暗礁,稍有不慎就可能让产品陷入漏洞风险。尤其是在面试场景中,面试官往往会跳出概念考核,直击「如何在实际开发中落地安全防护」的核心能力。作为高频考点的 XSS、CSRF、CSP、Clickjacking 等安全问题,不仅需要理解原理,更要掌握与主流框架(如 Vue、React、Next.js)深度结合的实践方案。
下文就将的技术栈:Vue、React、Next.js,谈一谈总结到的解决方法。
XSS(跨站脚本攻击)
-
Vue:
默认转义:使用 {{ }} 插值时自动转义 HTML。
风险点:v-html 指令会渲染原始 HTML,需用 DOMPurify 等库净化内容。
html
<div v-html="purifiedContent"></div>
<script>
import DOMPurify from 'dompurify';
export default {
data() { return { content: '<script>alert(1)</script>' }; },
computed: {
purifiedContent() { return DOMPurify.sanitize(this.content); }
}
};
</script>
- React:
自动转义:JSX 中的变量会被转义。
风险点:dangerouslySetInnerHTML,需净化内容。
js
import DOMPurify from 'dompurify';
function SafeComponent({ content }) {
const clean = DOMPurify.sanitize(content);
return <div dangerouslySetInnerHTML={{ __html: clean }} />;
}
CSRF(跨站请求伪造)
- 通用方案:
后端生成 CSRF Token,前端在请求头或表单中携带。
设置 Cookie 的 SameSite 属性为 Strict 或 Lax,同网站,同域名等。
-
Next.js 示例(API 路由):
js// 生成 Token 并存入 Session import { csrfToken } from 'csrf'; export async function getServerSideProps(context) { const token = csrfToken(context.req); return { props: { token } }; } // 提交时验证 export default function handler(req, res) { if (req.method === 'POST') { if (!validateCsrfToken(req.headers['x-csrf-token'], req.session.token)) { return res.status(403).json({ error: 'Invalid CSRF token' }); } } }
CSP(内容安全策略)
内容安全策略 (CSP)是一个额外的安全层,用于检测并削弱某些特定类型的攻击,包括跨站脚本(XSS)和数据注入攻击等。无论是数据盗取、网站内容污染还是恶意软件分发,这些攻击都是主要的手段。
CSP 被设计成完全向后兼容(除 CSP2 在向后兼容有明确提及的不一致; 更多细节查看这里 章节 1.1)。不支持 CSP 的浏览器也能与实现了 CSP 的服务器正常工作,反之亦然:不支持 CSP 的浏览器只会忽略它,如常运行,默认为网页内容使用标准的同源策略。如果网站不提供 CSP 标头,浏览器也使用标准的同源策略。
对加载内容、图片、媒体资源和脚本策略控制。具体示例可参考MDN
- Next.js 配置(next.config.js):
示例使用的是严格安全策略。
bash
module.exports = {
async headers() {
return [{
source: '/(.*)',
headers: [{
key: 'Content-Security-Policy',
value: "default-src 'self'; script-src 'self' 'unsafe-inline'"
}]
}];
}
};
点击劫持(Clickjacking)
X-Frame-Options
HTTP 响应头是用来给浏览器指示允许一个页面可否在 ](https://developer.mozilla.org/zh-CN/docs/Web/HTML/Reference/Elements/frame)、[
、](https://developer.mozilla.org/zh-CN/docs/Web/HTML/Reference/Elements/embed) 或者 [
中展现的标记。站点可以通过确保网站没有被嵌入到别人的站点里面,从而避免点击劫持攻击。
仅当访问文档的用户使用支持 X-Frame-Options
的浏览器时,此附加的安全性才会被提供。
具体案例介绍查看MDN
- Next.js 设置 HTTP 头:
bash
// next.config.js
headers: [{
key: 'X-Frame-Options',
value: 'DENY'
}]
依赖安全
- 使用 npm audit 或 yarn audit 检查漏洞,集成 Snyk 或 Dependabot 自动更新。
说明:Dependabot是Github提供的功能,需要在github仓库中设置并打开。
一份相关的配置文件.github/dependabot.yml参考如下:
yaml
version: 2
updates:
- package-ecosystem: "npm" # 表示依赖项是 npm 包
directory: "/" # 表示依赖文件位于仓库的根目录
schedule:
interval: "daily" # 每天检查更新
versioning-strategy: increase # 更新到更高的版本
allow:
# 可以指定允许更新的包名
- dependency-name: "express"
ignore:
# 可以指定忽略的包名和版本范围
- dependency-name: "moment"
versions: [">=2.0.0"]
CORS(跨域资源共享)
跨源资源共享 (CORS,或通俗地译为跨域资源共享)是一种基于 HTTP 头的机制,该机制通过允许服务器标示除了它自己以外的其他源(域、协议或端口),使得浏览器允许这些源访问加载自己的资源。跨源资源共享还通过一种机制来检查服务器是否会允许要发送的真实请求,该机制通过浏览器发起一个到服务器托管的跨源资源的"预检"请求。在预检中,浏览器发送的头中标示有 HTTP 方法和真实请求中会用到的头。
跨源 HTTP 请求的一个例子:运行在 https://domain-a.com
的 JavaScript 代码使用 XMLHttpRequest
来发起一个到 https://domain-b.com/data.json
的请求。
其它相关概念参考MDN。
- Next.js
js
// next.config.js
module.exports = {
async headers() {
return [{
source: '/api/(.*)',
headers: [
{ key: 'Access-Control-Allow-Origin', value: 'https://trusted-domain.com' },
{ key: 'Access-Control-Allow-Methods', value: 'GET,POST' },
],
}];
},
};
框架/技术栈特有安全实践
Vue
- 服务端渲染(SSR):避免直接将用户输入注入模板,使用 vue-server-renderer 的 createRenderer 自动转义。
- 第三方库:谨慎使用 vue-sanitize 等插件过滤内容
React
- 属性注入风险:检查动态属性(如 href)是否包含 javascript:
js
const userInput = 'javascript:alert(1)';
<a href={userInput}>Click</a> // 危险!
// 解决方案:校验协议
const safeUrl = userInput.startsWith('http') ? userInput : '#';
- PropTypes 校验:防止传递危险内容。
js
import PropTypes from 'prop-types';
MyComponent.propTypes = { content: PropTypes.string.isRequired };
Next.js
- API 路由安全:
身份验证:使用 next-auth 库管理会话。
输入验证:使用 zod 或 yup 校验请求体。
- 中间件防护(Next.js 12+):
js
// middleware.js
import { NextResponse } from 'next/server';
export function middleware(req) {
const token = req.cookies.get('auth-token');
if (!token) return NextResponse.redirect('/login');
return NextResponse.next();
}
-
静态资源 SRI:在 next.config.js 中为脚本添加完整性校验。
-
Next.js一些安全头的配置
js
// next.config.js
module.exports = {
async headers() {
return [
{
source: '/(.*)',
headers: [
// 防御 XSS
{ key: 'X-XSS-Protection', value: '1; mode=block' },
// 禁止 MIME 类型嗅探
{ key: 'X-Content-Type-Options', value: 'nosniff' },
// 防止点击劫持
{ key: 'X-Frame-Options', value: 'DENY' },
// 严格 CSP(需根据项目调整)
{ key: 'Content-Security-Policy', value: "default-src 'self'; script-src 'self' 'unsafe-inline'" },
],
},
];
},
};
Vue 3 新增安全特性
- 响应式数据边界
Vue 3 的响应式系统通过 Proxy 实现,避免了 Vue 2 中 Object.defineProperty 的枚举漏洞(如恶意对象污染响应式数据)。 - SFC 编译时转义
在单文件组件(SFC)中,模板表达式会在编译阶段自动转义 HTML 字符,减少运行时 XSS 风险。 - 自定义指令安全
避免在自定义指令中直接操作 el.innerHTML,如需渲染动态内容,建议通过计算属性配合 DOMPurify:
bash
<template>
<div v-my-directive="safeContent"></div>
</template>
<script>
import DOMPurify from 'dompurify';
export default {
directives: {
myDirective(el, { value }) {
el.textContent = DOMPurify.sanitize(value); // 避免 innerHTML
}
},
computed: {
safeContent() {
return DOMPurify.sanitize(this.userInput);
}
}
};
</script>
React 18+ 安全增强
- 自动批处理边界
React 18 的自动批处理机制可防止恶意代码通过 setTimeout 绕过批量更新,降低内存泄漏攻击风险。 - 严格模式检测
在 React.StrictMode 下会检测以下安全隐患:
jsx
<React.StrictMode>
{/* 检测过时生命周期 */}
{/* 检测未释放的 DOM 引用 */}
{/* 检测意外的副作用重复执行 */}
</React.StrictMode>
- 事件处理机制强化
React 合成事件系统会自动转义事件处理函数中的用户输入(如 onClick 中的动态内容),进一步防范 XSS。
内存泄漏攻击(如 Prototype Pollution)
-
场景
恶意用户通过深度嵌套对象污染 JavaScript 原型链,导致全局逻辑被篡改(如 Array.prototype.push 被覆盖)。
-
防御
在 Vue/React 中避免直接合并用户输入对象,使用 Object.assign({}, safeObj, userInput) 而非直接修改原型。
使用 immer 库处理不可变数据,其内部通过 produce 函数隔离原型污染风险:
jsx
// React 示例
import produce from 'immer';
const handleInput = useCallback((input) => {
setState(produce((draft) => {
draft.data = input; // immer 会创建新对象,避免原型污染
}));
}, []);
浏览器指纹跟踪防御
- 风险
攻击者通过浏览器指纹(如 navigator.hardwareConcurrency、canvas 指纹)唯一标识用户,绕过传统身份验证。
- 防御
在 Next.js 中配置 HTTP 头禁止浏览器指纹收集:
bash
// next.config.js
module.exports = {
async headers() {
return [
{
source: '/',
headers: [
// 禁止浏览器暴露硬件信息
{ key: 'Permissions-Policy', value: "hardware-concurrency=()", },
// 随机化 canvas 指纹
{ key: 'Cross-Origin-Embedder-Policy', value: 'require-corp', },
{ key: 'Cross-Origin-Opener-Policy', value: 'same-origin', },
],
},
];
},
};
- 使用第三方库主动混淆指纹特征。
更多的实际例子
前端无感刷新token(axios)
js
// Axios 拦截器示例(React)
axios.interceptors.response.use(
response => response,
async error => {
const originalRequest = error.config;
if (error.response.status === 401 && !originalRequest._retry) {
originalRequest._retry = true;
const newToken = await refreshToken(); // 调用刷新 Token 接口
axios.defaults.headers.common['Authorization'] = `Bearer ${newToken}`;
return axios(originalRequest);
}
return Promise.reject(error);
}
);
动态加载脚本并添加完整性校验(SRI)
js
// Vue 示例
mounted() {
const script = document.createElement('script');
script.src = 'https://example.com/analytics.js';
script.integrity = 'sha256-xxxx';
script.crossOrigin = 'anonymous';
document.head.appendChild(script);
}
使用rel="noopener noreferrer"(防止钓鱼攻击)
js
// React 示例
<a href=" " target="_blank" rel="noopener noreferrer">Link</a >
对Next.js API请求采取请求速率限制
js
// 使用 `rate-limiter-flexible` 库
import { RateLimiterMemory } from 'rate-limiter-flexible';
const limiter = new RateLimiterMemory({ points: 5, duration: 60 });
export default async function handler(req, res) {
try {
await limiter.consume(req.socket.remoteAddress);
} catch {
return res.status(429).json({ error: 'Too many requests' });
}
}
文件上传限制上传类型和大小
-
前端部分
js// React 示例 <input type="file" accept=".jpg,.png" onChange={(e) => { if (e.target.files[0].size > 5 * 1024 * 1024) { alert('File too large!'); } }} />
-
后端二次验证
js
// pages/api/upload.js
import { createReadStream } from 'fs';
import fileType from 'file-type';
export default async function handler(req, res) {
const stream = createReadStream(req.file.path);
const type = await fileType.fromStream(stream);
if (!['jpg', 'png'].includes(type.ext)) {
fs.unlinkSync(req.file.path); // 删除非法文件
return res.status(400).json({ error: 'Invalid file type' });
}
}
验证重定向参数
js
// Vue 路由守卫示例
router.beforeEach((to, from, next) => {
if (to.query.redirect) {
const allowedDomains = ['https://trusted.com', '/internal-path'];
if (!allowedDomains.some(domain => to.query.redirect.startsWith(domain))) {
delete to.query.redirect; // 删除非法重定向
}
}
next();
});
客户端的敏感数据
-
使用环境变量
js// .env.local API_KEY=xxxx // 页面中使用 export async function getServerSideProps() { const data = await fetch('https://api.example.com', { headers: { Authorization: process.env.API_KEY }, }); }
-
混淆前端代码(webpack terser)
js// next.config.js module.exports = { productionBrowserSourceMaps: false, // 禁用 Source Map webpack: (config) => { config.optimization.minimizer[0].options.terserOptions.compress.drop_console = true; return config; }, };
异常行为的监控
例如,登录失败五次以上,上传行为日志(获取到IP)
js
// pages/api/log.js
export default function handler(req, res) {
if (req.body.action === 'login' && req.body.failedAttempts > 5) {
console.warn(`Suspicious login activity from IP: ${req.socket.remoteAddress}`);
// 发送告警到监控系统(如 Sentry)
}
res.status(200).end();
}
Cloudflare Turnstile 人机识别,防止自动化攻击
不收集用户隐私,只通过用户的行为模式。
- 在前端网页中添加一个TurnStile的脚本,并渲染组件。
- TurnStile在后台分析用户的行为模式(鼠标移动、点击频率)判断是否人为。
- 生成token。如果验证通过,TurnStile会生成一个加密的Token,发送到后端验证。
- 后端可通过Cloudflare的API,也可以通过云函数的方式验证Token的有效性,确保请求来自合法用户。
面试回答注意事项
Vue 中如何避免 XSS?
- 答:避免使用 v-html,必须使用时用 DOMPurify 净化内容;确保服务端渲染时数据转义。
React 的 dangerouslySetInnerHTML 有什么风险?
- 答:直接插入原始 HTML 可能导致 XSS,应始终先净化内容,并使用 DOMPurify 处理。
Next.js 中如何配置 CSP?
- 答:在 next.config.js 的 headers 中设置 Content-Security-Policy,或使用自定义服务器中间件添加 HTTP 头。
如何防范 CSRF?Next.js 中的实现?
- 答:使用 CSRF Token 验证,Next.js 可在 API 路由中通过 getServerSideProps 传递 Token,并在提交时校验。
JWT 存储在前端哪里最安全?
- 答:存储在 httpOnly Cookie 中,避免被 XSS 窃取,同时设置 SameSite=Lax 和 Secure 标志(HTTPS)。
总结
- 框架特性:Vue/React 的自动转义机制需配合安全实践(如避免危险 API),Next.js 需合理配置 HTTP 头和中间件。
- 工具链:使用净化库(DOMPurify)、安全头扫描工具(SecurityHeaders.com)、依赖审计工具。
- 开发习惯:永远不信任用户输入,输出前转义/净化,最小化第三方依赖风险。