零信任安全模型:永不信任,始终验证
2026年的前端安全形势比以往任何时候都更加严峻。传统的"城堡与护城河"安全模型——即信任内网、不信任外网——已经彻底失效。零信任安全模型应运而生,其核心原则只有一条:
永不信任,始终验证(Never Trust, Always Verify)
这意味着每一个请求、每一次交互、每一个数据包,无论来自何处,都必须经过严格的身份验证和授权检查。不存在"可信区域",不存在"隐式信任"。
传统边界安全 vs 零信任架构
| 维度 | 传统边界安全 | 零信任架构 |
|---|---|---|
| 信任模型 | 内网可信,外网不可信 | 所有请求均不可信 |
| 验证方式 | 一次性登录验证 | 每次请求独立验证 |
| 网络边界 | 依赖防火墙/VPN | 无边界,身份即边界 |
| 攻击面 | 突破边界即可横向移动 | 每步都需要重新验证 |
| 适用于 | 传统单体应用 | 云原生/微服务/微前端 |
| 前端安全 | 假设前端在"安全区域" | 前端是不可信终端 |
传统模型最大的问题在于:一旦攻击者突破了网络边界,就可以在内网自由横向移动。而零信任架构下,即使攻击者拿到了某个Token,也无法长期冒充用户,因为每个操作都需要重新验证。
前端零信任:每个API请求都独立验证
前端是零信任架构中最关键的一环——因为前端代码运行在用户浏览器中,是完全不可控的环境。攻击者可以修改前端代码、拦截网络请求、篡改本地存储。
核心原则
- 无隐式信任:不因为用户已登录就信任后续所有请求
- 最小权限:每个请求只获取完成该操作所需的最小权限
- 持续验证:Token短期有效,频繁刷新
- 假设已被攻破:设计时假设攻击者已控制前端
请求级验证示例
class ZeroTrustHttpClient {
private tokenManager: TokenManager;
private proofManager: DPoPProofManager;
async request(config: RequestConfig): Promise<Response> {
const token = await this.tokenManager.getValidToken();
const proof = await this.proofManager.generateProof(
config.method,
config.url
);
const response = await fetch(config.url, {
method: config.method,
headers: {
'Authorization': `DPoP ${token}`,
'DPoP': proof,
'X-Request-ID': crypto.randomUUID(),
'X-Timestamp': Date.now().toString(),
},
body: config.body,
});
if (response.status === 401) {
await this.tokenManager.refreshToken();
return this.request(config);
}
return response;
}
}
每个请求都携带:独立的Token、DPoP证明、唯一请求ID、时间戳。服务端可以验证请求的完整性和时效性。
Token安全:JWT最佳实践
JWT(JSON Web Token)是前端零信任的核心载体,但使用不当会引入严重安全风险。
JWT最佳实践清单
| 实践 | 说明 | 风险等级 |
|---|---|---|
| 短期Access Token | 有效期≤5分钟 | 🔴 关键 |
| HttpOnly Cookie存储 | 防止XSS窃取 | 🔴 关键 |
| Refresh Token轮换 | 每次刷新颁发新Refresh Token | 🔴 关键 |
| Token绑定(DPoP) | Token与密钥对绑定 | 🟡 重要 |
| Audience限制 | 限定Token使用目标 | 🟡 重要 |
| Issuer验证 | 验证Token签发者 | 🟢 推荐 |
| JTI去重 | 防止Token重放攻击 | 🟢 推荐 |
Refresh Token轮换机制
class TokenManager {
private accessToken: string | null = null;
private refreshToken: string | null = null;
async getValidToken(): Promise<string> {
if (this.accessToken && !this.isExpired(this.accessToken)) {
return this.accessToken;
}
return this.refreshTokens();
}
private async refreshTokens(): Promise<string> {
if (!this.refreshToken) {
throw new Error('No refresh token available');
}
const response = await fetch('/api/auth/refresh', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-Refresh-Token': this.refreshToken,
},
});
if (!response.ok) {
this.accessToken = null;
this.refreshToken = null;
throw new Error('Token refresh failed - possible theft detected');
}
const { accessToken, refreshToken } = await response.json();
this.accessToken = accessToken;
this.refreshToken = refreshToken;
return accessToken;
}
}
关键点:如果旧的Refresh Token被使用后,服务端发现它已经被轮换过,说明存在Token盗窃,应立即撤销该用户所有Token。
DPoP:Token绑定证明
DPoP(Demonstration of Proof-of-Possession)是OAuth 2.0的扩展,将Token与客户端的密钥对绑定:
class DPoPProofManager {
private keyPair: CryptoKeyPair | null = null;
async init(): Promise<void> {
this.keyPair = await crypto.subtle.generateKey(
{ name: 'ECDSA', namedCurve: 'P-256' },
false,
['sign']
);
}
async generateProof(method: string, url: string): Promise<string> {
if (!this.keyPair) throw new Error('Key pair not initialized');
const header = { typ: 'dpop+jwt', alg: 'ES256', jwk: await this.getPublicKey() };
const payload = {
htu: new URL(url).origin + new URL(url).pathname,
htm: method,
iat: Math.floor(Date.now() / 1000),
jti: crypto.randomUUID(),
};
return this.signJWT(header, payload);
}
}
即使攻击者窃取了Token,没有对应的私钥也无法使用,因为无法生成有效的DPoP证明。
运行时保护:CSP、Trusted Types与SRI
CSP严格模式
Content Security Policy是前端运行时安全的第一道防线:
<meta http-equiv="Content-Security-Policy" content="
default-src 'none';
script-src 'strict-dynamic' 'nonce-abc123';
style-src 'self' 'nonce-abc123';
img-src 'self' https: data:;
font-src 'self';
connect-src 'self' https://api.example.com;
frame-src 'none';
object-src 'none';
base-uri 'self';
form-action 'self';
require-trusted-types-for 'script';
report-uri /api/csp-report;
">
关键策略解读:
'strict-dynamic':只允许由受信任脚本动态创建的脚本执行'nonce-abc123':只有携带匹配nonce的脚本才能执行require-trusted-types-for 'script':强制启用Trusted Typesdefault-src 'none':默认拒绝所有,白名单逐项开放
Trusted Types防XSS
Trusted Types API从根源上防止DOM XSS——它要求所有DOM Sink(如innerHTML、eval)只能接受经过策略验证的"可信类型":
if (window.trustedTypes) {
const sanitizer = trustedTypes.createPolicy('default', {
createHTML: (input: string) => {
const sanitized = DOMPurify.sanitize(input, {
ALLOWED_TAGS: ['b', 'i', 'em', 'strong', 'a', 'p', 'br'],
ALLOWED_ATTR: ['href', 'target'],
});
return sanitized;
},
createScriptURL: (input: string) => {
const allowed = ['https://cdn.example.com/'];
if (allowed.some(origin => input.startsWith(origin))) {
return input;
}
throw new Error('Untrusted script URL blocked');
},
createScript: () => {
throw new Error('createScript is not allowed');
},
});
window.trustedTypes.defaultPolicy = sanitizer;
}
效果:即使攻击者成功注入了恶意字符串,也无法将其赋值给innerHTML等DOM Sink,因为字符串不是Trusted Type。
SRI子资源完整性
Subresource Integrity确保加载的外部资源未被篡改:
<script
src="https://cdn.example.com/lib/v2.3.1/bundle.js"
integrity="sha384-oc5F5A5B7B6B8B9C0D1E2F3G4H5I6J7K8L9M0N1O2P3Q4R5S6T7U8V9W0X1Y2Z3"
crossorigin="anonymous"
></script>
零信任要求:所有第三方CDN资源必须配置SRI,否则应视为不可信资源拒绝加载。
微前端安全:沙箱隔离与权限控制
微前端架构下,多个子应用共享同一页面,安全隔离至关重要。
沙箱隔离策略
class MicroFrontendSandbox {
private proxyWindow: Window;
private allowedAPIs: Set<string>;
constructor(allowedAPIs: string[]) {
this.allowedAPIs = new Set(allowedAPIs);
this.proxyWindow = this.createProxyWindow();
}
private createProxyWindow(): Window {
const fakeWindow = Object.create(null) as Window;
const targetWindow = window;
return new Proxy(fakeWindow, {
get: (_, key: string) => {
if (this.allowedAPIs.has(key)) {
return targetWindow[key as keyof Window];
}
if (key === 'window' || key === 'self') {
return this.proxyWindow;
}
console.warn(`[Sandbox] Blocked access to window.${key}`);
return undefined;
},
set: (_, key: string, value) => {
(fakeWindow as any)[key] = value;
return true;
},
});
}
execute(code: string): void {
const wrappedCode = `(function(window, self, globalThis) { ${code} })(this, this, this)`;
new Function(wrappedCode).call(this.proxyWindow);
}
}
跨应用通信安全
class SecureInterAppCommunication {
private channel: BroadcastChannel;
private appIdentity: string;
private signingKey: CryptoKey;
async sendMessage(targetApp: string, message: unknown): Promise<void> {
const payload = {
from: this.appIdentity,
to: targetApp,
data: message,
timestamp: Date.now(),
nonce: crypto.randomUUID(),
};
const signature = await this.sign(JSON.stringify(payload));
this.channel.postMessage({ payload, signature });
}
async receiveMessage(event: MessageEvent): Promise<unknown> {
const { payload, signature } = event.data;
if (payload.to !== this.appIdentity) return null;
if (Date.now() - payload.timestamp > 5000) {
throw new Error('Message expired - possible replay attack');
}
const isValid = await this.verifySignature(payload.from, signature, payload);
if (!isValid) {
throw new Error('Invalid signature - message rejected');
}
return payload.data;
}
}
子应用权限控制矩阵
| 权限 | 主应用 | 用户中心 | 数据看板 | 营销模块 |
|---|---|---|---|---|
| localStorage | ✅ 读写 | ✅ 读写前缀 | ❌ | ❌ |
| 路由跳转 | ✅ 全部 | ✅ 自身路由 | ✅ 只读 | ✅ 自身路由 |
| API调用 | ✅ 全部 | ✅ /api/user | ✅ /api/data | ✅ /api/market |
| 全局状态 | ✅ 读写 | ✅ 只读 | ✅ 只读 | ✅ 读写 |
| Cookie | ✅ 读写 | ✅ 只读 | ❌ | ❌ |
供应链安全:npm审计与SBOM
前端供应链攻击已成为最危险的安全威胁之一。一个恶意的npm包可以窃取环境变量、植入后门、甚至加密劫持。
npm安全审计流程
# 1. 检查已知漏洞
npm audit --audit-level=high
# 2. 检查包的维护者信息
npx npm-check --skip-unused
# 3. 验证lockfile完整性
npm ci --ignore-scripts
# 4. 检查安装脚本(postinstall等)
npx lockfile-lint --path package-lock.json --type npm --validate-https
# 5. 生成SBOM
npx @cyclonedx/cyclonedx-npm --output-format json -o sbom.json
Lockfile完整性保护
{
"scripts": {
"preinstall": "npx lockfile-lint --path package-lock.json --type npm --validate-https --allowed-hosts npm",
"postinstall": "npm audit --audit-level=high || exit 1"
}
}
SBOM(软件物料清单)
SBOM记录了项目中所有依赖的完整清单,包括传递依赖:
{
"bomFormat": "CycloneDX",
"specVersion": "1.5",
"components": [
{
"type": "library",
"name": "react",
"version": "19.0.0",
"purl": "pkg:npm/react@19.0.0",
"hashes": [{ "alg": "SHA-512", "content": "abc123..." }]
},
{
"type": "library",
"name": "lodash",
"version": "4.17.21",
"purl": "pkg:npm/lodash@4.17.21",
"hashes": [{ "alg": "SHA-512", "content": "def456..." }]
}
]
}
最佳实践:每次CI构建都生成SBOM,并与已知漏洞数据库(如OSV、NVD)自动比对。
零信任前端架构Checklist
认证与授权
- Access Token有效期≤5分钟
- Refresh Token启用轮换机制
- Token存储在HttpOnly + Secure + SameSite=Strict的Cookie中
- 启用DPoP Token绑定
- 每个API请求携带唯一Request ID和时间戳
- 实现Token盗窃检测(Refresh Token重用检测)
运行时保护
- CSP策略设置为
default-src 'none'白名单模式 - 启用
strict-dynamic替代域名白名单 - 配置Trusted Types策略
- 所有CDN资源配置SRI
- 禁用
eval()、new Function()、document.write() - 配置CSP上报端点监控违规
微前端安全
- 子应用运行在JS沙箱中
- 跨应用通信使用签名验证
- 子应用权限矩阵已定义并强制执行
- 子应用样式隔离(Shadow DOM或CSS Modules)
- 子应用不可直接访问主应用Cookie/Token
供应链安全
-
npm audit集成到CI流水线 - lockfile完整性校验(lockfile-lint)
- 禁用自动执行install脚本(
--ignore-scripts) - 每次构建生成SBOM
- SBOM与漏洞数据库自动比对
- 依赖变更需要Code Review审批
监控与响应
- CSP违规上报实时告警
- Token异常使用检测(多地登录、频率异常)
- 前端异常监控集成安全事件
- 制定Token泄露应急响应流程
- 定期进行红队演练
零信任不是产品,而是一种安全思维方式。 它要求我们重新审视前端应用中的每一个信任假设,将"默认信任"转变为"默认拒绝,显式允许"。在2026年的威胁环境下,这不再是可选项,而是必选项。
本站提供浏览器本地工具,免注册即可试用 →