但愿经过小程序+node来总体的了解下小程序登录的流程。若有不对欢迎在评论区指出👏html
1. client: wx.login / wx.getUserInfonode
微信开放平台有APP PC 网站想用微信登陆/分享到朋友圈等其提供微信登陆、分享、支付等相关权限和服务。须要用unionid来区分用户的惟一性。 须要经过wx.getUserInfo()api拿到用户的encryptedData, iv
这些加密数据传给服务端,经过服务端解密获取用户的unionid数据库
getUserInfo() {
const _this = this
wx.login({
success(res) {
const { code } = res
console.log(res)
wx.getUserInfo({
withCredentials: true,
success(res) {
const { encryptedData, iv } = res
_this.reSaveUserLogin({ code, data: encryptedData, iv })
}
})
}
})
},
复制代码
这里须要注意的是,wx.getUserInfo()触发必须得经过button open-type设置为"getUserInfo"点击后才能触发。json
<button wx:if="!isLogin" open-type="getUserInfo" bindtap="getUserInfo">获取用户信息</button>
复制代码
2. service: request 请求微信服务 / 解密出unionid小程序
// 请求方法
const request = require('request')
const url = https://api.weixin.qq.com/sns/jscode2session?appid=APPID&secret=SECRET&js_code=JSCODE&grant_type=authorization_code
module.exports = {
async getSession(code) {
return new Promise((resolve, reject) => {
request(url,{
method: 'GET',
json: true
},
(error, res, body) => {
if (error) {
reject(error)
} else {
if (body.errcode) {
reject(new Error(body.errmsg))
} else {
resolve(body)
}
}
}
)
})
}
}
复制代码
async function login(ctx) {
const { code, data, iv } = ctx.request.body
const session = await getSession(code)
const { session_key, openid } = session
const pc = new WXBizDataCrypt(appId, session_key)
const result = pc.decryptData(data, iv)
console.log('解密后', result)
}
复制代码
3.service:服务端获取sesson_key 等信息不能直接返回客户端须要加密解密处理api
const crypto = require('crypto')
const secret = '2019_06'
const algorithm = 'aes-256-cbc'
function encode(id) {
const encoder = crypto.createCipher(algorithm, secret)
const str = [id, Date.now(), '2019'].join('|')
let encrypted = encoder.update(str, 'utf8', 'hex')
encrypted += encoder.final('hex')
return encrypted
}
function decode(str) {
const decoder = crypto.createDecipher(algorithm, secret)
let decoded = decoder.update(str, 'hex', 'utf8')
decoded += decoder.final('utf8')
const arr = decoded.split('|')
return {
id: arr[0],
timespan: parseInt(arr[1])
}
}
module.exports = {
encode,
decode
}
复制代码
4.service:返回登录态数组
const { encode } = require('./lib/crypto')
const jsonMine = 'application/json'
const now = Date.now()
function handle(ctx, data, code = 0, message = 'success') {
ctx.type = jsonMine
ctx.body = {
code,
data,
message
}
}
router.get('/login', async (ctx, next) => {
const { code } = ctx.request.query
const session = await login(code)
if (session) {
const { session_key, openid } = session
// 查找数据库中是否已经存有openid,若是 hasOpenid 为null说明是新用户
const hasOpenid = await User.findByPk(openid)
if(!hasOpenid){
// 数据库存储openid,时间戳
User.create({openid,timespan:Date.now()})
}
handle(ctx, { token: encode(openid) })
} else {
throw new Error('登录失败')
}
})
复制代码
5.client:存储登录态在storagebash
import { LOGIN_TOKEN } from '../../utils/localStorage'
// 拿到token存储到客户端
wx.setStorageSync(LOGIN_TOKEN, token)
复制代码
我在发起请求时将登录态放在请求头中,相应的服务端能够从请求头中获取微信
header: {
'x-session': wx.getStorageSync(LOGIN_TOKEN)
},
复制代码
6.service:校验登录态session
module.exports = async function(ctx, next) {
const sessionKey = ctx.get('x-session')
const { id, timespan } = decode(sessionKey)
// 查找数据库中是否存在该 openid,返回是一个数组,若是不存在则返回[]
const targetList = await getOpenid(id)
if (targetList.length > 0) {
// 若是超过设定的过时时间,标记isExpired字段为登录过时
const oneHour = 1000 * 60 * 60 * 24
if (Date.now() - timespan > oneHour) {
ctx.state.isExpired = true
// 跟前台约定,若是code=2说明登录过时跳登录页面
handle(ctx, '', 2, '登录过时')
} else {
handle(ctx, '', 0, '登录成功')
}
} else {
// 经过ctx.throw能够直接抛出错误
ctx.throw(401, '登录失败')
}
复制代码