阅读 1748
实现微信小程序登录流程

实现微信小程序登录流程

小程序登录

登录流程时序(重点)

说明

1.调用wx.login()获取 临时登录凭证code ,并回传到开发者服务器

2.调用auth.code2Session接口,换取 用户唯一标识 OpenID 和 会话密钥 session_key

之后开发者服务器可以根据用户标识来生成自定义登录态,用于后续业务逻辑中前后端交互时识别用户身份。

注意

  1. 会话密钥 session_key 是对用户数据进行加密签名的密钥。为了应用自身的数据安全,开发者服务器不应该把会话密钥下发到小程序,也不应该对外提供这个密钥

  2. 临时登录凭证 code 只能使用一次

前置知识点 

1.wx.checkSession(Object object)

检查登录态是否过期。 

通过 wx.login 接口获得的用户登录态拥有一定的时效性。用户越久未使用小程序,用户登录态越有可能失效。反之如果用户一直在使用小程序,则用户登录态一直保持有效。具体时效逻辑由微信维护,对开发者透明。开发者只需要调用 wx.checkSession 接口检测当前用户登录态是否有效。

登录态过期后开发者可以再调用 wx.login 获取新的用户登录态。调用成功说明当前 session_key 未过期,调用失败说明 session_key 已过期。

参数(红色框为常用)

示例代码
wx.checkSession({
    successs() {
        // sesssion_key未过期,并且在本生命周期一直有效
    },
    fail() {
        // session_key已经失效,需要重新执行登录流程
        wx.login() // 重新登录
    }
})
复制代码

2.wx.login(Object object)

调用接口获取登录凭证(code)。通过凭证进而换取用户登录态信息,包括用户的唯一标识(openid)及本次登录的会话密钥(session_key)等。用户数据的加解密通讯需要依赖会话密钥完成。

参数(红色框为常用)

示例代码
wx.login({
    success(res) {
        if(res.code) {
            // 发起网络请求
            wx.request({
                url: 'https://test.com/onLogin',
                data: {
                    code: res.code
                }
            })
        } else {
            console.log('登录失败!' + res.errMsg)
        }
    }
})
复制代码

3.auth.code2Session

本接口应在服务器端调用。

登录凭证校验。通过wx.login接口获得临时登录凭证 code 后传到开发者服务器调用此接口完成登录流程。

请求地址
GET api.weixin.qq.com/sns/jscode2…

请求参数(红色框为必备)

返回值(红色框为常用)

4.wx.request(Object object)

示例代码
wx.request({
    url: 'test.php', // 示例接口
    data: {
        x:'',
        y: ''
    },
    header: {
        'content-type': 'applicatin/json' //默认值
    },
    success(res) {
        console.log(res.data)
    }
})
复制代码

实际代码示例流程

后端以Egg.js示例

第一步:(小程序)新建Api

目录

代码实例
const apiHttp = "http://192.168.0.104:7001"; // 后端IP地址

// 请求
function request(url, method, data, header) {
    data = data || {};
    header = header || {};
    let token = wx.getStorageSync("token"); // 在本地缓存中获取token
    if(token) {
        if(!header || !header["Authorization"]) {
            header["Authorization"] = token;
        }
    }
    wx.showNavigationBarLoading(); // 在当前页面显示导航条加载动画
    let promise = new Promise(function(resolve, reject) {
        wx.request({
            url: apiHttp + url,
            header: header,
            data: data,
            method: method,
            success: function(res) {
                // 开发者服务器返回的 HTTP 状态码
                if(res.statusCode == 401) { // token过期,重新执行登录流程
                    reLogin().then(() => {
                        resolve();
                    }).catch(reason => {
                        console.log(reason);
                    })
                }
                resolve(res);
            },
            fail: reject,
            complete: function() {
                wx.hideNavigationBarLoading();
            }
        })
    })
    return promise;
}

// 重新登录
function reLogin() {
    return new Promise((resolve, reject) => {
        // 先移除已存在的token
        wx.removeStorageSync('token');
        wx.showToast({
            title: '登录信息过期',
            icon: "none",
            duration: 1000 // 持续时间
        })
        setTimeout(() => {
            wx.showLoading({ // 显示 loading 提示框。需主动调用 wx.hideLoading 才能关闭提示框
                title: '重新登录中',
                mask: true, // 是否显示透明蒙层,防止触摸穿透
                success: function() {
                    wx.login({
                        success: res => {
                            request('/user/login',"POST",{
                                code: res.code
                            }).then(res => {
                                wx.hideLoading();
                                if(res.statusCode == 200) {
                                    let token = res.data.token;
                                    wx.setStorageSync('token',token);
                                    wx.showToast({
                                        title: '登陆成功',
                                        icon: 'success'
                                    })
                                    resolve();
                                } else {
                                    reject(res.data.msg);
                                }
                            })
                        }
                    })
                }
            })
        }, 1000)
    })
}

module.exports = {
    apiHttp: apiHttp,
    "post": function(url, data, header) {
        return request(url, "POST", data, header);
     }
}
复制代码

第二步:(小程序)app.js

const request = require('/api/index.js');
App({
    login: function() {
        const that = this;
        return new Promise((resolve, reject) => {
            wx.checkSession({
                success() {
                    // session_key 未过期,并且在本生命周期一直有效;
                    resolve();
                },
                fail() {
                    // session_key 已经失效,需要重新执行登录流程
                    wx.login({
                        success: res => {
                            // 发送res.code到后台换取openId,sessionKey,unionId
                            request.post('/user/login', {
                                code: res.code
                            }).then(res => {
                                let token = res.data.token;
                                wx.setStorageSync('token', token);
                                resolve();
                            })
                        }
                    })
                }
            })
        })
    }
})
复制代码

第三步:(后端Egg)router.js

**
**

'use strict';

/**
 * @param {Egg.Application} app - egg application
 */
module.exports = app => {
    const { router, controller } = app;
    // 用户登录
    router.post('/user/login', controller.user.login);
}
复制代码

第四步:(后端Egg)controller/user.js

'use strict';

const Controller = require('egg').Controller;

class UserController extends Controller {
    async login() {
        const { ctx } = this;
        /**小程序调用wx.login()获取临时登录凭证code ,并传给后台,后台以code换取 用户唯一标识openid 和 会话密钥session_key*/
        const code = ctx.request.body.code;
        const resData = await ctx.service.user.login(code);
        // 判断当前openid是否存在数据库,没有则创建
        const userInfo = await ctx.model.User.findByPk(resData.openid,{raw: true});
        if(!userInfo) {
            await ctx.service.user.create(resData);
        }
        
        const token = await this.app.jwt.sign({ openid:resData.openid }, this.app.config.jwt.secret, { expiresIn: "24h" });
        ctx.status = 200;
        ctx.body = {
            msg: '登录成功!',
            token
        }
    }
}

module.exports = UserController;
复制代码

第五步:(后端Egg)service/user.js

'use strict';
// 引入axios插件
const axios = require('axios');

const Service = require('egg').Service;

class UserService extends Service {
    // 登录
    async login(code) {
        const resData = await axios.get('https://api.weixin.qq.com/sns/jscode2session?appid=' + this.config.appid + '&secret=' + this.config.secret + '&js_code=' + code + '&grant_type=authorization_code');
        if(resData.status == 200) {
            return resData.data
        } else {
            this.ctx.status = 500;
            this.ctx.body = {
                msg: '服务器发生错误'
            }
        }
    }
    
    // 创建用户
    async create(userInfo) {
        await this.ctx.model.User.create(userInfo);
        return;
    }
}
复制代码

第六步:(后端Egg)model/user.js

//用户表
module.exports = app => {
    const { STRING } = app.Sequelize;
    const User = app.model.define('user', {
        openid: { type:STRING(50), allowNull:false, primaryKey:true, unique:true, comment:"用户唯一标识" },
        session_key: { type:STRING(50), allowNull:false, comment:"会话密钥" },
        nickName: { type:STRING(30), comment:"发布者名" },
        avatarUrl: { type:STRING(150), comment:"用户头像" },
        signature: { type:STRING(30), comment:"个性签名" }
    });
    return User;
};
复制代码
文章分类
前端