SpringBoot项目实现微信小程序登录步骤

微信小程序登录

最近项目中需要微信登录,包括网页微信登录,小程序微信登录,App微信登录。本文先记录小程序微信登录。


微信小程序登录流程涉及到三个角色:小程序、开发者服务器、微信服务器


三者交互步骤如下

第一步:小程序通过wx.login()获取code。
第二步:小程序通过wx.request()发送code到开发者服务器。
第三步:开发者服务器接收小程序发送的code,并携带appid、appsecret(这两个需要到微信小程序后台查看)、code发送到微信服务器。
第四步:微信服务器接收开发者服务器发送的appid、appsecret、code进行校验。校验通过后向开发者服务器发送session_key、openid。
第五步:开发者服务器自己生成一个skey(自定义登录状态)与openid、session_key进行关联,并存到数据库中(mysql、redis等)。
第六步:开发者服务器返回生成skey(自定义登录状态)到小程序。
第七步:小程序存储skey(自定义登录状态)到本地。
第八步:小程序通过wx.request()发起业务请求到开发者服务器,同时携带skey(自定义登录状态)。
第九步:开发者服务器接收小程序发送的skey(自定义登录状态),查询skey在数据库中是否有对应的openid、session_key。
第十步:开发者服务器返回业务数据到小程序


登录流程时序如下:

登录流程

第一步:小程序通过wx.login()获取code。
第二步:小程序通过wx.request()发送code到开发者服务器。
第三步:开发者服务器接收小程序发送的code,并携带appid、appsecret(这两个需要到微信小程序后台查看)、code发送到微信服务器。
第四步:微信服务器接收开发者服务器发送的appid、appsecret、code进行校验。校验通过后向开发者服务器发送session_key、openid。
第五步:开发者服务器自己生成一个skey(自定义登录状态)与openid、session_key进行关联,并存到数据库中(mysql、redis等)。
第六步:开发者服务器返回生成skey(自定义登录状态)到小程序。
第七步:小程序存储skey(自定义登录状态)到本地。

controller

用于接收用户请求,校验签名,并生成skey,存储skey、openid等数据

代码如下(示例):

package cn.lastwhisper.springbootwx.controller;
 
import cn.lastwhisper.springbootwx.common.GlobalResult;
import cn.lastwhisper.springbootwx.mapper.UserMapper;
import cn.lastwhisper.springbootwx.pojo.User;
import cn.lastwhisper.springbootwx.common.WechatUtil;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import org.apache.commons.codec.digest.DigestUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
 
import java.util.Date;
import java.util.UUID;
 
/**
 * @author lastwhisper
 * @desc
 * @email [email protected]
 */
@Controller
public class UserController {
    
    
 
    @Autowired
    private UserMapper userMapper;
 
    /**
     * 微信用户登录详情
     */
    @PostMapping("wx/login")
    @ResponseBody
    public GlobalResult user_login(@RequestParam(value = "code", required = false) String code,
                                   @RequestParam(value = "rawData", required = false) String rawData,
                                   @RequestParam(value = "signature", required = false) String signature,
                                   @RequestParam(value = "encrypteData", required = false) String encrypteData,
                                   @RequestParam(value = "iv", required = false) String iv) {
    
    
        // 用户非敏感信息:rawData
        // 签名:signature
        JSONObject rawDataJson = JSON.parseObject(rawData);
        // 1.接收小程序发送的code
        // 2.开发者服务器 登录凭证校验接口 appi + appsecret + code
        JSONObject SessionKeyOpenId = WechatUtil.getSessionKeyOrOpenId(code);
        // 3.接收微信接口服务 获取返回的参数
        String openid = SessionKeyOpenId.getString("openid");
        String sessionKey = SessionKeyOpenId.getString("session_key");
 
        // 4.校验签名 小程序发送的签名signature与服务器端生成的签名signature2 = sha1(rawData + sessionKey)
        String signature2 = DigestUtils.sha1Hex(rawData + sessionKey);
        if (!signature.equals(signature2)) {
    
    
            return GlobalResult.build(500, "签名校验失败", null);
        }
        // 5.根据返回的User实体类,判断用户是否是新用户,是的话,将用户信息存到数据库;不是的话,更新最新登录时间
        User user = this.userMapper.selectById(openid);
        // uuid生成唯一key,用于维护微信小程序用户与服务端的会话
        String skey = UUID.randomUUID().toString();
        if (user == null) {
    
    
            // 用户信息入库
            String nickName = rawDataJson.getString("nickName");
            String avatarUrl = rawDataJson.getString("avatarUrl");
            String gender = rawDataJson.getString("gender");
            String city = rawDataJson.getString("city");
            String country = rawDataJson.getString("country");
            String province = rawDataJson.getString("province");
 
            user = new User();
            user.setOpenId(openid);
            user.setSkey(skey);
            user.setCreateTime(new Date());
            user.setLastVisitTime(new Date());
            user.setSessionKey(sessionKey);
            user.setCity(city);
            user.setProvince(province);
            user.setCountry(country);
            user.setAvatarUrl(avatarUrl);
            user.setGender(Integer.parseInt(gender));
            user.setNickName(nickName);
 
            this.userMapper.insert(user);
        } else {
    
    
            // 已存在,更新用户登录时间
            user.setLastVisitTime(new Date());
            // 重新设置会话skey
            user.setSkey(skey);
            this.userMapper.updateById(user);
        }
        //encrypteData比rowData多了appid和openid
        //JSONObject userInfo = WechatUtil.getUserInfo(encrypteData, sessionKey, iv);
        //6. 把新的skey返回给小程序
        GlobalResult result = GlobalResult.build(200, null, skey);
        return result;
    }
}

WechatUtil

代码如下(示例):

package cn.lastwhisper.springbootwx.common;/**
 * Create by eval on 2019/3/20
 */
 
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import org.apache.shiro.codec.Base64;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
 
import javax.crypto.Cipher;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.security.AlgorithmParameters;
import java.security.Security;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
 
/**
 * @ClassName WechatUtil
 * @Description TODO
 * @Author eval
 * @Date 9:44 2019/3/20
 * @Version 1.0
 */
public class WechatUtil {
    
    
    public static JSONObject getSessionKeyOrOpenId(String code) {
    
    
        String requestUrl = "https://api.weixin.qq.com/sns/jscode2session";
        Map<String, String> requestUrlParam = new HashMap<>();
        // https://mp.weixin.qq.com/wxopen/devprofile?action=get_profile&token=164113089&lang=zh_CN
        //小程序appId
        requestUrlParam.put("appid", "小程序appId");
        //小程序secret
        requestUrlParam.put("secret", "小程序secret");
        //小程序端返回的code
        requestUrlParam.put("js_code", code);
        //默认参数
        requestUrlParam.put("grant_type", "authorization_code");
        //发送post请求读取调用微信接口获取openid用户唯一标识
        JSONObject jsonObject = JSON.parseObject(HttpClientUtil.doPost(requestUrl, requestUrlParam));
        return jsonObject;
    }
 
    public static JSONObject getUserInfo(String encryptedData, String sessionKey, String iv) {
    
    
        // 被加密的数据
        byte[] dataByte = Base64.decode(encryptedData);
        // 加密秘钥
        byte[] keyByte = Base64.decode(sessionKey);
        // 偏移量
        byte[] ivByte = Base64.decode(iv);
        try {
    
    
            // 如果密钥不足16位,那么就补足.  这个if 中的内容很重要
            int base = 16;
            if (keyByte.length % base != 0) {
    
    
                int groups = keyByte.length / base + (keyByte.length % base != 0 ? 1 : 0);
                byte[] temp = new byte[groups * base];
                Arrays.fill(temp, (byte) 0);
                System.arraycopy(keyByte, 0, temp, 0, keyByte.length);
                keyByte = temp;
            }
            // 初始化
            Security.addProvider(new BouncyCastleProvider());
            Cipher cipher = Cipher.getInstance("AES/CBC/PKCS7Padding", "BC");
            SecretKeySpec spec = new SecretKeySpec(keyByte, "AES");
            AlgorithmParameters parameters = AlgorithmParameters.getInstance("AES");
            parameters.init(new IvParameterSpec(ivByte));
            cipher.init(Cipher.DECRYPT_MODE, spec, parameters);// 初始化
            byte[] resultByte = cipher.doFinal(dataByte);
            if (null != resultByte && resultByte.length > 0) {
    
    
                String result = new String(resultByte, "UTF-8");
                return JSON.parseObject(result);
            }
        } catch (Exception e) {
    
    
        }
        return null;
    }
}

微信小程序

pages/me/me.js
Page({
    
    
 
  /**
   * 页面的初始数据
   */
  data: {
    
    
    hasUserInfo: false,
    userInfo: null
  },
 
  onLoad: function() {
    
    
    // 页面加载时使用用户授权逻辑,弹出确认的框  
    this.userAuthorized()
  },
  
  userAuthorized() {
    
    
    wx.getSetting({
    
    
      success: data => {
    
    
        if (data.authSetting['scope.userInfo']) {
    
    
          wx.getUserInfo({
    
    
            success: data => {
    
    
              this.setData({
    
    
                hasUserInfo: true,
                userInfo: data.userInfo
              })
            }
          })
        } else {
    
    
          this.setData({
    
    
            hasUserInfo: false
          })
        }
      }
    })
  },
 
  onGetUserInfo(e) {
    
    
    const userInfo = e.detail.userInfo
    if (userInfo) {
    
    
      // 1. 小程序通过wx.login()获取code
      wx.login({
    
    
        success: function(login_res) {
    
    
          //获取用户信息
          wx.getUserInfo({
    
    
            success: function(info_res) {
    
    
              // 2. 小程序通过wx.request()发送code到开发者服务器
              wx.request({
    
    
                url: 'http://localhost:8080/wx/login',
                method: 'POST',
                header: {
    
    
                  'content-type': 'application/x-www-form-urlencoded'
                },
                data: {
    
    
                  code: login_res.code, //临时登录凭证
                  rawData: info_res.rawData, //用户非敏感信息
                  signature: info_res.signature, //签名
                  encrypteData: info_res.encryptedData, //用户敏感信息
                  iv: info_res.iv //解密算法的向量
                },
                success: function(res) {
    
    
                  if (res.data.status == 200) {
    
    
                    // 7.小程序存储skey(自定义登录状态)到本地
                    wx.setStorageSync('userInfo', userInfo);
                    wx.setStorageSync('skey', res.data.data);
                  } else{
    
    
                    console.log('服务器异常');
                  }
                },
                fail: function(error) {
    
    
                  //调用服务端登录接口失败
                  console.log(error);
                }
              })
            }
          })
        }
      })
      this.setData({
    
    
        hasUserInfo: true,
        userInfo: userInfo
      })
    }
  }
 
})

猜你喜欢

转载自blog.csdn.net/Wyangcsdb/article/details/109591336