简体中文 
 HBuilderX 3.6.8+ 支持,HarmonOS Next暂不支持安全网络
网络安全的问题很多:
当攻击者了解了你的服务器接收什么样的数据时,就可以冒名客户端,提交假数据来攻击你的服务器。
尤其当你的业务中涉及促销、返佣、激励视频等场景,非常容易被刷。薅羊毛已经是一个非常成熟的灰产,哪里有漏洞,哪里就有他们赚钱的机会。
DCloud面向开发者同时提供了端引擎uni-app 和 云引擎uniCloud,如今进一步升级,提供云端一体的安全网络的能力。
uni-app 连接 uniCloud 时,可以选择是否启动安全网络。它通过高安全的保护机制,解决了客户端受信和网络受信的问题,防止客户端伪造和通信内容抓包。
uni云端一体安全网络,提供了如下2个实用功能:
| 功能名称 | 功能描述 | 是否需要编码 | 具体文档 | 
|---|---|---|---|
| 客户端校验 | 指定合法的客户端,包括包名、证书、appid。不授信的客户端将无法请求服务器 | 只需配置,无需编码 | 文档 | 
| 网络传输数据加密 | 对网络传输的数据进行端到端加密,防止中间节点截获和篡改 | 需要在联网请求的代码里设定是否加密的参数 | 文档 | 
平台差异说明
| App | 微信小程序 | Web | 其他小程序 | 
|---|---|---|---|
| 3.6.8+ | 3.6.8+ | x | x | 
注意:安全网络只支持微信小程序和App。并且App的安全级别更高。安全网络仅在uni-app客户端连unicloud云函数/云对象生效,云函数url化场景下会直接跳过安全校验。
不管使用安全网络的哪个功能,首先要开通安全网络。App和微信小程序略有区别,但大体都要经过如下流程:
App和微信两个平台细化说明如下:
应用详情 --> 证书管理内填写安卓应用的包名、签名和iOS应用的bundleId。一个应用只能有一个发行证书配置,但是可以有多个开发证书配置

本质上安全网络绑定的是应用的appid、包名、证书等信息。只不过这些信息统一配置在dev中,在uniCloud的web控制台来选择。所以务必注意dev配置的正式版、测试版的包名、签名是否正确。

注意: 打包后生效。测试时需打包自定义基座。
opendb-app-client-key。新建后记得上传到uniCloud服务空间。参考文档:创建一个表
unicloud:secure:app-client-key:{appId}:{deviceId}:string遗留
安全网络在微信小程序上的实现,依赖了微信提供的一些用户级的凭据。所以需要下载 uni-id-pages 和 uni-open-bridge,并在app.vue里初始化。
无论是处理加密请求还是需要进行验证客户端的云函数在处理微信小程序发起的请求时都必须依赖uni-id-common和uni-open-bridge-common
应用详情 --> 【名称待定】内填写微信小程序的appId。一个应用只能有一个发行配置,但是可以有多个开发配置

uni-id-pages这个插件是云端一体的登录插件,其实安全网络只需要其中的uni-id-co云对象。插件中前端登录页面是否使用由开发者自己根据业务决定。
安全网络在微信小程序上依赖了微信的 access_token、session_key、encrypt_key等凭据。这些凭据需要uni-open-bridge统一接管。
登陆微信公众平台https://mp.weixin.qq.com/,获取微信小程序的固定凭据 appid 和 secret,配置到 uni-id-config
// uniCloud/cloudfunctions/common/uni-config-center/uni-id/config.json
{
  "dcloudAppid": "__UNI__xxxxxx", // 在项目的 manifest.json 中
  "mp-weixin": {
    "tokenExpiresIn": 259200,
    "oauth": {
      "weixin": {
        "appid": "", // 微信公众平台申请的小程序 appid
        "appsecret": "" // 微信公众平台申请的小程序 secret
      }
    }
  }
}
配置 uni-open-bridge 定时任务,定时从微信服务器获取 access_token 并保存到Redis或数据库
// uniCloud/cloudfunctions/common/uni-config-center/uni-open-bridge/config.json
{
  "schedule": {
    "__UNI__xxxxxx": { // dcloudAppid, 需要和 `uni-config-center` uni-id中的配置一致
      "enable": true, // 任务全局开关,优先级最高
      "weixin-mp": { // 平台,目前仅支持 微信小程序、微信 H5,详情参见 https://uniapp.dcloud.net.cn/uniCloud/uni-open-bridge#platform
        "enable": true, // 当前平台任务开关
        "tasks": ["accessToken"] // 要执行的任务,微信小程序支持 accessToken
      }
    }
  },
  "ipWhiteList": ["0.0.0.0"] // 用于 URL化后 http 调用的服务器IP白名单,即指定ip的服务器才可以访问URL化后的`uni-open-bridge云对象
}
注意:拷贝此文件内容时需要移除 注释。标准json不支持注释。在HBuilderX中可用多选//来批量移除注释。
如果项目之前已经使用过uni-id-pages和uni-open-bridge,则上述步骤可省略。
onLaunch(能保证在调用安全网络请求前调用完成的时机均可) 调用 uniCloud.initSecureNetworkByWeixin(),进行安全网络请求前的握手操作,关于此接口详细描述见:uniCloud.initSecureNetworkByWeixin对于使用uni-id-pages的项目App.vue页面需要补充如下代码,不使用uni-id-pages的开发者需要按照此文档进行操作:不使用uni-id-pages时如何使用微信小程序安全网络
<script>
  export default {
    onLaunch: async function() {
      // #ifdef MP-WEIXIN
      const userInfo = uniCloud.getCurrentUserInfo()
      const callLoginByWeixin = userInfo.tokenExpired < Date.now() // 用户为未登录状态时调用一次微信登录
      await uniCloud.initSecureNetworkByWeixin({
        callLoginByWeixin: callLoginByWeixin
      })
      // #endif
    }
  }
</script>
注意:此方法内部会调用一次微信小程序的login,然后使用返回的code调用uni-id-co的secureNetworkHandshakeByWeixin方法(新增于uni-id-pages 1.0.27)
云端一体安全网络模块
新增于HBuilderX 3.7.7
对于部分已有用户体系,不希望引入uni-id-pages的开发者,可使用如下方案来使用微信小程序安全网络。
客户端需调整为在调用安全网络请求前使用uniCloud.initSecureNetworkByWeixin方法传入用户openid
// app.js
<script>
  export default {
    onLaunch: async function() {
      // #ifdef MP-WEIXIN
      // 调用自有服务、云函数进行微信登录或以其他方式获取openid
      const openid = 'xxx'
      await uniCloud.initSecureNetworkByWeixin({
        openid: openid
      })
      // #endif
    }
  }
</script>
云函数内需要调用uni-open-bridge-common将微信应用级access_token及登录用户的session_key同步到uniCloud数据库,以便安全网络云端能从微信获取解密用参数。
uni-open-bridge-common使用注意事项
uni-open-bridge-common仍依赖uni-id的配置获取微信小程序appid,如何配置请参考:uni-id config
如果从自有服务器同步access_token和session_key到uniCloud数据库内可以使用uni-open-bridge提供的url化调用方式,请参考:uni-open-bridge url化调用
如果从云函数内同步access_token和session_key给安全网络按如下文档进行
云函数存储微信小程序应用级access_token
开发者应在自己云函数获取access_token,传递给uni-open-bridge-common进行存储,以供安全网络使用。或使用uni-open-bridge云函数的定时任务自动获取access_token,参考:应用级access_token
微信access_token有一些特性,处理不好容易出现bug,请务必详读微信公众平台关于access_token的说明(微信小程序、公众号逻辑一样):微信公众平台access_token
await require('uni-open-bridge-common').setAccessToken({
  dcloudAppid: '__UNI__xxx',
  platform: 'weixin-mp'
}, {
  access_token: accessToken
}, 7200) // 新获取的accessToken有效期是2小时
云函数存储微信用户session_key
开发者应在用户调用微信登录使将openid、session_key传递给uni-open-bridge-common进行存储,以供安全网络使用
await require('uni-open-bridge-common').setSessionKey({
  dcloudAppid: '__UNI__xxx',
  openid,
  platform: 'weixin-mp'
}, {
  session_key: sessionKey
}, 30 * 24 * 60 * 60) // session_key并没有固定有效期,暂以30天进行存储
新增于 HBuilderX 3.6.8
客户端验证用于确保发起请求的客户端的真实性,只有指定的客户端才能访问云函数。
客户端验证功能全流程由uniCloud进行控制,开启此功能后将直接拒绝无权访问的客户端调用云函数。
开发者首先在uniCloud控制台的安全网络页面选择哪些客户端应用可以与uniCloud建立安全网络,然后在页面上单独开启客户端强制校验。

切记
开启客户端验证功能后,默认对所有云函数启用安全验证,仅在安全网络应用列表内配置的应用允许访问云函数。但有时,会有排除某个云函数的需求。比如指定的云函数校验或不校验客户端身份,这个云函数可能要url供外部访问。
所以uni安全网络提供了自定义客户端校验规则。
在 uniCloud web控制台 的 安全网络页面,可打开自定义规则开关。开启自定义规则后,将不再执行全体云函数统一的客户端校验。改为,未被规则匹配到的云函数不进行客户端验证。
如下示例为一个简单的自定义规则配置:
{
  "verify-client": [{ // 可访问云函数verify-client的应用列表
    "appId": "__UNI_xxxx",  // 客户端的DCloud AppId
    "platform": "android",  // 客户端平台,有三个可选值:android(安卓)、ios(iOS)、mp-weixin(微信小程序)。注意是小写
    "version": "production" // 客户端版本,有两个可选值:production(正式版)、development(测试版)。注意是小写
  }]
}
上述规则意味着,这个名为verify-client的云函数,只有应用的appid为__UNI_xxxx、platform即客户端平台为android、且为正式版,才能访问这个云函数;其他客户端无法访问这个云函数;且除了verify-client外,其他云函数可以被任何客户端随意访问。
如果想增加更多规则,在json中添加更多数组,每个数组是一条规则。比如想配置ios平台,就追加一个数组。
注意:自定义规则是一个标准的json,不支持编写注释,如需拷贝示例代码请务必去除注释。
云函数名为json的key,但可以写多个云函数。包括以下几种写法:
verify-clientverify-client1,verify-client2,注意逗号为英文逗号*,代表所有云函数当匹配一个云函数的自定义规则配置时,优先使用单个云函数名的配置,其次是多个云函数名的配置,最后是通配符的配置。如果都未匹配到则不对此云函数执行验证客户端的逻辑。
注意
uni-clientDB作为云函数名除了校验客户端身份外,uni安全网络还提供了网络上下行传输数据的加密。
此时需要在客户端和服务器都要编写代码,倒不需要写具体的加密解密算法,而是需要在客户端指定哪些请求、哪些数据要加密,而在云端要校验客户端是否指定了正确的条件。
加密解密使用的是国际通行的高位AES算法。
具体写法如下:
客户端通过callFunction调用云函数时,加入secretType参数。
uniCloud.callFunction({
  name: 'collection',
  data: {
    name: 'user'
  },
  secretType: 'both' //both指上下行数据都加密,具体见下面的secretType章节
}).then(res => {
  const {
    errCode,
    errMsg
  } = res.result
  if(errCode) {
    uni.showModal({
      content: errMsg,
      showCancel: false
    })
  }
})
也就是每个callFunction请求,都可以指定是否加密,以及是对上行数据还是下行数据加密。
注意
res.result内包含具体错误客户端通过importObject调用云对象时,通过secretMethods参数来配置每个方法调用时是否加密。
const obj = uniCloud.importObject('object-name', {
  secretMethods: {'login':'both'} // 对login方法设置为上下行的数据均要加密。也支持配置所有方法设置加密,参见下面的 secretMethods 说明
})
obj.test().then(()=>{}).catch(err => {
  uni.showModal({
    content: err.errMsg || err.message,
    showCancel: false
  })
})
也就是云对象导入时配置某个方法的请求是否要加密,以及是对上行数据还是下行数据加密。那么在客户端调用云对象的相应方法时会自动按这个配置执行。
clientDB暂不支持网络数据加密传输。但仍可以使用客户端身份校验。
secretType 属性说明
| 值 | 描述 | 
|---|---|
| none | 上下行都不加密,默认值 | 
| request | 只加密客户端请求时的上行数据,服务器下发数据不加密 | 
| response | 客户端请求时不加密数据,只加密服务器下发的数据 | 
| both | 客户端和服务器上行下行数据都加密数据 | 
secretMethods 属性说明
secretMethods 是云对象中指定需要加密的方法名。
secretMethods: {'*':'both'}secretMethods: {'login':'both'},指定 login 方法的 secretType 为 both方法级配置优先级最高,例如 secretMethods: {'*':'response', 'login':'both'},login 的 both 覆盖了 '*':'response'
注意
uni云端一体安全网络,已经在底层封装好了复杂的安全相关的算法。开发者只需关心对哪些请求、哪些数据进行加密。
不管是客户端接收云端数据、还是云端接受客户端数据,开发者的代码拿到的数据永远都是解密后的数据。
但云端有一个注意事项:为了避免客户端伪造secretType获取服务器敏感数据,应以服务器端为准,如果客户端携带的 secretType 不符合要求应拒绝响应数据。
示例代码如下:
在云函数的context中有secretType。如果这个云函数的返回数据必须加密,那么应该使用如下方式校验客户端的请求是否合法。
exports.main = async (event, context) => {
  const secretType = context.secretType
  // secretType 是客户端调用 uniCloud.callFunction 传递的参数 secretType
  if (secretType !== 'both' && secretType !== 'response') {
    throw new Error('secretType invalid') // 拒绝返回有效数据
  }
}
在云对象的this中有secretType。如果这个云对象的reward方法的返回数据必须加密,那么应该使用如下方式校验客户端的请求是否合法。
module.exports = {
  async _before() {
    const methodName = this.getMethodName()
    const clientInfo = this.getClientInfo()
    const secretType = clientInfo.secretType
    // methodName 是客户端调用的方法名
    // secretType 是客户端调用 uniCloud.importObject 传递的参数 secretMethods
    if (methodName === 'reward' && (secretType !== 'both' && secretType !== 'response')) {
      throw new Error('secretType invalid') // 拒绝返回有效数据
    }
  }
}
自HBuilderX 3.6.9起安全网络使用以下错误规范,此前错误码未统一不建议使用。安全网络使用的错误规范参考:错误规范
| 错误码 | 错误详情 | 
|---|---|
| 10001 | App平台不支持小程序sdk和标准基座 | 
| 10003 | App平台appKey为空,请尝试重新打包 | 
| 10009 | App平台加密失败 | 
| 10010 | App平台解密失败 | 
| 20101 | 客户端信息不在允许访问的客户端信息列表内,如果云端调整配置需要重新打包/运行客户端才会生效 | 
| 20102 | 微信小程序平台获取加密key失败 | 
| 错误码 | 错误详情 | 
|---|---|
| 50000 | 系统错误 | 
| 60101 | 客户端AppId缺失 | 
| 60102 | 客户端DeviceId缺失 | 
| 60103 | 客户端OsName缺失 | 
| 60200 | 服务空间未开通安全网络 | 
| 60201 | 当前应用AppId尚未关联到uniCloud安全网络 | 
| 60202 | 当前应用AppId已关联到uniCloud安全网络,但是当前客户端平台未关联 | 
| 70001 | 客户端时间错误 | 
| 70002 | 客户端AppId未通过校验 | 
| 70003 | 客户端DeviceId未通过校验 | 
| 70004 | 客户端信息未通过校验 | 
| 70005 | 用户账号不存在 | 
| 70006 | 未找到用户openid | 
| 70007 | 获取加密key失败 | 
| 70008 | 客户端验证所需的签名缺失,详见下方详细说明 | 
| 70009 | 未找到加密key | 
| 70010 | 模拟器或root设备禁止访问 | 
微信小程序加解密时还会使用uni-id-common的checkToken方法,相关错误码参考:uni-id错误码
如出现预期外的70008错误请按照以下步骤排查