zoukankan      html  css  js  c++  java
  • 小程序版好友对战实战-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  

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

  • 相关阅读:
    【译】用 Chart.js 做漂亮的响应式表单
    【译】快速高效学习Java编程在线资源Top 20
    【译】理解Spring MVC Model Attribute 和 Session Attribute
    Github 恶搞教程(一起『玩坏』自己的 Github 吧)
    Effective Java 读书笔记(一):使用静态工厂方法代替构造器
    JavaScript 中 onload 事件绑定多个方法的优化建议
    【译】常见 Java 异常解释(恶搞版)
    Java 重写 equals 与 hashCode 的注意事项
    【译】Java语言速览:StackOverflow
    【译】StackOverflow——Java 中的 finally 代码块是否总会被执行?
  • 原文地址:https://www.cnblogs.com/zskbll/p/8532114.html
Copyright © 2011-2022 走看看