微信开发——网页授权
- 前期准备
- 前端
- 后端
前期准备
①微信客户端中访问第三方页面,公众号可以通过网页登陆授权,获取微信用户的基本信息(头像、昵称等),实现业务逻辑。一切按照官方文档说明开发。
②安装微信开发者工具。
③一个官方认证的公众号,或者使用微信公众平台的测试公众号。
④如果使用微信公众平台测试号,需要配置JS接口安全域名和网页授权获取用户基本信息,填写正确的域名和端口号。
⑤本文使用测试号来演示网页授权。前端页面使用VSCode的Live Server
本地开启服务器,端口号是5500
。后端使用Node的express
本地开启服务器,端口号是3000
。
前端
页面介绍
①回调页面: 你自己写的网页,即http://127.0.0.1:5500/
,与之前配置的JS接口安全域名和网页授权获取用户基本信息一致,可以打开在微信平台与非微信平台。
②静默授权链接: 将你的回调页用微信规定的静默授权格式包裹起来。在微信中打开时,会返回展示你的回调页,并且在URL上附加额外的code
和state
参数。静默授权只能获取到微信用户的openid
。
③主动授权链接: 将你的回调页用微信规定的主动授权格式包裹起来。在微信中打开时,弹出主动授权框。用户点击确定授权后,会返回展示你的回调页,并且在URL上附加额外的code
和state
参数。若用户近期主动授权过,则不会弹出主动授权框,自动刷新跳转到回调页。主动授权能进一步获取到微信用户的头像、昵称等信息。
④在主动授权链接中带上state=auth
参数,能够在用户主动授权后的回调页的URL上,知道用户已经主动授权了。
// 回调页
const cbURL = encodeURIComponent(`http://127.0.0.1:5500/`);
// 静默授权链接
const shareURL = `https://open.weixin.qq.com/connect/oauth2/authorize?appid=${appId}&redirect_uri=${cbURL}&response_type=code&scope=snsapi_base#wechat_redirect`;
// 主动授权链接
const authURL = `https://open.weixin.qq.com/connect/oauth2/authorize?appid=${appId}&redirect_uri=${cbURL}&response_type=code&scope=snsapi_userinfo&state=auth#wechat_redirect`;
二次分享与code参数
①code
参数是微信平台附加在回调页URL上的随机码,用于获取当前微信用户的access_token
。随机码只能用一次,5分钟未使用自动过期。原地刷新页面也会导致code
失效,需要重定向静默授权链接,重新获取随机码。
②非微信端打开的回调页分享到微信为首次分享,首次分享的分享链接即为静默授权链接。
③微信端打开的回调页,通过微信浏览器右上角的···
继续进行分享为二次分享,需要调用微信的SDK,参见我的另一篇博客《微信开发——开放标签》。
④二次分享的链接应该设置为回调页链接http://127.0.0.1:5500/
。若其他用户在微信平台打开二次分享的链接,那么就无法进行主动授权。所以需要在回调页判断微信平台,并在无code
参数时,重定向至静默授权链接。
记录并判断用户的主动授权
①从URL的state
参数或localStorage
中判断用户曾经是否主动授权过。
②若用户完成主动授权,则在localStorage
中持久化存入标志信息,建议设置一个过期时间,可以借助storejs第三方库。
// 判断微信平台
const ua = navigator.userAgent.toLowerCase();
const isWechat = /micromessenger/.test(ua);
// 解析URL的query参数
function query() {
const map = {};
const params = window.location.search.substring(1).split("&");
params.forEach((item) => {
const temp = item.split("=");
map[temp[0]] = temp[1];
});
return map;
}
// 判断用户是否曾经主动授权过
const auth = localStorage.getItem("Auth") || query().state || "";
const code = query().code || "";
const lastCode = sessionStorage.getItem("vercode");
// 在微信平台中
if (isWechat) {
// 储存本次code
sessionStorage.setItem("vercode", code);
// 二次分享无code,保证每次页面刷新,均存在有效的code,
if (code === lastCode || !code) {
window.location.replace(shareURL);
} else {
// 发起请求(下一节)
}
}
发起请求
①当回调页在微信浏览器中记载完毕时,需要做的第一件事就是发起一个请求,向后端发送code
参数,并判断用户是否主动授权过,从后端获取用户的基本信息。
②若用户未曾主动授权则只能获取到openid
,主动授权过才能获取到更多基本信息。
// AJAX请求
const ajax = {
get(url) {
return new Promise((resolve, reject) => {
let xhr = new XMLHttpRequest();
xhr.open("GET", url);
xhr.send();
xhr.onreadystatechange = function () {
if (xhr.readyState === 4) {
if (xhr.status >= 200 && xhr.status < 300) {
const res = JSON.parse(xhr.response);
if (!res.code) resolve(res.data);
else reject(res);
}
}
};
});
},
};
// 1是静默授权,0是主动授权
ajax
.get(`${location.protocol}//${location.hostname}:3000/auth?code=${code}&type=${auth ? 0 : 1}`)
.then((res) => {
UserInfo.openId = res.openId;
if (res.UserName && res.UserPhoto) {
UserInfo.UserName = res.UserName;
UserInfo.UserPhoto = res.UserPhoto;
// 主动授权后,储存记录
localStorage.setItem("Auth", 1);
}
})
.catch((e) => console.log(e.message));
引导用户主动主动授权
①在页面中放置一个按钮用于交互,绑定事件,出发主动授权,弹出主动授权框。已主动授权过的用户则点击无效。
const authBtn = document.getElementById("authBtn");
// 按钮点击事件,获取主动授权
const handleAuth = () => {
if (!auth) {
window.location.replace(authURL);
}
};
authBtn.addEventListener("click", handleAuth);
后端
express搭建一个服务器
①由于前后端不在一个服务器上,所以需要解决跨域的问题。
const express = require("express");
const app = express();
// CORS跨域设置
app.all("*", function (req, res, next) {
res.header("Access-Control-Allow-Origin", "*");
res.header("Access-Control-Allow-Headers", "X-Requested-With");
res.header("Access-Control-Allow-Methods", "PUT,POST,GET,DELETE,OPTIONS");
res.header("X-Powered-By", " 3.2.1");
res.header("Content-Type", "application/json;charset=utf-8");
next();
});
app.listen(3000, () => console.log("服务器已开启"));
通过code换取access_token和openid
①前端会将code
传给后端,后端需要用该code
向微信获取access_token
和openid
等信息。
②每个用户对于同一公众号的openid
都是唯一固定不变的。
③静默授权的流程到此结束。
const https = require("https");
const getAccessToken = (code) => {
return new Promise((resolve, reject) => {
const URL = `https://api.weixin.qq.com/sns/oauth2/access_token?appid=${appId}&secret=${appSecret}&code=${code}&grant_type=authorization_code`;
https
.get(URL, (res) => {
let rawData = "";
res.on("data", (data) => (rawData += data));
res.on("end", () => resolve(JSON.parse(rawData)));
})
.on("error", (e) => console.log(e.message));
});
};
通过access_token和openid获取UserInfo
①用户主动授权后,通过上一步获取的access_token
和openid
可以进一步获取用户的基本信息。
const getUserInfo = (accessToken, openId) => {
return new Promise((resolve, reject) => {
const URL = `https://api.weixin.qq.com/sns/userinfo?access_token=${accessToken}&openid=${openId}&lang=zh_CN`;
https
.get(URL, (res) => {
let rawData = "";
res.on("data", (data) => (rawData += data));
res.on("end", () => resolve(JSON.parse(rawData)));
})
.on("error", (e) => console.log(e.message));
});
};
部署GET接口
①根据前端传的type
参数确定本次获取的是静默授权信息还是主动授权信息,返回用户基本信息。
app.get("/auth", function (req, res) {
const wxAuth = async () => {
// 解析query参数,字符串类型
const { code, type } = req.query;
const token = await getAccessToken(code);
// 请求报错
if (!token.access_token) {
return res.send({ code: token.errcode, message: token.errmsg });
}
// 静默授权
if (Number(type)) {
return res.send({
code: 0,
message: "success",
data: { openId: token.openid },
});
// 主动授权
} else {
const UserInfo = await getUserInfo(token.access_token, token.openid);
// 请求报错
if (!UserInfo.openid) {
return res.send({ code: UserInfo.errcode, message: UserInfo.errmsg });
}
return res.send({
code: 0,
message: "success",
data: {
openId: UserInfo.openid,
UserName: UserInfo.nickname,
UserPhoto: UserInfo.headimgurl,
},
});
}
};
wxAuth();
});