1. 前言
- 登錄很簡(jiǎn)單,基本都做過,但是進(jìn)行總結(jié)的可能不多
- 今天閑來無事就總結(jié)一波
2. 登錄方式
- 微信小程序的登錄流程分為兩種情況
- 一種是使用微信開放平臺(tái)登錄
- 一種是使用自定義登錄方式
3. 使用微信開放平臺(tái)登錄
- 用戶在小程序中點(diǎn)擊
登錄
按鈕队丝,觸發(fā)登錄事件卧土。- 小程序調(diào)用
wx.login()
方法獲取臨時(shí)登錄憑證
code。- 小程序調(diào)用
wx.getUserInfo()
方法獲取用戶的基本信息
(頭像雕欺、昵稱等)盒齿。- 小程序?qū)?code>code和用戶信息發(fā)送給開發(fā)者的
后臺(tái)服務(wù)器
阿宅。- 開發(fā)者的后臺(tái)服務(wù)器使用
AppID
和AppSecret
調(diào)用微信接口獲取 session_key 和 openid
橡类。- 開發(fā)者的后臺(tái)服務(wù)器使用
session_key
對(duì)用戶信息進(jìn)行解密接谨,獲取用戶的真實(shí)信息摆碉。- 開發(fā)者的后臺(tái)服務(wù)器將用戶的信息存儲(chǔ)到數(shù)據(jù)庫(kù)中,并生成一個(gè)自定義登錄態(tài)
token
并返回給小程序脓豪。- 小程序
保存
該自定義登錄態(tài)token
巷帝,并在以后的請(qǐng)求
中帶上該token
。
4. 相關(guān)代碼
- 客戶端授權(quán)登錄
// 點(diǎn)擊按鈕觸發(fā)登錄事件
wx.login({
success: res => {
// 獲取臨時(shí)登錄憑證
const code = res.code;
// 獲取用戶信息
wx.getUserInfo({
success: res => {
const userInfo = res.userInfo;
// 將 code 和 userInfo 發(fā)送給后臺(tái)服務(wù)器
wx.request({
url: 'https://xx.xx.com/api/login',
data: {
code: code,
userInfo: userInfo
},
success: res => {
// 將服務(wù)器返回的自定義登錄態(tài) token 存儲(chǔ)到本地
wx.setStorageSync('token', res.data.token);
}
});
}
});
}
});
- 后臺(tái)服務(wù)器處理登錄邏輯
const request = require('request');
const crypto = require('crypto');
const APP_ID = '你的 app id';
const APP_SECRET = '你的 app secret';
// 處理小程序端登錄請(qǐng)求
function login(req, res) {
const code = req.body.code;
const userInfo = req.body.userInfo;
// 獲取 session_key 和 openid
request(`https://api.weixin.qq.com/sns/jscode2session?appid=${APP_ID}&secret=${APP_SECRET}&js_code=${code}&grant_type=authorization_code`, function(error, response, body) {
const result = JSON.parse(body);
const session_key = result.session_key;
const openid = result.openid;
// 使用 session_key 對(duì)用戶信息進(jìn)行解密
const encryptedData = userInfo.encryptedData;
const iv = userInfo.iv;
const decipher = crypto.createDecipheriv('aes-128-cbc', new Buffer(session_key, 'base64'), new Buffer(iv, 'base64'));
let decoded = decipher.update(encryptedData, 'base64', 'utf8');
decoded += decipher.final('utf8');
const userInfoObj = JSON.parse(decoded);
// 將用戶信息存儲(chǔ)到數(shù)據(jù)庫(kù)中扫夜,生成自定義登錄態(tài) token 并返回
const token = generateToken(userInfoObj);
res.json({ token: token });
});
}
// 生成自定義登錄態(tài) token
function generateToken(userInfo) {
const secret = 'your secret key';
const expireTime = new Date().getTime() + 24 * 3600 * 1000;
const data = JSON.stringify({
userInfo: userInfo,
expireTime: expireTime
});
const hash = crypto.createHmac('sha256', secret).update(data).digest('hex');
const token = `${data}.${hash}`;
return token;
}
只是大概流程 沒有考慮用戶退出登錄楞泼、token 過期等其他情況的處理。
5. 自定義登錄方式
- 用戶在小程序中輸入
賬號(hào)密碼
笤闯,點(diǎn)擊“登錄”按鈕堕阔,觸發(fā)登錄事件。- 小程序?qū)⒂脩糨斎氲馁~號(hào)密碼
發(fā)送
給開發(fā)者的后臺(tái)
服務(wù)器颗味。- 開發(fā)者的
后臺(tái)
服務(wù)器根據(jù)賬號(hào)密碼驗(yàn)證用戶的身份超陆,并生成一個(gè)自定義登錄態(tài)token
并返回給小程序。- 小程序保存該自定義登錄態(tài) token浦马,并在以后的
請(qǐng)求
中帶上該token
时呀。
- 需要注意的是张漂,在使用自定義登錄方式時(shí),為了保障用戶數(shù)據(jù)的安全谨娜,需要使用 HTTPS 協(xié)議鹃锈,并對(duì)用戶密碼進(jìn)行加密傳輸和存儲(chǔ)
6. 相關(guān)代碼
- 客戶端登錄邏輯
// 自定義登錄函數(shù),使用用戶名和密碼登錄
const login = (username, password) => {
return new Promise((resolve, reject) => {
wx.request({
url: 'https://xx.yzs.com/api/login',
method: 'POST',
data: {
username: username,
password: password
},
success: res => {
// 將服務(wù)器返回的自定義登錄態(tài) token 存儲(chǔ)到本地
wx.setStorageSync('token', res.data.token);
resolve(res.data);
},
fail: err => {
reject(err);
}
});
});
};
// 在登錄頁面中調(diào)用 login 函數(shù)進(jìn)行登錄
const handleLogin = () => {
const username = '密碼';
const password = '用戶名';
login(username, password).then(res => {
// 登錄成功瞧预,跳轉(zhuǎn)到首頁
wx.redirectTo({
url: '/pages/home/index',
});
}).catch(err => {
// 登錄失敗屎债,提示錯(cuò)誤信息
wx.showToast({
title: '登錄失敗,請(qǐng)重試垢油!',
icon: 'none'
});
});
};
- 后臺(tái)相關(guān)邏輯
const express = require('express');
const app = express();
const bodyParser = require('body-parser');
const crypto = require('crypto');
app.use(bodyParser.urlencoded({ extended: false }));
app.use(bodyParser.json());
// 處理自定義登錄請(qǐng)求
app.post('/api/login', (req, res) => {
const username = req.body.username;
const password = req.body.password;
// 根據(jù)用戶名和密碼查詢用戶信息
const user = getUserByUsernameAndPassword(username, password);
if (!user) {
res.status(401).json({ message: '用戶名或密碼錯(cuò)誤盆驹!' });
return;
}
// 將用戶信息存儲(chǔ)到數(shù)據(jù)庫(kù)中,生成自定義登錄態(tài) token 并返回
const token = generateToken(user);
res.json({ token: token });
});
// 生成自定義登錄態(tài) token
const generateToken = user => {
const secret = '自己的secret key';
const expireTime = new Date().getTime() + 24 * 3600 * 1000;
const data = JSON.stringify({
userId: user.id,
expireTime: expireTime
});
const hash = crypto.createHmac('sha256', secret).update(data).digest('hex');
const token = `${data}.${hash}`;
return token;
};
// 根據(jù)用戶名和密碼查詢用戶信息
const getUserByUsernameAndPassword = (username, password) => {
// TODO: 實(shí)現(xiàn)根據(jù)用戶名和密碼查詢用戶信息的邏輯
return {
id: 123,
username: 'testuser',
nickname: 'Test User'
};
};
app.listen(3000, () => {
console.log('Server started on port 3000.');
});
7. 請(qǐng)求配置 token
- 核心代碼
// 假設(shè) token 存在于本地緩存中滩愁,緩存的鍵名為 'token'
const token = wx.getStorageSync('token')
wx.request({
url: 'https://xx.com/api/some-resource',
header: {
'Authorization': `Bearer ${token}`
},
success(res) {
console.log(res.data)
},
fail(err) {
console.error(err)
}
})
- 通過
wx.getStorageSync()
方法獲取本地緩存中存儲(chǔ)的 token躯喇,- 然后將其添加到
header
對(duì)象中的 Authorization 屬性中,值為 Bearer ${token}硝枉,- 其中 Bearer 為固定字符串廉丽,
${token}
是變量,表示真正的 token 值妻味。- 最終發(fā)送的請(qǐng)求頭會(huì)包含
Authorization: Bearer ${token}
正压,其中 ${token} 會(huì)被替換為真實(shí)的 token 值。