安全与性能为什么要一起治理

很多团队把安全和性能当成两件事:安全是上线前过一遍审核,性能是用户吐槽了再优化。但在小程序里它们高度重叠——一个不校验的接口既是安全漏洞,也可能因为重复请求拖慢体验;一个臃肿的主包既拖慢启动,也把更多代码暴露在前端,增加被反编译分析的面。一起治理,成本最低。

小程序跑在别人的客户端里,前端代码本质上是「公开的」,真正的防线必须落在后端。
本篇聚焦的三件事
  • 前端敏感数据不落本地缓存,鉴权交给服务端。
  • 接口签名、时间戳防重、越权校验一个不能少。
  • 体积主包尽量小,页面与重型资源走分包与 CDN。
小程序安全与性能配图:中央带锁孔的盾牌与右侧速度仪表盘上升曲线
安全是盾牌,性能是仪表盘——两者共同决定小程序能不能长期留住用户。

前端安全:数据不落地、鉴权不偷懒

小程序的 storage 是明文可读的,把 token、用户身份证号、订单金额写进去等于公开。正确做法是:登录态用短期令牌,刷新交给后端,前端只保存必要的标识;涉及金额、库存这类关键数字,永远以服务端返回为准,不要信任前端算好的结果。

// 不要把敏感字段整包塞进 storage
wx.setStorageSync('openid', openid);   // 仅放可公开的标识

// 登录态过期交由后端判断,前端不自行决定
wx.checkSession({
  success() { /* 会话仍有效 */ },
  fail() { doLogin(); }
});

另外要避免在前端硬编码任何后端密钥、第三方 AppSecret。这些一旦被反编译就能直接拿到,所有签名安全都形同虚设。密钥只存在服务端,前端只持有短时令牌。

接口安全:签名、防重与越权

前端到后端的请求可以被抓包重放。防的办法是:每个请求带上时间戳和随机串,服务端校验时间窗(比如 5 分钟内有效)、拒绝重复的随机串,再对参数做签名。这样即使请求被截获,也无法在过期后或重复时复用。

接口防护清单
  • 越权每次都从登录态取用户身份,绝不信前端传来的 userId。
  • 防重随机串 + 时间戳,服务端短时去重。
  • 签名关键参数参与签名,密钥在服务端。
  • HTTPS全站强制 HTTPS,关掉 HTTP 兜底。

越权是最容易被忽略的一类:很多漏洞不是密码被破,而是用户 A 把订单 id 改成 B 的 id,接口就把 B 的数据返回了。服务端必须校验「这条资源属于当前登录用户」,而不只是校验「请求合法」。

包体积:主包与分包的取舍

小程序有严格的体积限制,主包过大会直接拖慢启动、影响审核。原则是:主包只放首页和公共能力,把二级、三级页面按业务拆成分包,用户走到时才下载。重型图片、字体、富文本一律走 CDN,不打进包里。

// app.json 中划分主包与分包
{
  pages: ['pages/index/index', 'pages/cart/cart'],
  subpackages: [
    { root: 'pkg_user', pages: ['pages/profile/profile'] },
    { root: 'pkg_order', pages: ['pages/list/list', 'pages/detail/detail'] }
  ]
}

还可以开启分包预下载:在用户可能进入订单页的关键路径上,提前静默下载对应分包,等他真正点击时页面已经就绪。注意预下载要克制,预下太多反而消耗用户流量、抵消了拆分的收益。

渲染性能:setData 与列表复用

小程序渲染性能的头号杀手是 setData。它每次都要把数据从逻辑层传到渲染层,频繁、大数据量的 setData 会明显卡顿。优化方向是:只传变化的字段、不要把整页数据反复 setData、长列表用分页或虚拟滚动而不是一次渲染上千条。

// 只更新需要变化的字段,而不是整对象
this.setData({
  'list[3].price': newPrice,
  loading: false
});

列表页优先用 recycle-list 或自实现的按需渲染,让屏幕外的节点被回收。图片记得加 lazy-load,非首屏的接口请求尽量延后到页面 onReady 之后,给首屏渲染让路。