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.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
<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"
}
}
解釋都在代碼裡面