前端安全

零信任安全模型:永不信任,始终验证

2026年的前端安全形势比以往任何时候都更加严峻。传统的"城堡与护城河"安全模型——即信任内网、不信任外网——已经彻底失效。零信任安全模型应运而生,其核心原则只有一条:

永不信任,始终验证(Never Trust, Always Verify)

这意味着每一个请求、每一次交互、每一个数据包,无论来自何处,都必须经过严格的身份验证和授权检查。不存在"可信区域",不存在"隐式信任"。


传统边界安全 vs 零信任架构

维度 传统边界安全 零信任架构
信任模型 内网可信,外网不可信 所有请求均不可信
验证方式 一次性登录验证 每次请求独立验证
网络边界 依赖防火墙/VPN 无边界,身份即边界
攻击面 突破边界即可横向移动 每步都需要重新验证
适用于 传统单体应用 云原生/微服务/微前端
前端安全 假设前端在"安全区域" 前端是不可信终端

传统模型最大的问题在于:一旦攻击者突破了网络边界,就可以在内网自由横向移动。而零信任架构下,即使攻击者拿到了某个Token,也无法长期冒充用户,因为每个操作都需要重新验证。


前端零信任:每个API请求都独立验证

前端是零信任架构中最关键的一环——因为前端代码运行在用户浏览器中,是完全不可控的环境。攻击者可以修改前端代码、拦截网络请求、篡改本地存储。

核心原则

  1. 无隐式信任:不因为用户已登录就信任后续所有请求
  2. 最小权限:每个请求只获取完成该操作所需的最小权限
  3. 持续验证:Token短期有效,频繁刷新
  4. 假设已被攻破:设计时假设攻击者已控制前端

请求级验证示例

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 Types
  • default-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年的威胁环境下,这不再是可选项,而是必选项。

本站提供浏览器本地工具,免注册即可试用 →

#零信任#前端安全#Token验证#CSP#微前端安全