小程序版好友对战实战-wss部署与小程序用户登录时序


上一篇文章是对需求的分析,本次将逐渐进入代码阶段。本次主要的内容包括服务端wss的部署以及小程序端用户授权的时序及逻辑。


wss的配置与部署

微信小程序出于安全考虑,要求所有涉及到网络的操作,必须使用安全的网络请求,如https和wss,却使用的域名必须备案以及不能带端口号。详情请参考微信小程序官方文档

虽然在开发的过程中,微信提供的开发者工具可以忽略https和wss的校验,但程序上线时,就必须使用https和wss协议,所以,在这里,先来介绍下wss协议的部署。

WSS 是 Web Socket Secure 的简称, 它是 WebSocket 的加密版本. 我们知道 WebSocket 中的数据是不加密的, 但是不加密的数据很容易被别有用心的人窃取, 因此为了保护数据安全, 人们将 WebSocket 与 SSL 结合, 实现了安全的 WebSocket 通信, 即 WebSocket Secure.

所以说 WSS 是使用 SSL 进行加密了的 WebSocket 通信技术.

因为我们的对战答题功能对及时性要求比较高,传统的http无法满足要求,所以,我们使用WebSocket作为客户端与服务端,客户端与客户端之间的通讯。

要使用wss首先我们需要购买个域名证书,现在腾讯云和阿里云都有提供免费的域名证书。下面我已腾讯云为例,简单介绍下域名证书的购买。

进入腾讯云官网,在顶部导航中,找到云产品。如下图所示:

点击ssl证书管理。进入证书管理页面,

点击购买证书,进入下级页面,选择免费版

进入下级页面,填写证书基本信息

下一步,验证域名所有权。这里我选择手动DNS验证。点击确认申请。

点击查看证书详情,然后根据腾讯云提供的DNS信息,添加域名解析。

下图是域名解析填写的信息(前提是你要有个域名):

然后,回到腾讯云,点击如下图中的查询按钮:

当出现如下图所示的信息后,稍后几秒后,即可证书即可申请成功。

申请成功后,刷新证书详情页面,如下图,点击下载按钮,将证书下载下来。

将下载后的文件解压备用。

下面进入WebSocket服务端代码实现阶段。这里我使用.net平台开源的Socket框架SuperSocket。首先打开vs,新建一个控制台应用程序项目。

这里我们通过Nuget的方式引用SuperSocket。如下图所示:

分别安装上图中标注的包。安装完成后,然后将之前下载的证书拷贝过来。使用之前解压的文件里的iis文件夹里的证书文件。将iis文件夹中的证书文件拷贝到项目。然后右击证书文件,选择属性,进入属性设置页面。

在复制到输出目录中,选择始终复制。

控制台中的代码:

   

 1 static void Main(string[] args)
 2 
 3         {
 4 
 5             #region 证书配置
 6 
 7             var certConfig = new CertificateConfig();
 8 
 9             certConfig.FilePath = "ttt.vqicard.com.pfx";//证书路径
10 
11             certConfig.Password = "123123";//证书密码。申请证书时填写的密码。没填,则此处为空
12 
13             certConfig.KeyStorageFlags = X509KeyStorageFlags.UserKeySet;
14 
15             certConfig.ClientCertificateRequired = false;
16 
17             #endregion
18 
19             var ws = new WebSocketServer();
20 
21             var serverConfig = new ServerConfig();
22 
23             serverConfig.Security = "tls";
24 
25             serverConfig.Certificate = certConfig;
26 
27             serverConfig.Ip = IPAddress.Any.ToString();//绑定的ip
28 
29             serverConfig.Port = 2018;//监听的端口号。此处填写默认端口。由于我的服务器的443端口已经被占用,
30 
31             //所以,这里使用其他端口。因为微信不支持带端口的地址,所以,正式部署后,必须设置为443端口。
32 
33             ws.NewDataReceived += Ws_NewDataReceived;//接收到新数据的回调
34 
35             ws.NewMessageReceived += Ws_NewMessageReceived;//接收到新字符串的回调
36 
37             ws.SessionClosed += Ws_SessionClosed;//回话关闭的回调
38 
39             ws.NewSessionConnected += Ws_NewSessionConnected;//新用户接入的回调
40 
41             if (ws.Setup(serverConfig))
42 
43             {
44 
45                 ws.Start();
46 
47                 Console.WriteLine("监听开始");
48 
49                 Console.ReadKey();
50 
51             }
52 
53         }
54 
55         private static void Ws_NewSessionConnected(WebSocketSession session)
56 
57         {
58 
59             //接收到新连接后,回复消息给客户端
60 
61             session.Send("hello");
62 
63         }
64 
65         private static void Ws_SessionClosed(WebSocketSession session, SuperSocket.SocketBase.CloseReason value)
66 
67         {
68 
69         }
70 
71         private static void Ws_NewMessageReceived(WebSocketSession session, string value)
72 
73         {
74 
75         }
76 
77         private static void Ws_NewDataReceived(WebSocketSession session, byte[] value)
78 
79         {
80 
81         }
82 
83     }



代码编写完成后,运行。然后编写小程序端连接WebSocket的代码。

使用wx.connectSocket接口放回一个SocketTask对象。代码如下:

 1 let task = wx.connectSocket({
 2 
 3 url: 'wss://ttt.vqicard.com:2018',
 4 
 5 success: function (res) {
 6 
 7 console.log(res)
 8 
 9 }
10 
11 })

然后SocketTask.onOpen监听连接打开事件。

SocketTask.onClose监听连接关闭事件。

SocketTask.onMessage(CALLBACK)

监听接收到服务器消息的事件。

通过SocketTask.send方法可以向服务器发送数据。

实例代码如下:

  

 1 task.onOpen(res => {
 2 
 3 console.log('连接服务器成功')
 4 
 5 })
 6 
 7 task.onMessage(res => {
 8 
 9 console.log(res)
10 
11 })

wss的基本配置到这里就完成了。

微信小程序登录时序分析

下图是微信官方提供的小程序的登录逻辑:

 

从上图我们可以大概分析出用户的小程序端用户授权登录的流程与逻辑。

1.小程序端,调用wx.login()获取code。

2.使用wx.request()将code发送给开发者服务器。

3.开发者服务器使用appid,appsecret,code调用微信提供的接口,获取当前用户的session_key以及openid,这里的session_key是微信服务器生成的针对用户数据加密签名的密钥。

4.开发者服务器使用指定的算法生成足够安全的第三方session。目的是保证session_key的安全性。所以,生成的第三方session应该满足如下条件:长度足够长,避免使用时间戳作为随机参数,设置一定的有效时间,过期即视为不合法。

5.以3rd_session为key,session_key+openid为value,写入session存储。目的是,可以通过3rd_session获取到真实的session_key。

6.将3rd_session返回到小程序端,在小程序端,使用storage存储到本地。

7.后续使用时,先判断3rd_session是否存在,如果不存在则重新从第一步开始。

以上为授权的基本流程,实际操作中,可能会比以上分析的麻烦一点,因为可能会涉及到用户不同意授权。或者以前点过不同意,现在又想点同意的情况。所以,具体的操作,还是通过代码来理解的比较透彻。

代码中,有两个地方是需要给服务器交互的,一个是验证本地存储的session是否合法,另一个是通过code换取第三方session。通常情况下,是使用https的方式与服务器交互,相关的代码在示例中我也写到。但这个答题项目主要是使用wss的方式与服务端通讯,所以,为了方便代码的管理,检测session和换取session的操作我都是用wss的方式,具体的看代码。下面是小程序的代码,注释已经很清楚了,我就不一一解释了。

  1 var wsTask
  2 
  3 //app.js
  4 
  5 App({
  6 
  7 onLaunch: function () {
  8 
  9 wsTask = wx.connectSocket({
 10 
 11  url: 'ws://192.168.0.253:2018'
 12 
 13 })
 14 
 15 this.wsTask = wsTask
 16 
 17 wsTask.onOpen(()=>{
 18 
 19  console.log('连接服务器成功')
 20 
 21 })
 22 
 23 wsTask.onMessage(msg=>{
 24 
 25  var res = JSON.parse(msg.data)
 26 
 27  switch(res.option){
 28 
 29  case 'checkSession':
 30 
 31  if(!res.status){
 32 
 33   this.login()
 34 
 35  }else{
 36 
 37   console.log('登录成功')
 38 
 39  }
 40 
 41  break
 42 
 43  case 'login':
 44 
 45   if(res.status){
 46 
 47   wx.setStorage({
 48 
 49    key: '3rd_session',
 50 
 51    data: res.session
 52 
 53   })
 54 
 55   console.log('登录成功')
 56 
 57   }
 58 
 59  break
 60 
 61  }
 62 
 63 })
 64 
 65 },
 66 
 67 checkSession: function () {
 68 
 69 //首先检测登录状态是否失效
 70 
 71 wx.checkSession({
 72 
 73  complete: cr => {
 74 
 75  if (cr.errMsg == 'checkSession:ok') {
 76 
 77   //授权状态有效,需判断3rd_session是否存在
 78 
 79   let rd_session = wx.getStorageSync('3rd_session')
 80 
 81   if (rd_session) {
 82 
 83   //第三方session存在
 84 
 85   wsTask.send({
 86 
 87    data: JSON.stringify({ option:'checkSession',session:rd_session})
 88 
 89   })
 90 
 91   return
 92 
 93   //将第三方session发送到服务器,验证合法性已经是否有效
 94 
 95   wx.request({
 96 
 97    url: 'checkSessionUrl',
 98 
 99    success: function (res) {
100 
101    if (res.status) {
102 
103     //根据服务端返回的验证结果进行判断,如果status为1,则表示3rdsession合法,且在有效期内。
104 
105    } else {
106 
107     //session无效
108 
109     this.login()
110 
111    }
112 
113    },
114 
115    fail: function (e) {
116 
117    console.error(e);//打印错误信息
118 
119    }
120 
121   })
122 
123   } else {
124 
125   //session不存在,则需重新进入授权流程
126 
127   this.login()
128 
129   }
130 
131  } else {
132 
133   //授权状态失效,则需重新进入授权流程
134 
135   this.login()
136 
137  }
138 
139  }
140 
141 })
142 
143 },
144 
145 login: function () {
146 
147 //检查用户是否已同意授权
148 
149 wx.authorize({
150 
151  scope: 'scope.userInfo',
152 
153  complete: res => {
154 
155  //不允许授权
156 
157  if (res.errMsg != 'authorize:ok') {
158 
159   //则获取用户的授权设置
160 
161   wx.getSetting({
162 
163   success: r => {
164 
165    //未开启授权
166 
167    if (!r.authSetting['scope.userInfo']) {
168 
169    //询问是否开启授权
170 
171    wx.showModal({
172 
173     title: '登录',
174 
175     content: '小程序需要使用您的授权信息,是否继续?',
176 
177     success: res => {
178 
179     console.log(res)
180 
181     if (res.confirm) {
182 
183      //同意开启授权,则跳转到设置页面,由用户打开授权。用户打开授权后,由用户操作,返回小程序,此时可以再onShow方法中再次调用login方法。
184 
185      wx.openSetting()
186 
187     }
188 
189     }
190 
191    })
192 
193    }
194 
195   }
196 
197   })
198 
199  } else {
200 
201   //表示已授权,此时,可以调用登录接口
202 
203   wx.login({
204 
205   success: res => {
206 
207    if(res.errMsg=='login:ok'){
208 
209    wsTask.send({data:JSON.stringify({option:'login',code:res.code})})
210 
211    return
212 
213    wx.request({
214 
215     url: 'loginUrl',
216 
217     data:{code:res.code},
218 
219     success:rq=>{
220 
221     //将此处返回的3rdsession保存在storage中,整个授权流程结束
222 
223     }
224 
225    })
226 
227    }
228 
229   }
230 
231   })
232 
233  }
234 
235  }
236 
237 })
238 
239 },
240 
241 onShow: function () {
242 
243 this.checkSession()
244 
245 },
246 
247 globalData: {
248 
249 userInfo: null
250 
251 }
252 
253 })

服务端的代码如下:

 1 private static void Ws_NewMessageReceived(WebSocketSession session, string value)
 2 
 3 {
 4 
 5 var jobj = JsonConvert.DeserializeObject<JObject>(value);
 6 
 7 var option = jobj.Value<string>("option");
 8 
 9 switch (option)
10 
11 {
12 
13 case "checkSession":
14 
15 var rdsession = jobj.Value<string>("session");
16 
17 var model = wxUserlist.FirstOrDefault(f => f.MyKey == rdsession);
18 
19 session.Send(JsonConvert.SerializeObject(new { option = option, status =model!=null?1:0}));
20 
21 break;
22 
23 case "login":
24 
25 var code = jobj.Value<string>("code");
26 
27 var res = LoginApi.CodeToMySession("你的appid", "你的appsecret", code);
28 
29 wxUserlist.Add(res);
30 
31 session.Send(JsonConvert.SerializeObject(new { status=1,session=res.MyKey,option=option}));
32 
33 break;
34 
35 }
36 
37 }
38 
39  

如需源码,请扫描二维码,关注微信公众号。回复:对战二

原文地址:https://www.cnblogs.com/zskbll/p/8532114.html