https://developers.weixin.qq.com/miniprogram/dev/framework/open-ability/login.html 微信小程序官方API
说明:
调用 wx.login() 获取 临时登录凭证code ,并回传到开发者服务器。调用 code2Session 接口,换取 用户唯一标识 OpenID 和 会话密钥 session_key。之后开发者服务器可以根据用户标识来生成自定义登录态,用于后续业务逻辑中前后端交互时识别用户身份。
注意:
会话密钥 session_key 是对用户数据进行 加密签名 的密钥。为了应用自身的数据安全,开发者服务器不应该把会话密钥下发到小程序,也不应该对外提供这个密钥。临时登录凭证 code 只能使用一次
这里仅按照官方推荐的规范来
0. 前置条件
一共有三端:
- 微信小程序客户端
- 第三方服务器端
- 微信服务器端
1.检测登录是否有效,如果无效则清楚登录信息(wx.checkSession);
2.调用接口获取登录凭证(code)(wx.login);通过凭证进而换取用户登录态信息,包括用户的唯一标识(openid)及本次登录的会话密钥(session_key)等。用户数据的加解密通讯需要依赖会话密钥完成。
3. 客户端获得code,并将code传给第三方服务端
微信小程序端调用wx.login,获取登录凭证(code),并调用接口,将code发送到第三方客户端
4. 第三方服务端用code换session_key和openid
小程序端将code传给第三方服务器端,第三方服务器端调用接口,用code换取session_key和openid
5. 第三方服务端生成新的session(3rd_session)
第三方服务器端拿到请求回来的session_key和openid,先留着,不能给客户端;然后用操作系统提供的真正随机数算法生成一个新的session,叫3rd_session
6. 第三方服务端建立对应关系,并存储
将3rd_session作为key,微信服务端返回的session_key和openid作为值,保存起来
7. 第三方服务端将3rd_session发送到客户端
客户端只拿到3rd_session就够了,大人说话小孩别插嘴,小程序不需要知道session_key和openid
8. 正常请求
小程序每次请求都将3rd_session放在请求头里,第三方服务端解析判断合法性,并进行正常的逻辑处理。
下面就封装一个小程序授权登录的组件
目录结构
1.server.js
//检测登录是否有效,如果无效则清除登录信息module.exports = {checkLogs() { let utoken = wx.getStorageSync("userInfo").utoken; if (typeof utoken == "undefined") { return false; } this.sendRequest({ url: '', // //检测登录是否有效的接口 data: { utoken }, method: 'POST', success: res => { if (res.data.code != 200) { wx.removeStorageSync('userInfo'); } }, fail: () => { wx.removeStorageSync('userInfo'); } }) }, //这里使用了iview框架,全局控制handleShow方法,授权登录的显示login: function() { const selector = '#login' const pages = getCurrentPages(); const ctx = pages[pages.length - 1]; const componentCtx = ctx.selectComponent(selector); if (!componentCtx) { console.error('无法找到对应的组件,请按文档说明使用组件'); return null; } componentCtx.handleShow(); }}app.js
var server = require('./utils/server');App({ onLaunch: function() { server.checkLogs();//全局调用checkLogs(),检查登录是否失效 }, globalData: {}})login.wxml
<!--component/login/login.wxml--><view class='fixBox' catchtouchmove='touchMove' catchtap='handleHide' wx:if="{{visible}}"> <form report-submit="true"> <button type='primary' form-type='submit' open-type="getUserInfo" catchtap='cantchTap' bindgetuserinfo="getUserInfo">一键授权</button> </form></view> <i-message id="message"/>注意:wx.authorize({scope: "scope.userInfo"}),无法弹出授权窗口,请使用 <button open-type="getUserInfo"/>login.js
// component/login/login.jsconst server = require('../../utils/server.js');Component({ properties: { }, data: { formid: null, visible: false }, methods: { handleShow() { this.setData({ visible: true }) }, handleHide() { this.setData({ visible: false }) }, touchMove() { return false; }, cantchTap() { return false; }, getUserInfo(res) { if (res.detail.errMsg == 'getUserInfo:ok') { let userInfo = { ...res.detail.userInfo } wx.login({ success: e => { let code = e.code; //调用wx.login,获取登录凭证(code),并调用接口,将code发送到第三方客户端 server.sendRequest({ url: '', //小程序端将code传给第三方服务器端,第三方服务器端调用接口,用code换取session_key和openid data: { encryptedData: res.detail.encryptedData, iv: res.detail.iv, code: code }, method: 'POST', success: res => { if (res.data.code == 200) { userInfo = { ...userInfo, ...res.data.result } console.log(userInfo); console.log(res.data.result) wx.setStorageSync('userInfo', userInfo); //授权成功 this.triggerEvent('login', { status: 1 }) this.$Message({ content: '登录成功', type: "success" }) this.handleHide(); } else { this.triggerEvent('login', { status: 0 }) this.$Message({ content: '登录失败', type: 'error' }); this.handleHide(); } } }) } }) } else { this.triggerEvent('login', { status: 0 }) this.$Message({ content: '登录失败', type: 'error' }); this.handleHide(); } }, $Message(options) { //把iview框架里的方法抽取出来 const componentCtx = this.selectComponent("#message"); componentCtx.handleShow(options); } }})login.json
{ "component": true, "usingComponents": { "i-message": "/component/iview/message/index" //引用iview框架里全局提示框 }}login.wxss
index.wxml
<view class='head'> <block wx:if="{{userInfo}}"> <image class="avatarImg" src="{{userInfo.avatarUrl}}"></image> <view class="nickname">{{userInfo.nickName}}</view> </block> <block wx:else> <image class="avatarImg" bindtap="login" src="/images/avatar.png"></image> <view class="nickname" bindtap="login">未登录</view> </block></view><view class="order"> <view class="list orderTitle"> <view class="listTitle">我的订单</view> <view class="readMore" bindtap='seeMore'> <text>查看更多</text> <image class="toRight" src="/images/toRight.png"></image> </view> </view></view><login id="login" bind:login="onLogin"></login>index.js
const server = require('../../utils/server.js');Page({ data: { useInfo: null }, onLoad: function(options) {}, onShow: function() { }, onLogin(res) { // 授权成功的回调,根据子组件传过来的status if (res.detail.status == 1) { let userInfo = wx.getStorageSync('userInfo'); this.setData({ userInfo }) } }, navigateTo(option) { // 封装一个具有判断是否授权登录的跳转方法 if (wx.getStorageSync("userInfo")) { wx.navigateTo(option); } else { server.login(); } }, seeMore() { this.navigateTo({ url: '/pages/order/orderList/orderList' }) },})index.wxss
page { background-color: #eee;}.head { width: 100%; height: 250rpx; background-color: #fff; box-sizing: border-box; padding: 0 20rpx; border-top: 1rpx solid #eee; display: flex; align-items: center; justify-content: flex-start;}.avatarImg { width: 150rpx; height: 150rpx; border-radius: 50%;}.nickname { display: inline-block; font-size: 40rpx; font-weight: 600; color: #212121; margin-left: 20rpx;}.order, .tool { margin-top: 20rpx; background-color: #fff;}.list { border-bottom: 2rpx solid #eee; height: 100rpx; box-sizing: border-box; padding: 0 20rpx;}.orderTitle { display: flex; align-items: center; justify-content: space-between;}.listTitle { color: #232323; font-size: 35rpx;}.readMore { line-height: 100rpx; font-size: 30rpx; color: #989898; display: flex; align-items: center;}.toRight { width: 28rpx; height: 28rpx; margin-left: 20rpx;}index.json
{ "usingComponents": { "login": "/component/login/login", "i-message": "/component/iview/message/index" }}解释都在代码里面