前言

最近不知道干什么,简单学学偏向开发的知识吧,小程序的漏洞还是比较多的,因为小程序访问和注册都需要通过微信,这个门槛使得小程序可以免受一些漏扫攻击,之前自己对小程序安全的认识还是,bp+Proxifier抓包,反编译看源码找接口,反正还是web那一套,个人感觉其实够用了,但是耐不住好奇心
借此希望这篇文章不单单是简单的知识记录,希望可以学到一些新的编程设计思想,说不定学安卓的时候就能用上了那

关于前后端分离的一些认识

在学习小程序开发的时候,我个人感觉跟vue很像,都是前端路由,后端接口服务,结合我之前对web开发的学习,现在总结一些对前后端分离的一些认识
前后端分离有三个阶段
第一阶段是前后端完全不分离,如(PHP / JSP / ASPX)
访问路径通常为相对文件路径,访问主体是一个文件,代码如同JS一样嵌在或者包含在页面中,页面完全由后端渲染

第二阶段是代码层分离,如(Spring Boot + templates / ThinkPHP)
访问主体依旧是一个文件,称为模板文件,但是后端逻辑被抽象成一个一个接口,方便项目管理,从浏览器这个视角来看其实没什么变化

第三阶段是架构层独立,如(Vue / 小程序)
这个时候的网站可以称得上是应用程序,初始化时需要先加载所有的 JS 代码和页面,前端框架在浏览器端独立运行

前两个阶段返回包是html或者html+AJAX获取的后端 API 请求数据,称为服务端渲染(SSR)
最后一个阶段返回包就只剩下后端 API 请求数据,称为客户端渲染(CSR)

但是需要注意的是小程序是不兼容script标签也没dom,所以xss就别想了

小程序项目目录结构

因为小程序前端和vue基本差不多,并且我又不打算搞小程序开发,所以就打算简单写写了

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
├── app.js                                 //加载页面前的预启动逻辑
├── app.json //全局配置,如页面路径列表,首页,底部 tab 栏等
├── app.wxss //全局样式
├── pages
│ ├── index
│ │ ├── index.js
│ │ ├── index.wxml
│ │ └── index.wxss
│ ├── logs
│ ├── logs.js //页面逻辑
│ ├── logs.json //页面配置
│ ├── logs.wxml //模板文件,跟vue标签差不多
│ └── logs.wxss //页面样式
├── project.config.json //项目配置
├── project.private.config.json

第一次访问后的调用顺序

  1. app.js
    └─ App() 注册
    └─ onLaunch(options) ← 全局最先执行
    └─ onShow(options)

  2. app.json
    └─ 解析 pages[]
    └─ 确定首页(pages[0])

  3. 首页 Page 初始化
    └─ Page() 注册
    └─ onLoad(query)
    └─ onShow()
    └─ onReady()

    page

    <view bind:tap="viewTap">{{num}}</view>

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    Page({
    onLoad: function (options) {},//生命周期函数
    onReady: function () {},
    data: {//初始数据
    num: 0,
    },
    viewTap: function () {//自定义触发函数
    this.setData({
    num: this.data.num + 1,
    });
    },
    });

    小程序是双线程模型(感觉叫双进程模型更合适),页面渲染相关的数据只能通过 setData 修改,直接赋值只会修改JS 内存里的值,不会触发视图更新

    1
    2
    3
    逻辑层(JS)
    ↕ setData 通信
    视图层(WXML / WXSS)

    与后端交互

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    wx.request({
    url: 'https://api.example.com/user/list',
    method: 'GET',
    data: {},
    success(res) {
    console.log(res.data)
    },
    fail(err) {
    wx.navigateTo({//跳转页面
    url: '/pages/404',
    })
    }
    })

    小程序手机号一键登录

老版本登录

小程序前端回调生成用户登录凭证

1
2
3
4
5
6
7
wx.login({
success (res) {
if (res.code) {//前端回调获取,用户登录凭证(有效期五分钟)
wx.request({//发给后端
url: 'https://example.com/onLogin',
data: {
code: res.code}})}}})

后端根据用户登录凭证和小程序密钥获取用户唯一标识和会话密钥

1
2
3
4
5
6
7
8
9
10
11
12
13
14
//GET https://api.weixin.qq.com/sns/jscode2session?appid=APPID&secret=APPsecret&js_code=code&grant_type="uthorization_code"
//AppID(小程序ID) wx93ae32e40909dff4

//AppSecret(小程序密钥) 03dd2a224c1be18a815e3d5fa077596d

//code(用户登录凭证)
//返回
{
"openid": "oVbhN4wjEtFYYwHAdQP6wrQWuO0s",//用户唯一标识
"session_key": "D1nlQwrAIhMuBVqI/fH37A==",//会话密钥
"unionid": "xxxxx",//同一个微信开放平台下的不同应用,UnionID是相同,适合多应用统一登录
"errcode": 0,
"errmsg": "xxxxx"
}

从基础库2.21.2之前,小程序前端button在用户点击后,前端回调获取到手机号密文信息,和对应的iv

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<button open-type="getPhoneNumber" bindgetphonenumber="getPhoneNumber"></button>
Page({
getPhoneNumber (e) {
console.log(e.detail.errMsg)
console.log(e.detail.iv) //加密算法的初始向量
console.log(e.detail.encryptedData) //用户信息密文,加密格式为AES-128-CBC,密钥为session_key
//发送给后端,后端解密
}
})
//encryptedData明文格式如下
{
"phoneNumber": "13580006666",
"purePhoneNumber": "13580006666",
"countryCode": "86",
"watermark":
{
"appid":"APPID",
"timestamp": TIMESTAMP
}
}

新版本登录

新版本登录在获取用户手机号信息的方式发生了改变,杜绝了因AppSecret泄露导致的任意用户登录
从基础库2.21.2之后,小程序前端button在用户点击后,前端回调获取到手机号动态令牌

1
2
3
4
5
6
7
8
9
<button open-type="getPhoneNumber" bindgetphonenumber="getPhoneNumber"></button>

Page({
getPhoneNumber (e) {
console.log(e.detail.code) // 手机号动态令牌
console.log(e.detail.errMsg) // 回调信息(成功失败都会返回)
console.log(e.detail.errno) // 错误码(失败时返回)
}
})

使用小程序密钥获取后台接口调用凭据

1
2
3
4
5
//GET https://api.weixin.qq.com/cgi-bin/token?grant_type="client_credential"&appid=APPID&secret=APPSECRET
{
"access_token": "ACCESS_TOKEN",//后台接口调用凭据
"expires_in": 7200
}

使用后台接口调用凭据和前面获取到的手机号动态令牌,获取手机号

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
//POST https://api.weixin.qq.com/wxa/business/getuserphonenumber?access_token=ACCESS_TOKEN
{
"code": "e31968a7f94cc5ee25fafc2aef2773f0bb8c3937b22520eb8ee345274d00c144"// 手机号动态令牌
}
//返回示例
{
"errcode": 0,
"errmsg": "ok",
"phone_info": {
"phoneNumber": "13580006666",
"purePhoneNumber": "13580006666",
"countryCode": 86,
"watermark": {
"timestamp": 1637744274,
"appid": "xxxx"
}
}
}