https://developers.weixin.qq.com/miniprogram/dev/framework/open-ability/login.html 微信小程序官方API
说明:
之后开发者服务器可以根据用户标识来生成自定义登录态,用于后续业务逻辑中前后端交互时识别用户身份。
注意:
session_key
是对用户数据进行 加密签名 的密钥。为了应用自身的数据安全,开发者服务器不应该把会话密钥下发到小程序,也不应该对外提供这个密钥。
这里仅按照官方推荐的规范来
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
注意:wx.authorize({scope: "scope.userInfo"}),无法弹出授权窗口,请使用
login.js
// component/login/login.js
const 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
{{userInfo.nickName}}
未登录
我的订单
查看更多
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"
}
}
解释都在代码里面