微信授权登录工作流程(二)

前言
小程序可以通过微信官方提供的登录能力方便地获取微信提供的用户身份标识,快速建立小程序内的用户体系

一.微信授权登录工作流程


1.理论叙述
触发授权登录: 用户在小程序中触发登录操作,通常通过点击登录按钮或执行相关操作。

授权弹窗: 小程序弹出授权登录的弹窗,要求用户授权小程序访问其微信账号信息。

用户选择授权: 用户可以选择授权或拒绝授权。如果用户拒绝,登录流程终止。

获取登录凭证: 如果用户选择授权,小程序会获得一个临时的 code。

向后台服务器发送登录凭证: 小程序将 code 发送到自己的后台服务器。

后台服务器与微信服务器通信: 后台服务器使用 code 向微信服务器发送请求,请求中包括小程序的 AppID、AppSecret 和 code。

微信服务器响应: 微信服务器会验证 code 的合法性,并在验证通过后,返回用户的唯一标识符 openid 和会话密钥 session_key。

后台服务器验证身份: 后台服务器通常会验证 openid 和 session_key 的有效性,以确保用户的身份合法。这一步也可以用于防止滥用和欺诈。

用户数据加解密: 小程序使用 session_key 来加密和解密用户的数据,以保护用户的隐私。

执行业务逻辑: 一旦用户登录成功,小程序可以执行与用户身份相关的业务逻辑,如显示个性化内容、保存用户操作记录等。

保护用户隐私: 小程序必须尊重用户隐私,仅获取有限的用户信息,不包括敏感信息。

用户退出: 提供用户注销或取消授权的选项,以允许用户随时退出登录。

2.图文详解

二.微信授权登录案例讲解
1.准备配置
1.1 前端接口调用地址
// 以下是业务服务器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’ //绑定微信手机号
};

1.2 封装微信调用request
/**

  • 封封微信的的request
    */
    function request(url, data = {}, method = “GET”) {
    return new Promise(function (resolve, reject) {
    wx.request({
    url: url,
    data: data,
    method: method,
    timeout:3000,
    header: {
    ‘Content-Type’: ‘application/json’,
    ‘X-OA-Token’: wx.getStorageSync(‘token’)
    },
    success: function (res) {
    if (res.statusCode == 200) {
    if (res.data.errno == 501) {
    // 清除登录相关内容
    try {
    wx.removeStorageSync(‘userInfo’);
    wx.removeStorageSync(‘token’);
    } catch (e) {
    // Do something when catch error
    }
    // 切换到登录页面
    wx.navigateTo({
    url: ‘/pages/auth/login/login’
    });
    } else {
    resolve(res.data);
    }
    } else {
    reject(res.errMsg);
    } },
    fail: function (err) {
    reject(err)
    }
    })
    });
    }

function redirect(url) {
//判断页面是否需要登录
if (false) {
wx.redirectTo({
url: ‘/pages/auth/login/login’
});
return false;
} else {
wx.redirectTo({
url: url
});
}
}

1.3 配置数据库及小程序和小程序密钥

2.点击触发授权登录

3.登录流程
代码流程解释:授权弹窗后会获取我们的用户信息,通过登录判断我们是否登录过,若没有,则调用微信登录的函数进行登录操作,首先,将用户信息以参数的形式进行传递,通过用户信息向远程服务器(后端)发送请求,并携带授权登录时的临时code值,再通过调用微信的授权登录接口,将code值进行传入,然后返回一个结果对象,通过当前对象获取我们的session_key和openid(唯一标识),通过当前的openid可以获取到我们的用户信息判断我们是否之前登录过当前小程序,并将登录信息存储到数据库中,且将我们的登录时间进行更新

2.1 前端函数代码
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(‘微信登录失败’);
}
});
},

2.2 后端逻辑代码

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

2.3 token
由上述代码可知,我们的末尾中获取当前token令牌,由于我们前面通过openid获取到所有的用户信息,包括我们的用户id,然后我们就可以使用这个用户id去生成一个专属的token令牌,并响应到前端,存储到我们的请求头中,具体了解token令牌详解请看往期博客,有详解一篇文章让你了解“JWT“

@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 data = new HashMap();
data.put(“metting_pubs”, wxUser.getUserLevel());
data.put(“metting_joins”,wxUser.getUserLevel());
return ResponseUtil.ok(data);
}
4.效果演示

3.1 登录时间更新

三.退出登录
前端向后端发送退出登录请求,后端通过判断userid将我们的token令牌进行销毁,防止被他人利用token令牌向服务器发送恶意请求,保证安全性

/**
* 注销登录
*/
@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();
}

今天的分享到这里就结束了,感谢各位大大的观看,各位大大的三连是博主更新的动力,感谢谢谢谢谢谢谢谢谢各位的支持!!!!!