小程序登录原理及实践

2,476 阅读6分钟

登录态维持

  • 在小程序中,业务服务端返回的session_id存在本地
  • 请求时手动添加到请求头的cookie上

用到的微信mp api

  • wx.getSetting() 获取用户是否授权过某个权限scope.xxxx
  • wx.login() 获取临时登录凭证code,
    • 内部调用wx.checkSession(),
      • 如果过期会重新生成code,小程序请求微信服务接口,返回新的code
      • 没过期,返回本地的code
    • 这个code在微信服务端对应一个session_key和一个过期时间
    • 业务方拿到code,调用微信服务接口,微信服务查表拿到session_key,返回给业务服务端
  • wx.checkSession() 检查小程序登录态是否过期
  • wx.getUserInfo() 获取微信用户信息,如果用户没有授权过或者没有登录(wx.checkSession()失败)过,会报错。

微信手机号、用户信息获取

  • <button open-type="getPhoneNumber">微信登录</button>
    • 只能通过这种方式获取手机加密数据
    • 返回手机号加密数据
  • <button open-type="getUserInfo">获取信息</button>

业务方服务端接口

  • /oauth/login 三方登录
  • /self/login 手机号、验证码、密码登录
  • /login/code 获取验证码
  • /user/info 获取用户信息
  • /user/update 更新用户信息

微信服务端接口

  • auth.code2Session
    • 参数 appid、appsecret、code(调用wx.login()拿到的)
    • 获取 openid、unionid(如果这个appid绑定过unionid)、session_key(用来解密数据用的)
  • auth.getAccessToken
    • 参数 appid、appsecret
    • 获取 access_token,接口调用凭证,在登录环节用不到

登录场景梳理

  • 本地storage获取session_token
    • 如果没有session_token,跳转到登录页,一般有两个操作按钮
      • 获取微信手机号登录
        • 用户点击 <button open-type="getPhoneNumber">按钮跳出授权提示,必须用按钮交互才拿到手机号加密信息
        • 用户同意
          • 回调函数拿到用户的加密数据
          • 调用wx.checksession()接口,这一步可以跳过,但是可能会出现服务端调用微信服务异常情况。
            • 如果过期,调用wx.login()接口,拿到code,类似用验证码功能
            • 如果没有过期继续以下步骤
          • 调用服务端/oauth/login接口
            • 服务端拿到请求中的code和手机号加密数据
            • 用code、小程序appid和appsecret,调用微信服务auth.code2Session接口,拿到微信登录态session_key、openid、unionid
            • 服务端解密手机加密数据
              • 通过上面微信返回的session_key和手机号加密数据,服务端解密拿到真实手机号
              • 如果这个手机号不在用户表中,注册一个新用户,
              • 如果存在,获取这个用户的信息,user_id等,
            • 检查用户没有绑定过,有unionid,则用去查unionid,没有用openid查
              • 建立关联,user_id、phone、openid或unionid
            • 在session表中生成一条记录,
              • 包含微信信息:session_key、openid、unionid(如果有的话)
              • 包含自定义信息:用户id、手机号、过期时间
            • 响应头设置set-cookie: <session_key>=<session_token>,这个session_token就是session记录id
          • 返回失败
            • 有可能是本地session过期,需要在调用/oauth/login接口前,
              • 先调用wx.login()接口,刷新本地session,并修改本地的加密数据
          • 返回成功
            • 拿到response header cookies中业务方的session_token值,存到global,
            • 调用获取用户信息/user/info接口,存到全局
            • 跳到指定的页面
        • 用户不同意,跳转到手动输入手机、密码或验证码登录页
          • 拿到用户输入的手机号、密码或验证码
            • 如果是验证码模式,点击获取验证码,
              • 调用服务端/login/code获取验证码接口,
              • 服务端拿到手机号,调用三方发送短信服务,
              • 三方短信服务,给用户发验证码,并也给服务端返回验证码
              • 服务在session表中生成一条记录,包含(验证码、过期时间)
          • 调用服务端/self/login接口
            • 拿到手机号,查用户表
            • 如果不存在这个用户,注册一个新用户(用户表生成一条记录),session表生成一条新记录,set-cookie设置为新生成的sessionid。
            • 如果存在这个用户,用户表中查到这个用户数据
              • 如果是密码登录,对比两个密码是否一致,
                • 如果不一致返回错误,
                • 如果一致
                  • 在session中生成一条记录,包含(用户id、过期时间)
                  • 设置response header写入cookie,值为新生成的sessionid
              • 如果是验证码登录
                • 拿到验证码code,查验证码表,拿到验证码数据(id,code)
                  • 如果存在,并且两者匹配
                    • 在表中删除这条记录
                    • 在session中生成一条记录,包含(用户id、过期时间)
                    • 设置response header写入cookie,值为新生成的sessionid
                    • 设置response header写入cookie,并返回
                  • 不存在、不匹配,返回错误
          • 获取response_header上cookie信息,拿到session_key对应的值(session_token),手动调用api存入local_storage中(因为不是浏览器环境)
          • 如果这个/oauth/login接口返回了用户信息
            • 设置到global上,并且跳转到对应页面(默认是首页)
          • 如果这个/oauth/login接口没有返回用户信息接口
            • 在调用获取用户信息接口/user/info,拿到信息写入global,跳到指定页面
      • 手动输入手机号密码登录
        • 用户点击这个按钮,直接跳转到手机号、密码或验证码登录页
    • 如果有session_token
      • 把session_token,手动设置到请求头上,因为小程序环境不像浏览器环境会自动添加cookie到请求头,调用业务方用户信息接口/user/info
        • 服务端获取请求头session_key对应的session_id,
        • 如果session_id不存在,或者不存在于session表中,返回登录失败
        • 如果能查到,拿到user_id,和过期时间expire_time
          • 如果已过期(new Date() >= expire_time),返回失败
          • 用user_id查用户表,
            • 若不存在,返回失败
            • 存在,响应中返回用户信息
      • 成功返回用户数据
        • 如果这个用户数据只有手机号,说明没有绑定过用户的微信账号数据
          • 通过wx.getSettting()接口查看用户有没有授权scope.userInfo,
            • 如果用户已经授权过获取用户详情,
              • 直接调用获取详情,
              • 调用服务端/user/update接口,写入昵称等微信数据
              • 用户数据写入global,跳转首页
            • 如果没有授权,页面展示<button open-type="getUserInfo">接口,用户点击
              • 如果同意,
                • 拿到数据,调用服务端/user/update接口,写入昵称等微信数据
                • 用户数据写入global,跳转首页
              • 如果不同意,用户数据写入global,跳转首页
        • 如果用户数据是完整的
          • 把用户信息写入global上,或者是vuex上(用uniapp开发的话)
      • 如果服务端返回异常(session_token过期),需要跳回到登录页