Vue 安全实践:敏感状态保护与错误边界处理

敏感状态的客户端保护

在 Vue 应用中,敏感状态(如用户凭证、权限信息等)的保护至关重要。以下是几种关键保护策略:

1. 避免直接存储敏感数据

// 不推荐
data() {
  return {
    userToken: localStorage.getItem('token'), // 敏感数据直接存储
    creditCardNumber: null
  }
}

// 推荐做法
export default {
  data() {
    return {
      auth: null // 仅存储认证状态
    }
  },
  methods: {
    async login(credentials) {
      const response = await authService.login(credentials)
      this.auth = { isAuthenticated: true }
      // 不存储token,使用httpOnly cookie
    }
  }
}

实践建议

  • 使用 HTTP-only cookies 存储会话令牌
  • 敏感数据应尽量保存在服务端,前端只保留必要的最小状态
  • 考虑使用 Web Crypto API 对必须存储在客户端的敏感数据进行加密

2. 响应式数据的保护

import { shallowRef, markRaw } from 'vue'

export default {
  setup() {
    const sensitiveData = shallowRef(markRaw({
      secretKey: 'should-not-be-reactive'
    }))
    
    return { sensitiveData }
  }
}

为什么有效

  • shallowRef 只对 value 的引用变化做出响应
  • markRaw 标记对象永远不会被转为响应式代理

3. 权限控制模式

图1

实现示例

// 权限指令
app.directive('permission', {
  mounted(el, binding) {
    const { hasPermission } = useAuth()
    if (!hasPermission(binding.value)) {
      el.parentNode?.removeChild(el)
    }
  }
})

// 使用方式
<button v-permission="'admin'">删除用户</button>

异步操作的错误边界处理

1. 全局错误处理

// main.js
app.config.errorHandler = (err, vm, info) => {
  console.error('全局错误:', err)
  sentry.captureException(err)
  // 显示友好错误界面
}

2. 错误边界组件

// ErrorBoundary.vue
<script setup>
import { ref, onErrorCaptured } from 'vue'

const error = ref(null)

onErrorCaptured((err, instance, info) => {
  error.value = err
  return false // 阻止错误继续向上传播
})
</script>

<template>
  <slot v-if="!error" />
  <div v-else class="error-fallback">
    <h3>抱歉,出了点问题</h3>
    <button @click="error = null">重试</button>
  </div>
</template>

// 使用方式
<ErrorBoundary>
  <AsyncComponent />
</ErrorBoundary>

3. 异步操作的最佳实践

// 组合式函数封装
export function useSafeAsync(asyncFn) {
  const loading = ref(false)
  const error = ref(null)
  const data = ref(null)
  
  const execute = async (...args) => {
    try {
      loading.value = true
      error.value = null
      data.value = await asyncFn(...args)
    } catch (err) {
      error.value = err
      throw err // 可选择是否继续抛出
    } finally {
      loading.value = false
    }
  }
  
  return { loading, error, data, execute }
}

// 使用示例
const { loading, error, data, execute } = useSafeAsync(fetchUserData)
execute(userId)

实践建议

  1. 为所有异步操作添加超时控制
  2. 实现重试机制(指数退避算法)
  3. 区分可恢复错误和不可恢复错误
  4. 记录错误日志但不要暴露敏感信息

4. 错误状态管理

// 错误状态模块 (Vuex/Pinia)
export const useErrorStore = defineStore('error', {
  state: () => ({
    errors: [],
    unhandledError: null
  }),
  actions: {
    capture(error, context = {}) {
      this.errors.push({ error, timestamp: Date.now(), ...context })
      if (!context.handled) {
        this.unhandledError = error
      }
    },
    clearUnhandled() {
      this.unhandledError = null
    }
  }
})

// 在组件中使用
const errorStore = useErrorStore()
try {
  await riskyOperation()
} catch (err) {
  errorStore.capture(err, { 
    handled: true,
    component: 'UserProfile'
  })
}

安全与错误处理的综合策略

  1. 深度防御:在客户端、网络层和服务端都实施保护
  2. 最小权限:前端也应遵循最小权限原则
  3. 优雅降级:当错误发生时提供有意义的备选方案
  4. 监控报警:集成错误监控系统(如 Sentry)
  5. 安全审计:定期检查前端代码中的敏感数据处理

通过以上实践,可以显著提高 Vue 应用的安全性和健壮性,同时提供更好的用户体验。

评论已关闭