【微信小程序开发】之微信授权登陆

目录

前言

​编辑一、微信授权登陆介绍

1. 基本概念

2. 微信小程序开发实现微信授权登陆原理流程

3. 小程序中运用微信授权登陆的好处

二、微信授权登陆接口演示

 1. 在微信开发工具中导入小程序授权微信登陆前端项目

2. 微信授权登陆的方式

2.1 wx.login

2.2 wx.getUserProfile

2.3 二者的区别

三、 小程序微信授权登陆流程

1. 流程图

 2. 代码调试

2.1 导入调试后端代码

 2.2 导入调试前端代码

 2.3 效果演示

3. 代码

小程序前端代码

login.wxml

 login.js

 后端代码

IpUtil.java

WxAuthController.java

 WxHomeController.java

 WxInfoController.java

 WxUserController.java

四、解决微信名称含特殊标签符号的存储问题

 1. 配置my.ini文件

2. 重启我们的数据库服务

 3. 点击多账号调试


前言

        在上一期的博客中与大家分享了微信小程序开发会议OA项目首页数据的后台交互,以及在其中使用到工具文件来实现便捷的去调用请求访问后台的接口方法,还涉及了.wxs文件的介绍和使用。今天给大家带来的是微信小程序开发之微信授权登陆,让我们一起来了解了解吧。

一、微信授权登陆介绍

1. 基本概念

        微信授权登录是指用户通过微信账号登录第三方应用或网站的一种身份验证方式。当用户在第三方应用或网站上选择使用微信登录时,该应用或网站会跳转到微信登录页面,用户在此页面输入微信账号和密码进行登录。一旦用户成功登录并授权,第三方应用或网站就能够获得用户在微信上的基本信息,如昵称、头像等,并用于用户在应用或网站上的个性化服务。这种授权登录的方式方便用户快速登录第三方平台,同时减少了用户填写注册信息的麻烦。

2. 微信小程序开发实现微信授权登陆原理流程

实现微信授权登录的主要原理流程如下:

  1. 在小程序中创建一个“登录”按钮或其他触发授权登录的交互元素。

  2. 用户点击登录按钮后,小程序调用wx.login接口获取临时登录凭证code

  3. 小程序调用wx.getUserInfo接口获取用户的基本信息,例如昵称、头像等。

  4. 小程序将获取到的临时登录凭证code和用户基本信息发送给开发者自己的后台服务器。

  5. 开发者的后台服务器收到请求后,通过code调用微信的登录凭证校验接口,验证凭证的有效性,并获取到openidsession_key

  6. 开发者的后台服务器根据获取的openidsession_key生成一个自定义的用户标识 token,并返回给小程序。

  7. 小程序将自定义的用户标识 token 存储起来,作为用户登录的凭证。

  8. 用户在之后的小程序访问中,可以携带这个自定义的用户标识 token 请求开发者的后台服务器,进行用户身份的验证和数据操作。

  9. 开发者可以在后台服务器中根据用户的openid进行用户识别和管理,例如创建新用户、更新用户信息、绑定其他第三方账号等。

  10. 如果开发者需要获得用户的更多个人信息,例如手机号码、地理位置等,可以引导用户进行相应的授权,通过调用相应的接口获取这些信息。

  11. 如果小程序需要将用户的登录状态同步到其他终端(如PC端、移动App等),开发者可以通过在后台服务器中记录登录状态,实现多终端的登录一致性。

  12. 对于用户敏感操作或需要保护的数据,开发者可以在后台服务器进行权限验证,确保用户有足够的权限进行相应的操作。

 注意:

        为了确保用户信息的安全性,开发者在传输用户信息时,应使用加密算法进行加密,以保护用户的隐私。此外,开发者的后台服务器需要与微信服务器进行密切的协作,以保证登录凭证和用户信息的有效性和安全性。

        为了确保用户信息的安全性,开发者在传输用户信息时,应使用加密算法进行加密,以保护用户的隐私。此外,开发者的后台服务器需要与微信服务器进行密切的协作,以保证登录凭证和用户信息的有效性和安全性。

3. 小程序中运用微信授权登陆的好处

使用微信授权登陆的优势有以下几点:

  1. 用户体验优化:相比传统的网页注册登录,微信授权登陆省去了繁琐的注册流程,用户只需授权手机用于绑定身份,即可完成登录,这大大提高了用户体验。
  2. 安全性:微信授权登陆采用了安全、可靠的授权机制,确保用户的个人信息和数据安全,同时避免了恶意注册和滥用账号的风险。
  3. 数据分析与优化:通过微信授权登陆,品牌企业可以获取到用户的手机信息,进而进行用户行为分析和数据挖掘,以便更好地了解用户需求,优化产品和服务。
  4. 提高安全性:微信授权登陆采用了安全、可靠的授权机制,确保用户的个人信息和数据安全,防止了恶意注册和滥用账号的风险。
  5. 简化开发流程:微信官方提供了丰富的API和开发工具,使得开发者可以轻松地实现微信授权登陆功能,简化了开发流程,提高了开发效率。

除此之外,还有跨平台整合、增加用户粘性、社交功能拓展、避免重复注册等等的好处。

二、微信授权登陆接口演示

        资源文件介绍

 

 1. 在微信开发工具中导入小程序授权微信登陆前端项目

 

2. 微信授权登陆的方式

2.1 wx.login

        该方式演示的效果是(微信直接登陆2),效果如下

 

2.2 wx.getUserProfile

   该方式演示的效果是(微信直接登陆1),效果如下。将index.js文件中的canIUseGetUserProfile的属性值改为true。效果演示如下

 

2.3 二者的区别

  1. wx.login: 这个接口用于获取用户的登录凭证(code),开发者可以通过这个凭证向服务器换取用户的唯一标识和会话密钥等信息。一般情况下,账号登录或者授权登录都会使用wx.login接口获取用户的code。

  2. wx.getUserProfile: 这个接口用于获取用户的个人信息,包括昵称、头像等。在用户授权的情况下,开发者可以通过wx.getUserProfile获取用户的个人信息。

        因此,两者的区别在于功能和用途上。wx.login用于获取用户登录凭证,而wx.getUserProfile用于获取用户的个人信息。需要注意的是,为了保护用户隐私,获取用户个人信息需要用户授权,而且在小程序中需要提前向用户展示授权弹窗,让用户确认是否授权。

        但是微信官方推荐使用wx.getUserProfile的这种授权方式,这种方式安全性高。

三、 小程序微信授权登陆流程

1. 流程图

 2. 代码调试

2.1 导入调试后端代码

        在我们的开发工具中导入资源中的ssm-oa项目文件

 

        我们还要看Maven文件是否一致,以及依赖下载网址是否是阿里云的下载路径

 配置我们的数据库连接接口文件以及appID和app密钥,在application.yml中配置

 

 2.2 导入调试前端代码

        导入我们前端的测试代码

 

 2.3 效果演示

         我们授权登陆后会在数据库中显示增加一条关于该用户的数据

3. 代码

小程序前端代码
login.wxml
<!--pages/auth/login/login.wxml-->
<view class="container">
  <view class="login-box">
    <button wx:if="{{canIUseGetUserProfile}}" type="primary" class="wx-login-btn" bindtap="getUserProfile">微信直接登录</button>
    <button wx:else open-type="getUserInfo" type="primary" class="wx-login-btn" bindgetuserinfo="wxLogin">微信直接登录</button>
    <button type="primary" class="account-login-btn" bindtap="accountLogin">账号登录</button>
  </view>
</view>

        这编辑的是登陆功能的页面演示

 login.js
// pages/auth/login/login.js
var util = require('../../../utils/util.js');
var user = require('../../../utils/user.js');
const app = getApp();
Page({

    /**
     * 页面的初始数据
     */
    data: {
        canIUseGetUserProfile: false, // 用于向前兼容
        lock:false
    },
    onLoad: function(options) {
        // 页面初始化 options为页面跳转所带来的参数
        // 页面渲染完成
        if (wx.getUserProfile) {
          this.setData({
            canIUseGetUserProfile: true
          })
        }
        //console.log('login.onLoad.canIUseGetUserProfile='+this.data.canIUseGetUserProfile)
    },

    /**
     * 生命周期函数--监听页面初次渲染完成
     */
    onReady() {

    },

    /**
     * 生命周期函数--监听页面显示
     */
    onShow() {

    },
    getUserProfile(e) {
        // 推荐使用wx.getUserProfile获取用户信息,开发者每次通过该接口获取用户个人信息均需用户确认
        // 开发者妥善保管用户快速填写的头像昵称,避免重复弹窗
        wx.getUserProfile({
            desc: '用于完善会员资料', // 声明获取用户个人信息后的用途,后续会展示在弹窗中,请谨慎填写
            success: (res) => {
                //console.log(res);
                debugger
                user.checkLogin().catch(() => {
                    user.loginByWeixin(res.userInfo).then(res => {
                      app.globalData.hasLogin = true;
                      debugger
                      wx.navigateBack({
                        delta: 1
                      })
                    }).catch((err) => {
                      app.globalData.hasLogin = false;
                      if(err.errMsg=="request:fail timeout"){
                        util.showErrorToast('微信登录超时');
                      }else{
                        util.showErrorToast('微信登录失败');
                      }
                      this.setData({
                        lock:false
                      })
                    });
                  });
            },
            fail: (res) => {
                app.globalData.hasLogin = false;
                console.log(res);
                util.showErrorToast('微信登录失败');
            }
        });
    },
    wxLogin: function(e) {
        if (e.detail.userInfo == undefined) {
          app.globalData.hasLogin = false;
          util.showErrorToast('微信登录失败');
          return;
        }
        user.checkLogin().catch(() => {
            user.loginByWeixin(e.detail.userInfo).then(res => {
              app.globalData.hasLogin = true;
              wx.navigateBack({
                delta: 1
              })
            }).catch((err) => {
              app.globalData.hasLogin = false;
              if(err.errMsg=="request:fail timeout"){
                util.showErrorToast('微信登录超时');
              }else{
                util.showErrorToast('微信登录失败');
              }
            });
      
          });
    },
    accountLogin() {
        console.log('开发中....')
    }

})

        login.js文件中定义的是授权登陆所要涉及到的方法函数

 后端代码
IpUtil.java
package com.zking.ssm.wxcontroller;

import java.net.InetAddress;
import java.net.UnknownHostException;

import javax.servlet.http.HttpServletRequest;

/**
 * IP地址相关工具类
 */
public class IpUtil {
	public static String client(HttpServletRequest request) {
		String xff = request.getHeader("x-forwarded-for");
		if (xff == null) {
			xff = request.getRemoteAddr();
		}
		return xff;
	}

	public static String getIpAddr(HttpServletRequest request) {
		String ipAddress = null;
		try {
			ipAddress = request.getHeader("x-forwarded-for");
			if (ipAddress == null || ipAddress.length() == 0 || "unknown".equalsIgnoreCase(ipAddress)) {
				ipAddress = request.getHeader("Proxy-Client-IP");
			}
			if (ipAddress == null || ipAddress.length() == 0 || "unknown".equalsIgnoreCase(ipAddress)) {
				ipAddress = request.getHeader("WL-Proxy-Client-IP");
			}
			if (ipAddress == null || ipAddress.length() == 0 || "unknown".equalsIgnoreCase(ipAddress)) {
				ipAddress = request.getRemoteAddr();
				if (ipAddress.equals("127.0.0.1")) {
					// 根据网卡取本机配置的IP
					InetAddress inet = null;
					try {
						inet = InetAddress.getLocalHost();
					} catch (UnknownHostException e) {
						e.printStackTrace();
					}
					ipAddress = inet.getHostAddress();
				}
			}
			// 对于通过多个代理的情况,第一个IP为客户端真实IP,多个IP按照','分割
			if (ipAddress != null && ipAddress.length() > 15) { // "***.***.***.***".length()
				// = 15
				if (ipAddress.indexOf(",") > 0) {
					ipAddress = ipAddress.substring(0, ipAddress.indexOf(","));
				}
			}
		} catch (Exception e) {
			ipAddress = "";
		}
		// ipAddress = this.getRequest().getRemoteAddr();

		return ipAddress;
	}
}
WxAuthController.java
package com.zking.ssm.wxcontroller;

/**
 * @Autho donkee
 * @Since 2022/6/27
 */

import cn.binarywang.wx.miniapp.bean.WxMaPhoneNumberInfo;
import com.alibaba.fastjson.JSONObject;
import com.zking.ssm.annotation.LoginUser;
import com.zking.ssm.model.UserInfo;
import com.zking.ssm.model.WxLoginInfo;
import com.zking.ssm.model.WxUser;
import com.zking.ssm.service.UserToken;
import com.zking.ssm.service.UserTokenManager;
import com.zking.ssm.service.WxUserService;
import com.zking.ssm.util.JacksonUtil;
import com.zking.ssm.util.ResponseUtil;
import com.zking.ssm.util.UserTypeEnum;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import cn.binarywang.wx.miniapp.api.WxMaService;
import cn.binarywang.wx.miniapp.bean.WxMaJscode2SessionResult;
import javax.servlet.http.HttpServletRequest;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;

/**
 * 鉴权服务
 */
@Slf4j
@RestController
@RequestMapping("/wx/auth")
public class WxAuthController {
    @Autowired
    private WxMaService wxService;
    @Autowired
    private WxUserService userService;
    /**
     * 微信登录
     *
     * @param wxLoginInfo
     *            请求内容,{ code: xxx, userInfo: xxx }
     * @param request
     *            请求对象
     * @return 登录结果
     */
    @PostMapping("login_by_weixin")
    public Object loginByWeixin(@RequestBody WxLoginInfo wxLoginInfo, HttpServletRequest request) {

        //客户端需携带code与userInfo信息
        String code = wxLoginInfo.getCode();
        UserInfo userInfo = wxLoginInfo.getUserInfo();
        if (code == null || userInfo == null) {
            return ResponseUtil.badArgument();
        }
        //调用微信sdk获取openId及sessionKey
        String sessionKey = null;
        String openId = null;
        try {
            long beginTime = System.currentTimeMillis();
            //
            WxMaJscode2SessionResult result = this.wxService.getUserService().getSessionInfo(code);
//            Thread.sleep(6000);
            long endTime = System.currentTimeMillis();
            log.info("响应时间:{}",(endTime-beginTime));
            sessionKey = result.getSessionKey();//session id
            openId = result.getOpenid();//用户唯一标识 OpenID
        } catch (Exception e) {
            e.printStackTrace();
        }

        if (sessionKey == null || openId == null) {
            log.error("微信登录,调用官方接口失败:{}", code);
            return ResponseUtil.fail();
        }else{
            log.info("openId={},sessionKey={}",openId,sessionKey);
        }
        //根据openId查询wx_user表
        //如果不存在,初始化wx_user,并保存到数据库中
        //如果存在,更新最后登录时间
        WxUser user = userService.queryByOid(openId);

        if (user == null) {
            user = new WxUser();
            user.setUsername(openId);
            user.setPassword(openId);
            user.setWeixinOpenid(openId);
            user.setAvatar(userInfo.getAvatarUrl());
            user.setNickname(userInfo.getNickName());
            user.setGender(userInfo.getGender());
            user.setUserLevel((byte) 0);
            user.setStatus((byte) 0);
            user.setLastLoginTime(new Date());
            user.setLastLoginIp(IpUtil.client(request));
            user.setShareUserId(1);

            userService.add(user);

        } else {
            user.setLastLoginTime(new Date());
            user.setLastLoginIp(IpUtil.client(request));
            if (userService.updateById(user) == 0) {
                log.error("修改失败:{}", user);
                return ResponseUtil.updatedDataFailed();
            }
        }
        // token
        UserToken userToken = null;
        try {
            userToken = UserTokenManager.generateToken(user.getId());
        } catch (Exception e) {
            log.error("微信登录失败,生成token失败:{}", user.getId());
            e.printStackTrace();
            return ResponseUtil.fail();
        }
        userToken.setSessionKey(sessionKey);
        log.info("SessionKey={}",UserTokenManager.getSessionKey(user.getId()));
        Map<Object, Object> result = new HashMap<Object, Object>();
        result.put("token", userToken.getToken());
        result.put("tokenExpire", userToken.getExpireTime().toString());
        userInfo.setUserId(user.getId());
        if (!StringUtils.isEmpty(user.getMobile())) {// 手机号存在则设置
            userInfo.setPhone(user.getMobile());
        }
        try {
            DateFormat df = new SimpleDateFormat("yyyy-MM-dd");
            String registerDate = df.format(user.getAddTime() != null ? user.getAddTime() : new Date());
            userInfo.setRegisterDate(registerDate);
            userInfo.setStatus(user.getStatus());
            userInfo.setUserLevel(user.getUserLevel());// 用户层级
            userInfo.setUserLevelDesc(UserTypeEnum.getInstance(user.getUserLevel()).getDesc());// 用户层级描述
        } catch (Exception e) {
            log.error("微信登录:设置用户指定信息出错:"+e.getMessage());
            e.printStackTrace();
        }
        result.put("userInfo", userInfo);


        log.info("【请求结束】微信登录,响应结果:{}", JSONObject.toJSONString(result));

        return ResponseUtil.ok(result);
    }
    /**
     * 绑定手机号码
     *
     * @param userId
     * @param body
     * @return
     */
    @PostMapping("bindPhone")
    public Object bindPhone(@LoginUser Integer userId, @RequestBody String body) {
        log.info("【请求开始】绑定手机号码,请求参数,body:{}", body);

        String sessionKey = UserTokenManager.getSessionKey(userId);
        String encryptedData = JacksonUtil.parseString(body, "encryptedData");
        String iv = JacksonUtil.parseString(body, "iv");
        WxMaPhoneNumberInfo phoneNumberInfo = null;
        try {
            phoneNumberInfo = this.wxService.getUserService().getPhoneNoInfo(sessionKey, encryptedData, iv);
        } catch (Exception e) {
            log.error("绑定手机号码失败,获取微信绑定的手机号码出错:{}", body);
            e.printStackTrace();
            return ResponseUtil.fail();
        }
        String phone = phoneNumberInfo.getPhoneNumber();
        WxUser user = userService.selectByPrimaryKey(userId);
        user.setMobile(phone);
        if (userService.updateById(user) == 0) {
            log.error("绑定手机号码,更新用户信息出错,id:{}", user.getId());
            return ResponseUtil.updatedDataFailed();
        }
        Map<Object, Object> data = new HashMap<Object, Object>();
        data.put("phone", phone);

        log.info("【请求结束】绑定手机号码,响应结果:{}", JSONObject.toJSONString(data));
        return ResponseUtil.ok(data);
    }
    /**
     * 注销登录
     */
    @PostMapping("logout")
    public Object logout(@LoginUser Integer userId) {
        log.info("【请求开始】注销登录,请求参数,userId:{}", userId);
        if (userId == null) {
            return ResponseUtil.unlogin();
        }
        try {
            UserTokenManager.removeToken(userId);
        } catch (Exception e) {
            log.error("注销登录出错:userId:{}", userId);
            e.printStackTrace();
            return ResponseUtil.fail();
        }

        log.info("【请求结束】注销登录成功!");
        return ResponseUtil.ok();
    }
}
 WxHomeController.java
package com.zking.ssm.wxcontroller;

import com.zking.ssm.mapper.InfoMapper;
import com.zking.ssm.model.Info;
import com.zking.ssm.util.ResponseUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * @Autho donkee
 * @Since 2022/6/29
 */
@RestController
@RequestMapping("/wx/home")
public class WxHomeController {
    @Autowired
    private InfoMapper infoMapper;
    @RequestMapping("/index")
    public Object index(Info info) {
        List<Info> infoList = infoMapper.list(info);
        Map<Object, Object> data = new HashMap<Object, Object>();
        data.put("infoList",infoList);
        return ResponseUtil.ok(data);
    }
}
 WxInfoController.java
package com.zking.ssm.wxcontroller;

import com.zking.ssm.mapper.InfoMapper;
import com.zking.ssm.model.Info;
import com.zking.ssm.util.ResponseUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * @Autho donkee
 * @Since 2022/7/29
 */
@SuppressWarnings("all")
@RestController
@RequestMapping("/wx/info")
public class WxInfoController {
    @Autowired
    private InfoMapper infoMapper;
    @RequestMapping("/list")
    public Object list (Info info){
        List<Info> list = infoMapper.list(info);
        Map<Object, Object> data = new HashMap<Object, Object>();
        data.put("infoList",list);
        return ResponseUtil.ok(data);
    }
}
 WxUserController.java
package com.zking.ssm.wxcontroller;

import java.math.BigDecimal;
import java.util.HashMap;
import java.util.Map;
import java.util.Random;

import com.zking.ssm.annotation.LoginUser;
import com.zking.ssm.model.WxUser;
import com.zking.ssm.service.UserTokenManager;
import com.zking.ssm.service.WxUserService;
import com.zking.ssm.util.ResponseUtil;
import lombok.extern.slf4j.Slf4j;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;

import com.alibaba.fastjson.JSONObject;

import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;


/**
 * 用户服务
 */
@Slf4j
@RestController
@RequestMapping("/wx/user")
@Validated
public class WxUserController {
	@Autowired
	private WxUserService userService;

	/**
	 * 用户个人页面数据
	 * <p>
	 * @param userId
	 *            用户ID
	 * @return 用户个人页面数据
	 */
	@GetMapping("index")
	public Object list(@LoginUser Integer userId, @RequestHeader("X-OA-token") String token) {
		log.info("【请求开始】用户个人页面数据,请求参数,userId:{}", userId);
		log.info("【请求开始】用户个人页面数据,请求参数,token:{}", token);
		if (userId == null) {
			log.error("用户个人页面数据查询失败:用户未登录!!!");
			return ResponseUtil.unlogin();
		}
		WxUser wxUser = userService.selectByPrimaryKey(userId);
		Map<Object, Object> data = new HashMap<Object, Object>();
		data.put("metting_pubs", wxUser.getUserLevel());
		data.put("metting_joins",wxUser.getUserLevel());
		return ResponseUtil.ok(data);
	}

}

        这就是微信授权登陆小程序所涉及的后台代码。

四、解决微信名称含特殊标签符号的存储问题

        我们的一些微信名称有时会包含一些特殊符号的名称,当我们存储到数据库中时会出现异常,接下来就来解决一下这个问题。

 1. 配置my.ini文件

        找到我们安装mysql的数据库下的my.ini文件,在文件中修改default-character-set和character-set-server的属性值修改为utf8mb4

 

2. 重启我们的数据库服务

        在我们的服务中找到mysql服务重新启动即可。 

 3. 点击多账号调试

        由此我们可以看到在数据库中我们成功将特殊字符存储到数据库中了。 

        本期的博客分享到此结束,谢谢到家