小程序的安全与管控

100 阅读3分钟

传统web网页的缺点和小程序的进击

JS的灵活性以及浏览器接口的丰富性导致前端的漏洞有很多,为了管控这些危险的因素,小程序采用了沙箱环境,并且不支持动态载入脚本(解决XSS),同时建立了自己的审核机制。
沙箱环境:提供一个纯JavaScript的解释执行环境,这个环境没有浏览器的相关接口,所以也不用担心操作DOM,跳转等问题。

这个环境在IOS下是内置的JavaScriptCore框架,在安卓下是JsCore环境。

有关小程序业务逻辑的代码,都会在JS的解释引擎中创建一个单独的线程去执行。 界面渲染相关的任务都在webView线程中(开发者就没法直接操作 DOM => 没法动态去更改界面或者抓取页面数据)。

小程序中的网络通信

每个微信小程序需要事先设置一个通讯域名,小程序只可以跟指定的域名与进行网络通信,包括普通 HTTPS 请求、上传文件、下载文件和 WebSocket 通信。

小程序会对证书进行校验

  • HTTPS 证书必须有效;

    • 证书必须被系统信任,即根证书被已系统内置
    • 部署 SSL 证书的网站域名必须与证书颁发的域名一致
    • 证书必须在有效期内
    • 证书的信任链必须完整(需要服务器配置)

小程序的登录机制

CSRF 跨站请求攻击:利用了 web 中用户身份验证的一个漏洞,简单的身份验证只能保证请求发自某个用户的浏览器,却不能保证请求本身是用户自愿发出的。通常的罪魁祸首则是浏览器的 cookie 登录态。

解决方法:

  1. 检查Referer 开发者可以通过检查Referer字段的值来确保请求是从期望的来源页面发起的。在后端处理请求时,对比Referer字段的值与期望的来源页面的URL,以确定请求是否合法。如果Referer字段的值与当前页面的URL不匹配,可能暗示这是一个潜在的CSRF攻击
  2. 使用token: 在小程序中调用wx.login(),能拿到一个code作为用户登录凭证(有效期五分钟)。在开发者服务器后台,开发者可使用code换取openidsession_key等信息(code只能使用一次)。微信服务器为了确保拿code过来换取身份信息的人就是刚刚对应的小程序开发者,到微信服务器的请求要同时带上AppIdAppSecret

以下是一个简单的登录过程

export const login = () =>
  // 返回一个Promise对象,用于处理异步操作
  new Promise((res, rej) => {
    // 调用微信小程序的登录接口wx.login,设置超时时间为30秒
    wx.login({
      timeout: 30000,
    }).then(async ({ code }) => {
      try {
        // 从本地存储中获取上一次的登录凭证或使用当前获取的code
        let lastCode = wx.getStorageSync(WXCODE) || code;
        // 获取登录时间戳logintimestamp
        const logintimestamp = wx.getStorageSync(LOGIN_TIME_STAMP);
        const now = new Date().getTime();
        // 检查登录时间戳是否存在或是否超过12小时,如果是则需要重新设置登录时间戳并刷新code
        if (!logintimestamp || now - logintimestamp * 1 >= OUTDATE_TIME) {
          wx.setStorageSync(LOGIN_TIME_STAMP, now);
          lastCode = code;
        }
        // 将最终确定的登录凭证code存储到本地存储中
        wx.setStorageSync(WXCODE, lastCode);
        // 调用后端服务loginServer,传入最终的登录凭证lastCode
        const { data } = await loginServer(lastCode); // 获取用户数据,返回用户信息,openId等
        // 处理后端返回的数据
        if (data && data.code === 0) {
          // 如果后端返回数据正常,组装最终的登录用户信息,并标记为已登录状态
          const result = { ...data.data, isLogin: true };
          // 将用户信息存储到本地
          wx.setStorageSync('userInfo', result);
          // 调用Promise的resolve方法,将结果返回
          res(result);
        } else {
          // 如果后端返回数据不正常,调用Promise的reject方法,将错误信息返回
          rej(data);
        }
      } catch (e) {
        // 捕获try块中的异常,调用Promise的reject方法,将异常信息返回
        rej(e);
      }
    });
  });

授权时序图.jpg

其他

小程序的全局配置