普通视图
前端高频面试题之CSS篇(一)
TinyEngine 低代码实时协作揭秘:原理 +实操,看完直接用!
【Vue3】大屏性能优化黑科技:Vue 3 中实现请求合并,让你的大屏飞起来!
大部分人都错了!这才是chrome插件多脚本通信的正确姿势 | 掘金一周 11.27
通过<RouterView/>来切换页面组件时,transition如何生效?
浅记一下ElementPlus中的虚拟化表格(el-table-v2)的简单使用
vue3中createApp多个实例共享状态
Antigravity 登录问题/数据泄露风险 (附:白嫖一个月 Gemini Enterprise 攻略)
都React 19了,他到底带来了什么?
被国外IP持续DDOS怎么办?
还在用 WebSocket 做实时通信?SSE 可能更简单
CopilotKit-丝滑连接agent和应用-理论篇
深入 Nestjs 底层概念(1):依赖注入和面向切面编程 AOP
🍭🍭🍭升级 AntD 6:做第一个吃螃蟹的人
flutter睡眠与冥想数据可视化神器:sleep_stage_chart插件全解析
Vue3 脚本革命:<script setup> 让你的代码简洁到飞起!
诶,这么好用的 mock 你怎么不早说
前言
老板:小王啊,你 bug 率也太高了,得想想办法,不然扣你绩效。 小王眼里闪过一分无奈二分心酸三分苦涩四分身不由己,很多条件 case、边缘 case 都不好触发,很难自测充分,比如:
- 新手引导,标识由后端下发,申请个新账号非常麻烦
- 各类状态的流转,比如 xx 任务失败,但是大多数情况下都走不到失败路线
- 余额为 0 的情况下的各种 case,测试账号往往都是拉满的余额
- vip 与普通用户的各种差异点
对这类的情况,就只能盲写,然后手动在代码里改状态来大致看一下效果,就很难模拟出一个完整的流程,会出 bug 也属实正常。
有心急的同学就要说了,mock 呀,用 mock 呀!
话虽如此,但是要使用这一套,一是你需要起服务/开程序、写数据、运行,这一套下来还是比较费时间的。二是此类问题绵绵无绝期,每次都这么搞,时间成本会无限累加。
而且频繁调试往往会弄脏代码,很容易提交上一些调试数据,有没有更简单好用的方案呢?
有的有的(掏出 chrome),这么简单的方案还有一个,那就是我们常打交道的控制台了。
chrome 之替换
这里举个实际例子带大家走一遍,比如我是掘金的前端,我想测试一下文章审核中的样式
![]()
但是总不能回回都重新写一个审核中的文章供自己测试,费时费力。已知审核中的状态由后端 dynamic 接口的 audit_status决定,该状态为 1 时会展示审核中,为 0 时是正常。
先看一下最终实现的效果吧,只需要简单的操作就可以开闭 mock,而且不侵入代码:
![]()
OK,再说下详细使用步骤:
- 在网络中找到目标接口
- 右键进行替换
- 如果是首次使用,会有个提示,需要你选择一个文件夹来存放数据
- 此时会进入到替换的编辑页,该页面可能为刚才请求的内容,也有时候为空。
如果为空的话,可以先去刚才的响应里复制,再贴过来
- 接着就可以自行修改响应的内容了:这里我们把
audit_status从0改为1
- ctrl s 保存后,刷新页面即可看到效果
大功告成,虽然看起来步骤很多,但大多数情况下实际上只需要右键替换,编辑保存两步就完事了,既方便启用,又不侵入代码。
关于XSS和CSRF,面试官更喜欢这样的回答!
这是我们前端最常见的两种攻击手段,也是面试中最常考的前端攻击。这篇文章我用最精炼、最优雅,也是面试官最喜欢的回答方式来讲解下 XSS 和 CSRF。
一、XSS(跨站脚本)
原理
攻击者把 恶意脚本 注入到受信任页面并被浏览器执行,脚本 利用页面的信任上下文(Cookies、localStorage、DOM)窃取数据或劫持会话。
常见类型
- 反射型(参数或路径直接反射到页面并执行)
- 存储型(恶意内容存储在服务器,其他用户访问时触发)
- DOM-based(客户端不安全的 DOM 操作导致执行,和服务器无关)
最小复现示例(不安全的后端 + 不安全的前端)
后端(Express — 危险示例)
// server.js(示例,仅演示不安全行为)
const express = require('express');
const app = express();
app.get('/search', (req, res) => {
const q = req.query.q || '';
// 直接把用户输入拼到 HTML 中 —— 危险!
res.send(`<html><body>搜索: ${q}</body></html>`);
});
app.listen(3000);
访问 /search?q=<script>alert(1)</script> 会执行脚本(反射型)。
前端 DOM XSS(危险)
<div id="out"></div>
<script>
const q = location.search.split('q=')[1] || '';
document.getElementById('out').innerHTML = q; // 不转义 —— 危险(DOM XSS)
</script>
实战防范要点
- **输出编码(服务器端)**:所有插入 HTML 的内容做 HTML 转义(
&<>\"')。 -
前端最小化 innerHTML:尽量用框架绑定(React/Vue 的模板)替代
innerHTML。框架框出来的插值(
{value}/{{ value }})会自动做 HTML 转义,把<、>、&、"、'等关键字符替换成实体(<等),从而把攻击脚本当文本显示,而不是执行。 -
富文本白名单清洗:对于必须存储/渲染的 HTML(富文本),后端用白名单 sanitizer(比如
bleach/html-sanitizer),前端再用 DOMPurify 做一次保护,对标签属性等进行清洗。 - Content-Security-Policy(CSP)头部:禁止内联脚本、只允许可信源。
- HttpOnly Cookie 头部:token/cookie 设置 HttpOnly,防止被脚本直接读取(减轻 XSS 后果)。
示例代码 — 安全改造
后端(Express + 转义)
const escapeHtml = s => String(s)
.replace(/&/g, '&')
.replace(/</g, '<')
.replace(/>/g, '>')
.replace(/"/g, '"')
.replace(/'/g, ''');
app.get('/search', (req, res) => {
const q = escapeHtml(req.query.q || '');
res.send(`<html><body>搜索: ${q}</body></html>`);
});
前端(若必须渲染 HTML,用 DOMPurify)
<!-- npm install dompurify -->
<script src="https://unpkg.com/dompurify@2.<!--version-->/dist/purify.min.js"></script>
<div id="content"></div>
<script>
// htmlFromServer 来自后端 API,仍需 sanitize
const htmlFromServer = '<img src=x onerror=alert(1)>';
document.getElementById('content').innerHTML = DOMPurify.sanitize(htmlFromServer);
</script>
设置 CSP(Nginx/Express header 示例)
Content-Security-Policy: default-src 'self'; script-src 'self' https://cdn.example.com; object-src 'none';
二、CSRF(跨站请求伪造)
原理
利用用户已登录且浏览器会自动带上凭证(cookie)的特性,攻击者诱导用户发起对受信任站点的请求(如通过自动提交表单或图片请求),从而在用户 名下执行未授权操作。
最小复现示例(攻击者页面)
如果 bank.com/transfer 接受 GET 或 POST 并依赖 cookie 验证,攻击页面可这样写:
<!-- auto.html(在攻击者域名上) -->
<form action="https://bank.com/transfer" method="POST" id="f">
<input name="to" value="attacker" />
<input name="amount" value="1000" />
</form>
<script>document.getElementById('f').submit();</script>
用户在已登录 bank.com 的情况下访问攻击页面时,浏览器会自动带上 bank.com 的 cookie,导致转账。
防护要点
-
SameSite Cookie:把 session/cookie 设置
SameSite=Lax或Strict(Lax 对 POST 有保护,适配大多数情形)。 - **CSRF Token(同步/双提交)**:服务端生成随机 token,响应给前端;敏感请求必须携带并校验该 token。
该 token 不同于 jwt token ,此处的 csrf-token 只为配合 session+cookie 传统鉴权策略做安全防护。
-
检查 Origin/Referer:对跨站请求校验
Origin或Referer头(通常对 POST/PUT/DELETE 生效)。 -
避免用 cookie 做对外 API 的认证:采用
Authorization: Bearerheader 的 token 机制(只有 JS 能读/写),结合 CORS 限制。 - 敏感操作二次确认:密码/OTP/二次验证。
示例代码(Express + scrf token + csurf)
csurf使用 **双提交验证机制(CSRF Token)**:
- 服务端生成一个 CSRF Token,放在 cookie 或 session 中。
- 前端每次发 POST/PUT/DELETE 请求要带上这个 token,常放在请求头或表单隐藏字段,比如:
X-CSRF-Token: ey2423482374823748234- 服务端校验 token,是否匹配、是否未过期、是否合法。
后端(Express)
// server.js
const express = require('express');
const cookieParser = require('cookie-parser');
const csurf = require('csurf');
const app = express();
app.use(cookieParser());
app.use(express.json());
app.use(csurf({ cookie: { httpOnly: true, sameSite: 'lax' } }));
app.get('/csrf-token', (req, res) => {
// 返回 token 给 SPA 前端(用于后续请求 header)
res.json({ csrfToken: req.csrfToken() });
});
app.post('/transfer', (req, res) => {
// csurf 中间件会自动校验请求中的 token(_csrf 字段或 X-CSRF-Token header)
// 执行转账逻辑...
res.json({ ok: true });
});
app.listen(3000);
前端 SPA(获取 token 并在请求头中发送)
// 初始化时获取 token
async function init() {
const r = await fetch('/csrf-token', { credentials: 'include' });
const { csrfToken } = await r.json();
window.__CSRF_TOKEN = csrfToken;
}
// 发送受保护请求
async function transfer() {
await fetch('/transfer', {
method: 'POST',
credentials: 'include', // 仍然带 cookie
headers: {
'Content-Type': 'application/json',
'X-CSRF-Token': window.__CSRF_TOKEN
},
body: JSON.stringify({ to: 'bob', amount: 100 })
});
}
只用 SameSite(简洁替代,适用多数场景),在服务端设置 cookie:
Set-Cookie: session=...; HttpOnly; Secure; SameSite=Lax; Path=/;
这就能阻止绝大多数通过第三方页面触发的 POST/跨站敏感操作。
三、XSS 与 CSRF 的关键总结
概念:
- XSS:攻击者注入脚本并可读取页面内容(更强),根源是输出/DOM 不安全。
- CSRF:攻击者伪造用户请求,无法直接读取响应,根源是浏览器自动带凭证。
防护:
- 后端统一使用 HTML escape 库;富文本走白名单 sanitizer。
- 全站 Cookie:
HttpOnly; Secure; SameSite=Lax。 - 对需要的页面开启 CSP(report-only 先观测,再 enforce)。
- SPA:首次获取 CSRF token 并在后续请求中以 header 发送;服务端检查
Origin/Referer。 - CI/代码审查禁止随意使用
innerHTML/eval/dangerouslySetInnerHTML。 - 对关键操作实施二次验证(密码/OTP)。