最近在開發(fā)公眾號網(wǎng)頁, 所以對授權(quán)進行了探索
示例代碼: klren0312/wechatVueHash (github.com)
1. 官方文檔步驟
2 第二步:通過code換取網(wǎng)頁授權(quán)access_token
4 第四步:拉取用戶信息(需scope為 snsapi_userinfo)
5 附:檢驗授權(quán)憑證(access_token)是否有效
2. 問題
當(dāng)使用vue的hash路由時, 微信授權(quán)重定向到前端時, 會把路由放到url最后, 例如
https://open.weixin.qq.com/connect/oauth2/authorize?appid=yourappid&redirect_uri=https%3A%2F%2Fxx.xx.xx%2Fwechat&response_type=code&scope=snsapi_base&state=wechat&connect_redirect=1#wechat_redirect
會變成
https://xx.xx.xx/wechat/?code=091v5v000CeBWM1bGz2005y2Sd3v5v0q&state=wechat#/codePage
hash路由問題
3. 處理方法
1) 方法一
在路由攔截器中截取#/
后的路由, 重新拼接成正確url, 并使用location.href
進行跳轉(zhuǎn)
如果想帶參, 可以直接放在路由后面或者放在state
里面
帶參
注意: redirect_uri
和state
都得使用encodeURIComponent
進行編碼
當(dāng)然我們得拿code
去后臺請求openId
等參數(shù)進行業(yè)務(wù)開發(fā)
路由攔截器中進行路由拼接與code獲取請求接口例子(本例子頁面參數(shù)是從state中獲取)
router.beforeEach(async (to, from, next) => {
const href = window.location.href
if (href.indexOf('/?code') > -1) {
const urlArr = href.split('/?')
const leftUrl = urlArr[0] + '/#/'
const rightUrlArr = urlArr[1].split('#/')
const queryObj = {}
// 獲取code和state參數(shù)
rightUrlArr[0]
.split('&')
.map((item) => {
const splitStr = item.split('=')
return {
key: splitStr[0],
value: splitStr[1],
}
})
.forEach((item) => {
queryObj[item.key] = item.value
})
// 使用微信code請求后臺接口拿openId等你業(yè)務(wù)參數(shù)
getOpenId(queryObj.code)
.then((res) => res.json())
.then((res) => {
if (res.code === 0) {
// 解碼state參數(shù)
const state = decodeURIComponent(queryObj.state)
// 拼接url, 跳轉(zhuǎn)
location.href = `${leftUrl}${rightUrlArr[1]}?openid=${res.openid}&state=${state}`
} else {
location.href = leftUrl + 'login'
}
})
.catch(() => {
location.href = leftUrl + 'login'
})
} else {
next()
}
})
2) 方法二
授權(quán)回調(diào)后端接口, 后端獲取微信的code重定向給前端, 前端拿url中的code參數(shù)再請求后端接口獲取openId等
流程
# 設(shè)置為后臺接口地址
https://open.weixin.qq.com/connect/oauth2/authorize?appid=wxd5be0fe8e3c48877&redirect_uri=https%3A%2F%2Fxx.xx.xx%2Fapi%2FgetCode&response_type=code&scope=snsapi_base&state=wechat&connect_redirect=1#wechat_redirect
# 最后跳轉(zhuǎn)地址
https://xx.xx.xx/wechat/#/codePage?code=001sMjFa1F7uhC0lncJa1jHXCs3sMjFa
后端nodejs示例代碼
const got = require('got')
const express = require('express')
const bodyParser = require('body-parser')
const ioredis = require('ioredis')
const redis = new ioredis()
const app = express()
app.use('/static', express.static('public'))
app.use(bodyParser.json())
app.use(bodyParser.urlencoded({ extended: false }))
const appID = ''
const appsecret = ''
const BASEURL = encodeURIComponent('https://xx.xx.xx/wechat')
const BASEURL2 = encodeURIComponent('https://xx.xx.xx/api/getCode')
//設(shè)置所有路由無限制訪問委刘,不需要跨域
app.all('*', function(req, res, next) {
res.header('Access-Control-Allow-Origin', '*')
res.header('Access-Control-Allow-Headers', 'Origin, X-Requested-With, Content-Type, Accept')
res.header('Access-Control-Allow-Methods', '*')
next()
})
const SERVERURL = '/api'
// 微信域名校驗
app.get(SERVERURL + '/wechat', function(req, res) {
const { signature, timestamp, nonce, echostr } = req.query
console.log(req.query)
const token = 'zzes'
jsSHA = require('jssha')
const arr = [token, timestamp, nonce].sort()
shaObj = new jsSHA(arr.join(''), 'TEXT')
const currentSign = shaObj.getHash('SHA-1', 'HEX')
if (currentSign === signature) {
res.send(echostr)
} else {
res.send('')
}
})
// 獲取用戶openId
app.post(SERVERURL + '/getOpenId', function(req, res) {
const { code } = req.body
const url = `https://api.weixin.qq.com/sns/oauth2/access_token?appid=${appID}&secret=${appsecret}&code=${code}&grant_type=authorization_code`
got(url).then(data => {
const result = JSON.parse(data.body)
if (result?.openid) {
console.log('openid:' + result.openid)
res.send({
code: 0,
binding: true,
openid: result.openid
})
} else {
console.log('err', result)
res.send({
code: result.errcode,
binding: false,
openid: '',
msg: result.errmsg
})
}
}).catch(err => {
res.send({
code: -1,
binding: false,
openid: '',
msg: err.message
})
})
})
// 后端拿code, 這里授權(quán)域名得配后臺的域名
app.get(SERVERURL + '/getCode', async function(req, res) {
const { code } = req.query
console.log(req.query)
res.redirect(`${decodeURIComponent(BASEURL)}/#/codePage?code=${code}`)
})
// 發(fā)送模板消息
app.get(SERVERURL + '/sendMsg', async function(req, res) {
const { openid } = req.query
const result = await sendTemplateMsg(openid)
res.send(result)
})
//端口:18888
var server = app.listen(28848, function() {
console.log("127.0.0.1:28848")
})
// 創(chuàng)建菜單
setWechatMenu()
async function setWechatMenu() {
const url = encodeURIComponent(`/#/`)
const menu = {
button: [
{
name: '菜單',
sub_button: [
{
type:'view',
name:'測試一',
url:`https://open.weixin.qq.com/connect/oauth2/authorize?appid=${appID}&redirect_uri=${BASEURL}${encodeURIComponent(`/#/`)}wechat&response_type=code&scope=snsapi_base&state=111#wechat_redirect`
},
{
type:'view',
name:'測試二',
url:`https://open.weixin.qq.com/connect/oauth2/authorize?appid=${appID}&redirect_uri=${BASEURL}${encodeURIComponent(`/#/`)}wechat2&response_type=code&scope=snsapi_base&state=111#wechat_redirect`
},
{
type:'view',
name:'測試',
url:`https://open.weixin.qq.com/connect/oauth2/authorize?appid=${appID}&redirect_uri=${BASEURL2}&response_type=code&scope=snsapi_base&state=111#wechat_redirect`
}
]
}
]
}
let accessToken = await redis.get('access_token')
if (!accessToken) {
accessToken = await getAccessToken()
}
got({
url: `https://api.weixin.qq.com/cgi-bin/menu/create?access_token=${accessToken}`,
method: 'POST',
body: JSON.stringify(menu)
}).then(data => {
const result = JSON.parse(data.body)
console.log('菜單', result)
})
}
/**
* 發(fā)送模板消息
*/
async function sendTemplateMsg(openid) {
let accessToken = await redis.get('access_token')
if (!accessToken) {
accessToken = await getAccessToken()
}
const state = encodeURIComponent(`wechat&id=${Math.floor(Math.random() * 100)}`)
return got({
url: `https://api.weixin.qq.com/cgi-bin/message/template/send?access_token=${accessToken}`,
method: 'POST',
body: JSON.stringify({
touser: openid,
template_id: 'WfcomWPkkbQlvTJXJpzFVWGc14hOeyI23TXgHPST8-I',
url: `https://open.weixin.qq.com/connect/oauth2/authorize?appid=${appID}&redirect_uri=${BASEURL}${encodeURIComponent(`/#/`)}wechat1&response_type=code&scope=snsapi_base&state=${state}#wechat_redirect`,
data: {
time: {
value: new Date().toLocaleString(),
color: '#323232'
},
content: {
value: '您有新的消息, 請點擊查看',
color: '#ff0000'
}
}
})
}).then(data => {
const result = JSON.parse(data.body)
return result
})
}
/**
* 獲取access_token
*/
function getAccessToken() {
return got(`https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=${appID}&secret=${appsecret}`)
.then(data => {
console.log(data.body)
const result = JSON.parse(data.body)
if (result?.access_token) {
redis.set('access_token', result.access_token, 'EX', result.expires_in - 60)
return result.access_token
} else {
console.log('err', result)
return ''
}
})
.catch(err => {
console.log(err)
return ''
})
}
示例測試公眾號
簡書不給發(fā) 也是牛 去github 的 readme中看吧