轻源码

  • QingYuanMa.com
  • 全球最大的互联网技术和资源下载平台
搜索
一起源码网 门户 终极进阶 查看主题

微信小程序session管理

发布者: hkingson | 发布时间: 2018-6-2 03:51| 查看数: 5217| 评论数: 1|帖子模式

作者:it-man,来自授权地址 
最近微信小程序开发很火。我们的移动端项目也开始使用小程序来实现,在这之前我们已经基于Html5实现了类似于小程序的应用。了解了小程序开发后觉得有很多相似之处,还是要用到js和css这些技术。但也有许多不同,jquery等这些js库不能直接使用了、http session也不支持、页面发起http请求小程序有自己的api。

对于我们项目来说就不只是简单的将H5页面翻译成小程序的页面这么简单了。首先要解决的问题就是http session。在H5项目中,使用http session来关联微信openid这样每次http请求都能确定是哪个用户发起的请求。如果熟悉http session的原理,session问题就好解决了。常见的session保持方式是,当浏览器向服务端发起http请求时,服务端检查在http 头部cookie参数里是否包含sessionid,如果有sessionid就根据sessionid去查看存储在服务器端的session,session里保存的当前会话的一些信息。如果sessionid没有服务端就会分配一个,写到cookie字段里,浏览器下次发起其它请求的时候带上。而在小程序里所有的请求都通过wx.request API来发起的。如果对wx.request API包装一下,使其每次向服务端发起请求时也添加一个名称为Cookie的http header,这样也不用对服务端作改动。服务端分配的sessionid使用wx.setStorageSync API存储在微信客户端。

项目源码地址:

1、客户端实现 
客户端代码目录smallapp-session/views,客户端主要实现对wx.request的封装,在wafer-client-demo项目的基础上作了一些修改。 
wx.request封装

  1. var constants = require('./constants');
  2. var utils = require('./utils');
  3. var Session = require('./session');
  4. var loginLib = require('./login');
  5. var noop = function noop() {};
  6. var buildAuthHeader = function buildAuthHeader(session) {
  7. var header = {};
  8. if (session && session.id) {
  9. header['Cookie'] =constants.WX_HEADER_ID+'='+session.id;
  10. }
  11. return header;
  12. };
  13. function request(options) {
  14. if (typeof options !== 'object') {
  15. var message = '请求传参应为 object 类型,但实际传了 ' + (typeof options) + ' 类型';
  16. throw new RequestError(constants.ERR_INVALID_PARAMS, message);
  17. }
  18. var requireLogin = options.login;
  19. var success = options.success || noop;
  20. var fail = options.fail || noop;
  21. var complete = options.complete || noop;
  22. var originHeader = options.header || {};
  23. // 成功回调
  24. var callSuccess = function () {
  25. success.apply(null, arguments);
  26. complete.apply(null, arguments);
  27. };
  28. // 失败回调
  29. var callFail = function (error) {
  30. fail.call(null, error);
  31. complete.call(null, error);
  32. };
  33. // 是否已经进行过重试
  34. var hasRetried = false;
  35. if (requireLogin) {
  36. doRequestWithLogin();
  37. } else {
  38. doRequest();
  39. }
  40. // 登录后再请求
  41. function doRequestWithLogin() {
  42. loginLib.login({ success: doRequest, fail: callFail });
  43. }
  44. // 实际进行请求的方法
  45. function doRequest() {
  46. var authHeader = buildAuthHeader(Session.get());
  47. console.log(authHeader)
  48. wx.request(utils.extend({}, options, {
  49. header: utils.extend({}, originHeader, authHeader),
  50. success: function (response) {
  51. var data = response.data;
  52. console.log("err:",data)
  53. console.log("errid:",data[constants.WX_SESSION_MAGIC_ID])
  54. // 如果响应的数据里面包含 SDK Magic ID,表示被服务端 SDK 处理过,此时一定包含登录态失败的信息
  55. if (data && data[constants.WX_SESSION_MAGIC_ID]) {
  56. console.log("clear session")
  57. // 清除登录态
  58. Session.clear();
  59. var error, message;
  60. if (data.error === constants.ERR_INVALID_SESSION) {
  61. // 如果是登录态无效,并且还没重试过,会尝试登录后刷新凭据重新请求
  62. if (!hasRetried) {
  63. hasRetried = true;
  64. doRequestWithLogin();
  65. return;
  66. }
  67. message = '登录态已过期';
  68. error = new RequestError(data.error, message);
  69. } else {
  70. message = '鉴权服务器检查登录态发生错误(' + (data.error || 'OTHER') + '):' + (data.message || '未知错误');
  71. error = new RequestError(constants.ERR_CHECK_LOGIN_FAILED, message);
  72. }
  73. callFail(error);
  74. return;
  75. }
  76. callSuccess.apply(null, arguments);
  77. },
  78. fail: callFail,
  79. complete: noop,
  80. }));
  81. };
  82. };

登录处理

  1. /**
  2. * 微信登录,获取 code 和 encryptData
  3. */
  4. var getWxLoginResult = function getLoginCode(callback) {
  5. wx.login({
  6. success: function (loginResult) {
  7. wx.getUserInfo({
  8. success: function (userResult) {
  9. callback(null, {
  10. code: loginResult.code,
  11. encryptedData: userResult.encryptedData,
  12. iv: userResult.iv,
  13. userInfo: userResult.userInfo,
  14. });
  15. },
  16. fail: function (userError) {
  17. var error = new LoginError(constants.ERR_WX_GET_USER_INFO, '获取微信用户信息失败,请检查网络状态');
  18. error.detail = userError;
  19. callback(error, null);
  20. },
  21. });
  22. },
  23. fail: function (loginError) {
  24. var error = new LoginError(constants.ERR_WX_LOGIN_FAILED, '微信登录失败,请检查网络状态');
  25. error.detail = loginError;
  26. callback(error, null);
  27. },
  28. });
  29. };
  30. var noop = function noop() {};
  31. var defaultOptions = {
  32. method: 'GET',
  33. success: noop,
  34. fail: noop,
  35. loginUrl: null,
  36. };
  37. /**
  38. * @method
  39. * 进行服务器登录,以获得登录会话
  40. *
  41. * @param {Object} options 登录配置
  42. * @param {string} options.loginUrl 登录使用的 URL,服务器应该在这个 URL 上处理登录请求
  43. * @param {string} [options.method] 请求使用的 HTTP 方法,默认为 "GET"
  44. * @param {Function} options.success(userInfo) 登录成功后的回调函数,参数 userInfo 微信用户信息
  45. * @param {Function} options.fail(error) 登录失败后的回调函数,参数 error 错误信息
  46. */
  47. var login = function login(options) {
  48. options = utils.extend({}, defaultOptions, options);
  49. if (!defaultOptions.loginUrl) {
  50. options.fail(new LoginError(constants.ERR_INVALID_PARAMS, '登录错误:缺少登录地址,请通过 setLoginUrl() 方法设置登录地址'));
  51. return;
  52. }
  53. var doLogin = () => getWxLoginResult(function (wxLoginError, wxLoginResult) {
  54. if (wxLoginError) {
  55. options.fail(wxLoginError);
  56. return;
  57. }
  58. var userInfo = wxLoginResult.userInfo;
  59. // 构造请求头,包含 code、encryptedData 和 iv
  60. var code = wxLoginResult.code;
  61. var encryptedData = wxLoginResult.encryptedData;
  62. var iv = wxLoginResult.iv;
  63. var header = {};
  64. //加密用户信息,在服务端解密
  65. header[constants.WX_HEADER_CODE] = code;
  66. header[constants.WX_HEADER_ENCRYPTED_DATA] = encryptedData;
  67. header[constants.WX_HEADER_IV] = iv;
  68. // 请求服务器登录地址,获得会话信息
  69. wx.request({
  70. url: options.loginUrl,
  71. header: header,
  72. method: options.method,
  73. data: options.data,
  74. success: function (result) {
  75. var data = result.data;
  76. // 成功地响应会话信息
  77. if (data && data[constants.WX_SESSION_MAGIC_ID]) {
  78. if (data.session) {
  79. data.session.userInfo = userInfo;
  80. console.log("set session")
  81. Session.set(data.session);
  82. options.success(userInfo);
  83. } else {
  84. var errorMessage = '登录失败(' + data.error + '):' + (data.message || '未知错误');
  85. var noSessionError = new LoginError(constants.ERR_LOGIN_SESSION_NOT_RECEIVED, errorMessage);
  86. options.fail(noSessionError);
  87. }
  88. // 没有正确响应会话信息
  89. } else {
  90. var errorMessage = '登录请求没有包含会话响应,请确保服务器处理 `' + options.loginUrl + '` 的时候正确使用了 SDK 输出登录结果';
  91. var noSessionError = new LoginError(constants.ERR_LOGIN_SESSION_NOT_RECEIVED, errorMessage);
  92. options.fail(noSessionError);
  93. }
  94. },
  95. // 响应错误
  96. fail: function (loginResponseError) {
  97. var error = new LoginError(constants.ERR_LOGIN_FAILED, '登录失败,可能是网络错误或者服务器发生异常');
  98. options.fail(error);
  99. },
  100. });
  101. });
  102. var session = Session.get();
  103. console.log("get session",session)
  104. if (session) {
  105. wx.checkSession({
  106. success: function () {
  107. options.success(session.userInfo);
  108. },
  109. fail: function () {
  110. Session.clear();
  111. doLogin();
  112. },
  113. });
  114. } else {
  115. doLogin();
  116. }
  117. };
  118. var setLoginUrl = function (loginUrl) {
  119. defaultOptions.loginUrl = loginUrl;
  120. };

对wx.request完成封装后,小程序中所有http请求都使用qcloud.request来实现,如views/pages/index/index.js

  1. var qcloud = require('../../vendor/qcloud-weapp-client-sdk/index');
  2. Page({
  3. onLoad: function () {
  4. console.log('onLoad')
  5. var that = this
  6. qcloud.request({
  7. login:true, //是否自动登录
  8. url:"",
  9. success:function(res){
  10. console.log(res.data)
  11. that.setData({
  12. userInfo:res.data
  13. })
  14. }
  15. })
  16. }
  17. })

2、服务端实现 
服务端使用golang实现,使用了beego框架。 
controllers/user.go

  1. var WX_SESSION_MAGIC_ID = "F2C224D4-2BCE-4C64-AF9F-A6D872000D1A"
  2. // Operations about Users
  3. type UserController struct {
  4. beego.Controller
  5. }
  6. type LoginResult struct {
  7. ErrorCode int `json:"errcode"`
  8. ErrorMsg string `json:"errmsg,omitempty"`
  9. Session `json:"session,omitempty"`
  10. }
  11. type Session struct {
  12. ID string `json:"id"`
  13. }
  14. // @Title login
  15. // @Description Logs user into the system
  16. // @Param code query string true "wx.Login response code"
  17. // @Success 200 {string} login success
  18. // @Failure 403 user not exist
  19. // @router /login [get]
  20. func (u *UserController) Login() {
  21. fmt.Println(u.Ctx.Input.CruSession)
  22. session := u.Ctx.Input.CruSession
  23. code := u.Ctx.Input.Header("X-WX-Code")
  24. encryptedData := u.Ctx.Input.Header("X-WX-Encrypted-Data")
  25. iv := u.Ctx.Input.Header("X-WX-IV")
  26. if len(code) > 0 && len(encryptedData) > 0 && len(iv) > 0 {
  27. userInfo, err := services.Login(code, encryptedData, iv)
  28. if err != nil {
  29. u.Data["json"] = LoginResult{
  30. ErrorCode: 1,
  31. ErrorMsg: "invaliad params",
  32. }
  33. } else {
  34. result := make(map[string]interface{})
  35. result["session"] = Session{ID: session.SessionID()}
  36. result[WX_SESSION_MAGIC_ID] = 1
  37. u.Data["json"] = result
  38. //save userinfo into session
  39. session.Set("userinfo", userInfo)
  40. }
  41. } else {
  42. u.Data["json"] = LoginResult{
  43. ErrorCode: 1,
  44. ErrorMsg: "invaliad params",
  45. }
  46. }
  47. u.ServeJSON()
  48. }
  49. // @Title login
  50. // @Description Logs user into the system
  51. // @Param code query string true "wx.Login response code"
  52. // @Success 200 {string} login success
  53. // @Failure 403 user not exist
  54. // @router /query [get]
  55. func (u *UserController) Query() {
  56. session := u.Ctx.Input.CruSession
  57. if val := session.Get("userinfo"); val != nil {
  58. u.Data["json"] = val.(services.UserInfo)
  59. } else {
  60. u.Data["json"] = LoginResult{
  61. ErrorCode: 1,
  62. ErrorMsg: "need login",
  63. }
  64. }
  65. u.ServeJSON()
  66. }

services/user.go

  1. type Code2sessionResult struct {
  2. ErrorCode int `json:"errcode"`
  3. ErrorMsg string `json:"errmsg,omitempty"`
  4. SessionKey string `json:"session_key,omitempty"`
  5. ExpiresIn int `json:"expires_in,omitempty"`
  6. Openid string `json:"openid,omitempty"`
  7. }
  8. type WaterMark struct {
  9. AppID string `json:"appid"`
  10. Timestamp int `json:"timestamp"`
  11. }
  12. type UserInfo struct {
  13. Openid string `json:"openid"`
  14. NickName string `json:"nickName"`
  15. Gender int `json:"gender"`
  16. City string `json:"city"`
  17. Province string `json:"province"`
  18. Country string `json:"country"`
  19. AvatarURL string `json:"avatarUrl"`
  20. UnionID string `json:"unionId"`
  21. WaterMark `json:"watermark"`
  22. }
  23. var APPID = beego.AppConfig.String("APPID")
  24. var SECRET = beego.AppConfig.String("SECRET")
  25. func Login(code, encrytedData, iv string) (userInfo UserInfo, err error) {
  26. //get openid and session_key
  27. url := "" + APPID + "&secret=" + SECRET + "&js_code=" + code + "&grant_type=authorization_code"
  28. r, err := util.HttpGet(url)
  29. if err != nil {
  30. //auth error
  31. fmt.Println(err)
  32. return
  33. }
  34. code2session := Code2sessionResult{}
  35. err = json.Unmarshal(r, &code2session)
  36. if err != nil {
  37. fmt.Println(err)
  38. return
  39. }
  40. if code2session.ErrorCode > 0 {
  41. err = fmt.Errorf("%d=>%s", code2session.ErrorCode, code2session.ErrorMsg)
  42. return
  43. }
  44. //code2session success,check signature
  45. //aes decrypt
  46. decrypted, err := util.WXBizDataDecrypt(code2session.SessionKey, encrytedData, iv)
  47. if err != nil {
  48. fmt.Println(err)
  49. return
  50. }
  51. fmt.Println(string(decrypted))
  52. err = json.Unmarshal(decrypted, &userInfo)
  53. if err != nil {
  54. fmt.Println(err)
  55. return
  56. }
  57. return
  58. }

加密数据解密

  1. import (
  2. "crypto/aes"
  3. "crypto/cipher"
  4. "crypto/sha1"
  5. "encoding/base64"
  6. "fmt"
  7. )
  8. func WXBizDataDecrypt(sessionKey, encryptedData, iv string) (decrypted []byte, err error) {
  9. var (
  10. decryptedkey, decryptedIV, decryptedData []byte
  11. )
  12. decryptedkey, err = base64.StdEncoding.DecodeString(sessionKey)
  13. if err != nil {
  14. return
  15. }
  16. decryptedData, err = base64.StdEncoding.DecodeString(encryptedData)
  17. if err != nil {
  18. return
  19. }
  20. decryptedIV, err = base64.StdEncoding.DecodeString(iv)
  21. if err != nil {
  22. return
  23. }
  24. block, err := aes.NewCipher(decryptedkey)
  25. if err != nil {
  26. fmt.Println(err)
  27. return
  28. }
  29. blockMode := cipher.NewCBCDecrypter(block, decryptedIV)
  30. decrypted = make([]byte, len(decryptedData))
  31. // origData := crypted
  32. blockMode.CryptBlocks(decrypted, decryptedData)
  33. decrypted = PKCS5UnPadding(decrypted)
  34. return
  35. }
  36. func WXBizDataSignature(sessionKey, rawData string) string {
  37. hash := sha1.New()
  38. _, err := hash.Write([]byte(rawData + sessionKey))
  39. if err != nil {
  40. return ""
  41. }
  42. sign := hash.Sum(nil)
  43. return string(sign)
  44. }
  45. func PKCS5UnPadding(origData []byte) []byte {
  46. length := len(origData)
  47. // 去掉最后一个字节 unpadding 次
  48. unpadding := int(origData[length-1])
  49. return origData[:(length - unpadding)]
  50. }

本文相关文章:跳坑《一百一十七》Session、session_key及checkSession

最新评论

【‖狼行‖】 发表于 2022-6-21 14:31
哪里可以下载免费音乐

轻源码让程序更轻更快

QingYuanMa.com

工作时间 周一至周六 8:00-17:30

侵权处理

客服QQ点击咨询

关注抖音号

定期抽VIP

Copyright © 2016-2021 https://www.171739.xyz/ 滇ICP备13200218号

快速回复 返回顶部 返回列表