Vue项目在部署上线后,前端暴露在公网环境中的攻击面比很多人想象的要大。路由懒加载、数据持久化、第三方依赖、接口调用、DOM渲染这些环节,任何一个没有做收敛,都可能被利用来窃取令牌、注入脚本或者直接拖垮页面。全局安全防护不是装几个插件就完事,而是要在构建、运行时、数据层和网络层同时做约束。下面直接拆解可落地的配置方案。

路由层强制鉴权与异常兜底

很多Vue应用把权限判断写在组件内部,这会导致页面闪烁和逻辑分散。更稳妥的做法是在路由守卫中统一处理。首先在router实例上挂载全局前置守卫,每次跳转前校验本地令牌是否存在、是否过期。令牌过期判断不能只看有没有值,要解析JWT中的exp字段,和当前时间戳对比。如果过期,立即清理本地存储并重定向到登录页,同时在query中带上原始目标路径,方便登录后回跳。

router.beforeEach((to, from, next) => {
  const token = localStorage.getItem('access_token')
  if (to.meta.public) {
    next()
    return
  }
  if (!token) {
    next({ path: '/login', query: { redirect: to.fullPath } })
    return
  }
  try {
    const payload = JSON.parse(atob(token.split('.')[1]))
    const now = Math.floor(Date.now() / 1000)
    if (payload.exp && payload.exp < now) {
      localStorage.removeItem('access_token')
      next({ path: '/login', query: { redirect: to.fullPath } })
      return
    }
  } catch (e) {
    localStorage.removeItem('access_token')
    next({ path: '/login' })
    return
  }
  next()
})

除了前置守卫,还要加一个全局后置钩子,用来在路由跳转异常时兜底。比如动态路由匹配失败、组件加载失败,统一重定向到404页面,避免白屏。同时把错误信息上报到监控系统,方便追踪是权限配置遗漏还是资源加载问题。

HTTP请求层统一加签名与防重放

前端请求拦截器是安全防护的第二道关口。不要在业务代码里到处手动加token,要在axios实例中统一处理。除了Authorization头,还应该加入时间戳和随机数,配合后端做防重放校验。具体做法是在请求拦截器中生成nonce和timestamp,用与后端约定的密钥做一次摘要签名,放到自定义请求头里。这样即使请求被中间人截获,签名和时间窗口也能限制重放攻击的窗口期。

http.interceptors.request.use(config => {
  const token = localStorage.getItem('access_token')
  const timestamp = Date.now().toString()
  const nonce = Math.random().toString(36).slice(2) + Date.now().toString(36)
  const signStr = `${config.method}${config.url}${timestamp}${nonce}`
  const sign = CryptoJS.HmacSHA256(signStr, 'your-secret-key').toString()
  config.headers['X-Timestamp'] = timestamp
  config.headers['X-Nonce'] = nonce
  config.headers['X-Sign'] = sign
  if (token) {
    config.headers.Authorization = `Bearer ${token}`
  }
  return config
})

响应拦截器里要集中处理401、403和5xx错误。401时不要直接跳转,先判断当前路由是否已经在登录页,避免死循环。403提示权限不足,5xx做统一降级提示。另外,对于敏感接口的响应数据,不要直接透传到组件,可以在拦截器里做一次字段白名单过滤,防止后端误返回多余字段被前端缓存。

XSS防护从模板到富文本全链路

Vue默认的插值语法会自动转义HTML,这是第一层基础防护。但很多项目里会用到v-html渲染富文本,这就打开了XSS注入的口子。全局安全配置必须对v-html的使用做收口。不要在组件里直接调用v-html,而是封装一个安全渲染函数,内部先用DOMPurify对内容做白名单过滤,只允许安全的标签和属性通过。把DOMPurify配置成全局过滤器或者自定义指令,强制业务方走这个通道。

import DOMPurify from 'dompurify'

Vue.directive('safe-html', {
  inserted(el, binding) {
    const clean = DOMPurify.sanitize(binding.value, {
      ALLOWED_TAGS: ['p', 'br', 'strong', 'em', 'ul', 'ol', 'li', 'a', 'img'],
      ALLOWED_ATTR: ['href', 'src', 'alt', 'title', 'target']
    })
    el.innerHTML = clean
  }
})

另外,要防止用户输入的内容在属性值中逃逸。Vue的v-bind绑定属性时,如果属性值来自用户输入,必须做一次转义。特别是href和src,要校验协议头,只允许http、https和相对路径,javascript:、data:text/html这类协议直接拦截。可以在全局混入一个方法,统一处理URL安全校验。

依赖与构建层安全收敛

前端安全漏洞很大比例来自第三方依赖。package.json里锁定的版本可能已经包含已知漏洞。每次构建前应该跑一次npm audit或者用更细粒度的依赖检查工具,把高危漏洞的包升级或替换。对于不再维护的依赖,要评估迁移成本。构建配置里关闭source-map的上传,生产环境不要生成完整的.map文件,否则源码逻辑和注释会直接暴露。如果必须保留source-map用于线上排错,要配置成只上传到内部错误监控平台,而不是随静态资源一起发布。

Vue CLI和Vite都支持构建时环境变量注入。不要把敏感配置写在前端代码里,比如第三方服务的secret、加密密钥、内部接口地址。这些内容即使经过混淆,也能被逆向还原。正确做法是使用构建时变量注入,只注入非敏感的环境标识,真正的密钥放在后端,前端通过一次性令牌换取临时凭证。

本地存储与状态管理加固

localStorage和sessionStorage是XSS攻击的重点目标。不要把高价值令牌长期放在localStorage里。如果业务允许,优先使用httpOnly cookie承载会话令牌,前端无法通过脚本读取,能显著降低XSS窃取令牌的风险。如果必须用localStorage,要对令牌做二次加密存储,并且设置较短的过期时间。Vuex或Pinia中的状态不要持久化敏感数据,只存用户展示层信息。持久化插件要配置白名单,避免把整个store都写入本地。

对于需要记住用户登录状态的场景,可以把刷新令牌和访问令牌分离。访问令牌放在内存中,刷新令牌放在httpOnly cookie里,页面刷新时用刷新令牌静默换取新的访问令牌。这样即使XSS注入成功,攻击者拿到的也只是短时效的访问令牌。

CSP与响应头配置

内容安全策略是前端最后一道系统性防线。在index.html的meta标签中配置CSP,限制脚本、样式、图片、字体的加载来源。对于Vue应用,script-src要包含self和必要的CDN域名,style-src要允许unsafe-inline,因为Vue组件样式在开发模式下会内联注入。生产环境如果做了CSS提取,可以收紧style-src。connect-src限制接口请求域名,img-src限制图片加载来源。配置CSP后,即使有恶意脚本被注入,浏览器也会阻止其执行。


如果项目部署在Nginx等Web服务器后面,还可以在服务器配置里增加安全响应头。X-Content-Type-Options设置为nosniff,防止MIME类型嗅探。X-Frame-Options设置为DENY或SAMEORIGIN,防止点击劫持。Referrer-Policy限制来源信息泄露。这些响应头虽然不在Vue代码内配置,但属于全局安全防护的必要组成部分,需要前端在部署文档中明确要求运维配合。

错误处理与监控防信息泄露

生产环境的全局错误处理不能把堆栈信息直接展示给用户。Vue提供了errorHandler钩子,可以捕获组件渲染和生命周期中的错误。在errorHandler中,把错误详情上报到监控平台,同时给用户展示一个通用的错误提示页面或组件。不要在控制台输出完整堆栈,更不要把错误对象序列化后显示在页面上。接口错误响应中可能包含数据库字段、内部路径等信息,拦截器中要统一清洗后再展示。

Vue.config.errorHandler = (err, vm, info) => {
  const safeError = {
    message: err.message,
    stack: err.stack,
    component: vm?.$options?.name || 'anonymous',
    info
  }
  reportError(safeError)
  showToast('系统繁忙,请稍后重试')
}

全局安全防护的配置需要持续迭代。每次新增路由、接口、依赖时,都要检查是否破坏了已有的安全约束。把安全配置拆成独立模块,在入口文件中统一注册,避免散落在业务代码里。定期做一次前端安全自查,用自动化扫描工具跑一遍构建产物,检查是否有敏感信息泄露、不安全的协议引用、过期的依赖版本。安全配置不是一次性工作,而是和业务功能同步演进的基础设施。