微信小程序之微信授权登入及授权的流程讲解

简介: 微信小程序之微信授权登入及授权的流程讲解

一、微信登录流程讲解

1. 微信登录图解



2.认识openid,unionid 和code


2.1 openid


openid是用来唯一标识用户的一个字符串。在微信小程序中,每个用户的openid都是唯一的。通过openid,小程序可以获取用户的基本信息,如头像、昵称等。


注意: 同一个用户在不同的小程序中拥有不同的openid。因此,在开发小程序时,不能使用openid来进行用户的唯一性判断。


2.2 unionid


unionid是在用户绑定同一微信开放平台账号下的多个应用时,用来唯一标识用户的一个字符串。如果用户在多个小程序中使用同一个微信号进行登录授权,那么这些小程序中的unionid都是相同的。


注意: 用户的unionid只有在用户将多个应用绑定到同一个微信开放平台账号下时才会生成。因此,如果用户没有绑定多个应用,那么小程序将无法获取用户的unionid。


2.3 code


code是用户登录凭证,由微信服务器颁发给小程序。在用户授权登录后,小程序可以通过调用微信登录接口获取用户的code。然后,通过code向微信服务器请求用户的openid和session_key等信息。


注意: 每个code只能使用一次,且有效期为5分钟。因此,在使用code进行登录时,需要及时将其转换成用户的openid和session_key等信息,以免出现code过期的情况


openid、unionid和code是微信小程序登录授权中非常重要的三个参数,了解这些参数的作用和用法,有助于开发者更好地设计和开发小程序登录授权功能。


解释:


通过wx.login()获取code。

将这个code发送给后端,后端会返回一个token,这个token将作为你身份的唯一标识。

将token通过wx.setStorageSync()保存在本地存储。

用户下次进入登录界面时,会先通过wx.getStorageSync() 方法判断token是否有值,如果有值,则可以请求其它数据,如果没有值,则进行登录操作。

二、登录方法讲解

wxLogin


调用接口获取登录凭证(code)。通过凭证进而换取用户登录态信息,包括用户在当前小程序的唯一标识(openid)、微信开放平台账号下的唯一标识(unionid,若当前小程序已绑定到微信开放平台账号)及本次登录的会话密钥(session_key)等。用户数据的加解密通讯需要依赖会话密钥完成。

d7d8efbe62ae46cea192b6aa7f88f8b4.png

效果图:


该方法对于用户的体验及安全性问题有所欠缺,点击就直接获取到了用户的信息,进行了一个显示,这个方法官方已经不推荐了。

  • wx.getUserProfile



获取用户信息。页面产生点击事件(例如 buttonbindtap 的回调中)后才可调用,每次请求都会弹出授权窗口,用户同意后返回 userInfo。该接口用于替换 wx.getUserInfo,详见 用户信息接口调整说明

效果图:




05f3a49879954f4ca437019f737038af.png


需要用户允许才能进行下一步,安全性大大提高

三、数据交互授权登入

1. 前端

api.js

// 以下是业务服务器API地址
 // 本机开发API地址
var WxApiRoot = 'http://localhost:8080/oapro/wx/';
// 测试环境部署api地址
// var WxApiRoot = 'http://192.168.191.1:8080/oapro/wx/';
// 线上平台api地址
//var WxApiRoot = 'https://www.oa-mini.com/demo/wx/';
module.exports = {
  IndexUrl: WxApiRoot + 'home/index', //首页数据接口
  SwiperImgs: WxApiRoot+'swiperImgs',
  MettingInfos: WxApiRoot+'meeting/list',
  AuthLoginByWeixin: WxApiRoot + 'auth/login_by_weixin', //微信登录
  UserIndex: WxApiRoot + 'user/index', //个人页面用户相关信息
  AuthLogout: WxApiRoot + 'auth/logout', //账号登出
  AuthBindPhone: WxApiRoot + 'auth/bindPhone' //绑定微信手机号
};


个人中心

index.wxml

<view class="page-container">
    <view class="user-info-container">
        <view class="user-info"  bindtap="goLogin">
            <image class="user-img" mode="scaleToFill" src="{{userInfo.avatarUrl}}" />
            <text class="user-info-name">{{userInfo.nickName}}</text>
        </view>
        <image class="user-update" src="/static/tabBar/component.png" bindtap='goPages' data-url='/pages/ucenter/user/user'/>
    </view>
    <view class="boundary" />
    <view class="cells-container">
        <view class="cell-wrap">
            <image class="cell-icon" src="/static/tabBar/sdk.png" />
            <text class="cell-text">我主持的会议</text>
            <view class="cell-right">
                <view class="cell-list-num">{{metting_pubs}}</view>
                <view class="cell-arrow"></view>
            </view>
        </view>
        <view class="cell-wrap">
            <image class="cell-icon" src="/static/tabBar/sdk.png" />
            <text class="cell-text">我参与的会议</text>
            <view class="cell-right">
                <view class="cell-list-num">{{metting_joins}}</view>
                <view class="cell-arrow"></view>
            </view>
        </view>
    </view>
    <view class="boundary" />
    <view class="cells-container">
        <view class="cell-wrap">
            <image class="cell-icon" src="/static/tabBar/sdk.png" />
            <text class="cell-text">我发布的投票</text>
            <view class="cell-right">
                <view class="cell-list-num">1</view>
                <view class="cell-arrow"></view>
            </view>
        </view>
        <view class="cell-wrap">
            <image class="cell-icon" src="/static/tabBar/sdk.png" />
            <text class="cell-text">我参与的投票</text>
            <view class="cell-right">
                <view class="cell-list-num">10</view>
                <view class="cell-arrow"></view>
            </view>
        </view>
    </view>
    <view class="boundary" />
    <view class="cells-container">
        <view class="cell-wrap">
            <image class="cell-icon" src="/static/tabBar/template.png" />
            <text class="cell-text">消息</text>
            <view class="cell-right">
                <view class="cell-list-num"></view>
                <view class="cell-arrow"></view>
            </view>
        </view>
        <view class="cell-wrap">
            <image class="cell-icon" src="/static/tabBar/component.png" />
            <text class="cell-text">设置</text>
            <view class="cell-right">
                <view class="cell-list-num"></view>
                <view class="cell-arrow"></view>
            </view>
        </view>
    </view>
</view>


index.js

// pages/ucenter/index/index.js
var util = require('../../../utils/util.js');
var api = require('../../../config/api.js');
const app = getApp();
Page({
    /**
     * 页面的初始数据
     */
    data: {
        userInfo: {
            nickName: '点击登录',
            avatarUrl: '/static/images/avatar.png'
        },
        hasLogin: false,
        metting_pubs: '',
        metting_joins: ''
    },
    /**
     * 生命周期函数--监听页面加载
     */
    onLoad(options) {
    },
    /**
     * 生命周期函数--监听页面显示
     */
    onShow() {
        this.getUserInfo();
    },
    getUserInfo() {
      // console.log('ucenter.index.app.globalData.hasLogin='+app.globalData.hasLogin)
        //获取用户的登录信息
        if (app.globalData.hasLogin) {
            let userInfo = wx.getStorageSync('userInfo');
            this.setData({
                userInfo: userInfo,
                hasLogin: true
            });
            //查询个人统计信息
            util.request(api.UserIndex).then(res => {
                if (res.errno === 0) {
                    this.setData({
                        metting_pubs: res.data.metting_pubs,
                        metting_joins: res.data.metting_joins
                    });
                }
            });
        }
    },
    goLogin() {
        if (!this.data.hasLogin) {
            wx.navigateTo({
                url: "/pages/auth/login/login"
            });
        }
    },
    /**
     * 页面跳转
     */
    goPages: function (e) {
        if (this.data.hasLogin) {
            wx.navigateTo({
                url: e.currentTarget.dataset.url
            });
        } else {
            wx.navigateTo({
                url: "/pages/auth/login/login"
            });
        };
    }
})


user.wxml

<!--pages/ucenter/user/user.wxml-->
<form bindsubmit="formSubmit">
    <view class='personal-data'>
        <view class='list'>
            <view class='item acea-row row-between-wrapper'>
                <view>头像</view>
                <view class='pictrue'>
                    <image src='{{userInfo.avatarUrl}}'></image>
                </view>
            </view>
            <view class='item acea-row row-between-wrapper'>
                <view>名字</view>
                <view class='input'><input type='text' disabled='true' name='nickname' value='{{userInfo.nickName}}'></input></view>
            </view>
            <view class='item acea-row row-between-wrapper'>
                <view>手机号码</view>
                <button name='phone' class='phoneW' value='{{userInfo.phone}}' wx:if="{{!userInfo.phone}}" bindgetphonenumber="getPhoneNumber" hover-class='none' open-type='getPhoneNumber'>
                    点击获取
                </button>
                <view class='input acea-row row-between-wrapper' wx:else>
                    <input type='text' disabled='true' name='phone' value='{{userInfo.phone}}' class='id'></input>
                    <text class='iconfont icon-suozi'></text>
                </view>
            </view>
            <view class='item acea-row row-between-wrapper'>
                <view>ID号</view>
                <view class='input acea-row row-between-wrapper'>
                    <input type='text' value='1000{{userInfo.userId}}' disabled='true' class='id'></input>
                    <text class='iconfont icon-suozi'></text>
                </view>
            </view>
        </view>
        <button class='modifyBnt' bindtap="exitLogin">退 出</button>
    </view>
</form>

user.js

var util = require('../../../utils/util.js');
var api = require('../../../config/api.js');
var user = require('../../../utils/user.js');
var app = getApp();
Page({
  /**
   * 页面的初始数据
   */
  data: {
    userInfo: {},
    hasLogin: false,
    userSharedUrl: ''
  },
  /**
      * 生命周期函数--监听页面加载
   */
  onLoad: function (options) {
  },
  onShow: function () {
    let that = this;
    //获取用户的登录信息
    let userInfo = wx.getStorageSync('userInfo');
    this.setData({
      userInfo: userInfo,
      hasLogin: true
    });
  },
  getPhoneNumber: function (e) {
      console.log(e);
    let that = this;
    if (e.detail.errMsg !== "getPhoneNumber:ok") {
      // 拒绝授权
      return;
    }
    if (!this.data.hasLogin) {
      wx.showToast({
        title: '绑定失败:请先登录',
        icon: 'none',
        duration: 2000
      });
      return;
    }
    util.request(api.AuthBindPhone, {
      iv: e.detail.iv,
      encryptedData: e.detail.encryptedData
    }, 'POST').then(function (res) {
      if (res.errno === 0) {
        let userInfo = wx.getStorageSync('userInfo');
        userInfo.phone = res.data.phone;//设置手机号码
        wx.setStorageSync('userInfo', userInfo);
        that.setData({
          userInfo: userInfo,
          hasLogin: true
        });
        wx.showToast({
          title: '绑定手机号码成功',
          icon: 'success',
          duration: 2000
        });
      }
    });
  },
  exitLogin: function () {
    wx.showModal({
      title: '',
      confirmColor: '#b4282d',
      content: '退出登录?',
      success: function (res) {
        if (!res.confirm) {
          return;
        }
        util.request(api.AuthLogout, {}, 'POST');
        app.globalData.hasLogin = false;
        wx.removeStorageSync('token');
        wx.removeStorageSync('userInfo');
        wx.reLaunch({
          url: '/pages/index/index'
        });
      }
    })
  }
})

2. 后端代码

WxAuthController :

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();
    }
}

application.yml

application.yml 文件中进行配置后台的数据库及微信小程序的AppID(小程序ID)及AppSecret(小程序密钥),来帮助访问微信的接口服务。

server:
  port: 8080 #指服器端口号
  servlet:
    context-path: /oapro
spring:
  datasource:
    #type连接池类型 DBCP,C3P0,Hikari,Druid,默认为Hikari
    type: com.zaxxer.hikari.HikariDataSource
    driver-class-name: com.mysql.jdbc.Driver
    url: jdbc:mysql://localhost:3306/mybatis_oapro?useUnicode=true&characterEncoding=UTF-8&useSSL=false
    username: root
    password: 123456
mybatis:
  mapper-locations: classpath*:mapper/*.xml #指定mapper文件位置
  type-aliases-package: com.CloudJun.ssm.model #指定自动生成别名所在包
logging:
  level:
    root:  info
    org.springframework:  info
    org.mybatis:  ERROR
    com.CloudJun.ssm.mapper: debug
oa:
  wx:
    app-id: # 填写AppID(小程序ID)
    app-secret: # 填写AppSecret(小程序密钥)
    msgDataFormat: JSON

效果图:


目录
相关文章
|
7天前
|
存储 小程序 前端开发
【微信小程序 - 工作实战分享】1.微信小程序发送手机短信验证码(阿里云)
【微信小程序 - 工作实战分享】1.微信小程序发送手机短信验证码(阿里云)
16 0
|
2天前
|
小程序
【亲测有效】支持横竖屏 微信小程序video禁止进度条拖动,微信小程序遮罩进度条,
【亲测有效】支持横竖屏 微信小程序video禁止进度条拖动,微信小程序遮罩进度条,
6 1
【亲测有效】支持横竖屏 微信小程序video禁止进度条拖动,微信小程序遮罩进度条,
|
11天前
|
JSON 安全 Java
Spring Security 6.x 微信公众平台OAuth2授权实战
上一篇介绍了OAuth2协议的基本原理,以及Spring Security框架中自带的OAuth2客户端GitHub的实现细节,本篇以微信公众号网页授权登录为目的,介绍如何在原框架基础上定制开发OAuth2客户端。
35 4
Spring Security 6.x 微信公众平台OAuth2授权实战
|
2天前
|
小程序 前端开发
【非常全】微信小程序下载图片到相册,微信小程序自动获取分享图片到相册
【非常全】微信小程序下载图片到相册,微信小程序自动获取分享图片到相册
17 3
|
15天前
|
JavaScript Java 测试技术
基于SpringBoot+Vue+uniapp的微信食堂线上订餐小程序的详细设计和实现(源码+lw+部署文档+讲解等)
基于SpringBoot+Vue+uniapp的微信食堂线上订餐小程序的详细设计和实现(源码+lw+部署文档+讲解等)
|
17天前
|
XML JSON 数据安全/隐私保护
如何使用Fiddler抓取APP接口和微信授权网页源代码
Fiddler是一款强大的抓包工具,用于捕获HTTP/HTTPS流量,包括手机APP和微信授权页面的数据。下载安装Fiddler后,需设置电脑代理,如端口8888,并在手机上配置相同代理,确保两者在同一局域网。通过安装Fiddler证书,可解密HTTPS请求。在手机上打开目标应用或网页,Fiddler将显示请求详情,便于接口调试和数据查看。
21 0
如何使用Fiddler抓取APP接口和微信授权网页源代码
|
20天前
|
小程序 前端开发 JavaScript
微信外卖小程序
微信外卖小程序
|
20天前
|
小程序 前端开发 JavaScript
微信小程序|英语学习交流平台小程序
微信小程序|英语学习交流平台小程序
|
20天前
|
小程序 前端开发 JavaScript
微信小程序|springboot“口腔助手”小程序的设计与实现
微信小程序|springboot“口腔助手”小程序的设计与实现
|
2天前
|
移动开发 小程序 前端开发
php + h5使用 scheme页面跳转微信小程序-其他浏览器一键跳转到微信并打开小程序
php + h5使用 scheme页面跳转微信小程序-其他浏览器一键跳转到微信并打开小程序
4 0