微信公众号开发中几个阶段的权限校验,相对于前端同学来说(特别是没有nodejs基础的同学来说)可能相当费解,不过话说回来,现代前端不会nodejs可能会混的相当难受。
咱们今天把这些烦人的 access_token、 jsapi_ticket、signature 一次盘明白,本文所有操作都基于公众号的测试号,因此在开始 bibi 前需要先申请一个公众号的测试号,这里不再赘述。
稍微复杂一些的公众号服务都会涉及自定义回复,比如某些消息内容需要返回数据库里面的数据,这就需要用到微信提供的消息接口:
如上图
注意!在提交时微信会向你填写的URL地址发送一个get请求,所以提交之前你必须保证这个域名下的相应接口必须能正常返回(这里就涉及到了下一步要说的微信校验)
其实按理说这个才应该是第一步,因为如果服务端接口没有实现,上一步根本不能成功。好吧,顺序我就不改了,能看明白就行。
Step1:微信校验接口实现
这里是个什么概念呢?就是在你提交上面第一步填写的URL地址的时候,微信服务器会向你的地址发送一个get请求,同时带上四个参数:
![在这里插入图片描述](https://img-blog.csdnimg.cn/2020050414182280.png
为了你的服务器安全,你需要校验这个请求是否来自微信,校验的方法就是:将你自己保存的token和微信带过来的timestamp、nonce三个字段进行字典排序后使用sha1算法加密,把得到的结果与微信带过来的signature进行对比,如果相等说明对方确实拥有你设定的token,然后把微信带过来的echostr 随机字符串原样返回。
注意,这一步的作用是你的服务器校验微信,如果你不想校验,直接返回echostr也是可以配置成功的,下面来一段实现代码
上代码前先声明一下使用的框架和工具:
const crypto = require("crypto");
// 我把appid和 appsecret 放在了conf.js中
const conf = require("./conf");
router.get("/wechat1", async ctx => {
console.log("微信校验...", ctx.url);
const { query } = url.parse(ctx.url, true);
const { signature, nonce, timestamp, echostr } = query;
const str = [conf.token, nonce, timestamp].sort().join("");
const signed = crypto
.createHash("sha1")
.update(str)
.digest("hex");
console.log("接收到的签名为:", signature);
console.log("计算得到签名为:", signed);
if (signature === signed) {
console.log("对比结果成功!!!");
ctx.body = echostr;
} else {
console.log("对比结果不通过");
ctx.body = "你不是微信";
}
});
接下来是服务端代码(省略了中间件的引入和一些常规代码)
Step2:消息接口实现
这中间的原理就是,每当用户向公众号发送消息的时候,微信服务器会向你的服务器发送一个post请求,并携带以下内容:
我们要做的就是根据Content 来返回自己预定的内容就好了,因为默认情况下微信请求时通过xml格式来传输数据的,所以我使用了 xml2js 这个工具来实现 xml 和js 对象两种格式的转换。
const xml2js = require("xml2js");
// 这个wechat 接口是你自己可以在上面的接口配置信息里面自己设定的
router.post("/wechat", async ctx => {
const { xml: msg } = ctx.request.body;
console.log("receive...", msg);
const builder = new xml2js.Builder();
// 微信需要接收xml格式数据,所以这里使用xml2js转译成xml格式
const result = builder.buildObject({
xml: {
ToUserName: msg.FromUserName,
FromUserName: msg.ToUserName,
CreateTime: Date.now(),
MsgType: msg.MsgType,
Content: "你好 " + msg.Content
}
});
ctx.body = result;
});
嗯,好吧,你才好2
微信公众号为开发者提供了一些针对服务端的信息服务,比如 获取用户列表,获取用户基本信息,地理位置 等,但是呢,不能是台服务器就随便调用微信的接口(也不安全),因此就有了服务端的access_token,它是公众号的全局唯一接口调用凭证。以下是官方的接口条用请求说明:
这个咱们直接按照官方文档直接调用就好了,比较简单,下面是代码:
// 我把appid和 appsecret 放在了conf.js中
const conf = require("./conf.js");
const tokenCache = {
access_token: "",
update_time: Date.now(),
expires_in: 7200
};
async function getToken() {
const url = `https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=${conf.appid}&secret=${conf.appsecret}`;
let res = await axios.get(url);
console.log("res: ", res.data);
// 保存access_token
Object.assign(tokenCache, res.data, { update_time: Date.now() });
};
当然,上面的代码只是一个简单的示例,所以我把获取回来的token直接保存到内存中了,实际工作中你可以把它存到数据库中。那么获取到token我们就可以大胆使用微信提供的信息服务了,下面示范一个获取用户信息:
async function getFollowers() {
// 先获取用户列表
const url = `https://api.weixin.qq.com/cgi-bin/user/get?access_token=${tokenCache.access_token}`;
let res = await axios.get(url);
console.log("res.data.openid", res.data);
let openids = res.data.data.openid.map(item => ({
openid: item,
lang: "zh_CN"
}));
// 获取用户信息
const url1 = `https://api.weixin.qq.com/cgi-bin/user/info/batchget?access_token=${tokenCache.access_token}`;
console.log("openids--->", openids);
res = await axios.post(url1, { user_list: openids });
console.log("followers: ", res.data);
ctx.body = res.data;
});
如果用户在微信上访问第三方网页,公众号可以通过微信网页授权机制,获取用户信息,有的同学可能会问,第二步不是已经获取了一次 access_token 吗?实际上两个token的使用范围是不一样的,刚刚的服务端token可以获取你的公众号用户列表 也就是关注了你公众号的用户,而这里的网页端token是可以获取未关注用户的用户信息的
我们平常使用微信访问第三方网页的时候是不是也经常会跳转到一个授权页面呢?相信你一定有印象
这一步是微信校验中最复杂的一步,使用的是oAuth2 第三方校验方式,目前市面上的第三方登录用的基本都是oAuth2认证,对oAuth2校验不是很了了解的同学可以移步阮一峰老师的博客:传送门 理解OAuth 2.0
因为比较复杂咱们把这里拆分为三步:
这里有非常重要的一步是 拼接返回地址 这个返回地址就是当用统一授权后微信页面会从定向到的地址,重定向的时候会在查询参数里面拼上用户获取access_token的code。
// 我把appid和 appsecret 放在了conf.js中
const conf = require("./conf.js");
router.get("/wxAuthorize", async ctx => {
const state = ctx.query.id;
const scope = "snsapi_userinfo";
console.log("href: ", ctx.href);
const path = new URL(ctx.href);
const redirectUrl = `${path.protocol}//${path.hostname}/wxCallback`;
const url = `https://open.weixin.qq.com/connect/oauth2/authorize?appid=${conf.appid}&redirect_uri=${redirectUrl}&response_type=code&scope=snsapi_userinfo&state=${state}#wechat_redirect`
ctx.redirect(url);
});
上一步中提到,当用户点击确定按钮同意授权后,微信会把页面重定向到你预先定义的 redirectUrl 并附上获取access_token 的 code,我们要做的就是定义好要跳转的路由,在里面拿到code,获取access_token。为了更好理解,这里附上文档截图:
const conf = require("./conf.js");
router.get("/wxCallback", async ctx => {
const code = ctx.query.code;
console.log("wxCallback...");
// 使用axios发送get请求
const res = await axios.get('https://api.weixin.qq.com/sns/oauth2/access_token', {
params: {
appid: conf.appid,
secret: conf.appsecret,
code: code,
grant_type: 'authorization_code'
}
});
console.log("token_res: ", res);
// 保存access_token 需要连接数据库,我这里省略了
// 重定向回访问页面
ctx.redirect("/”);
});
先来张参数截图:
这里就非常简单了,直接axios发送请求获取回来就ok,就不上示例代码了。
当然上面省略了一步刷新access_token,这个我认为比较简单,而且跟获取token差不多,就不赘述了。
这一步的目的当然就是 为了能畅通无阻的调用微信提供的拍照、录音、拍视频等api 因为微信规定,所有页面在使用它提供的api之前必须先注入配置信息,并且跳转到一个url不同的页面都必须进行签名。这里让前端同学费解的就是这个签名了。
同样,咱们分解成三步:
在计算签名之前必须先用 access_token 获取 jsapi_ticket,注意!这里的 access_token 是指我们在第二步提到的服务端 access_token。 先不上代码,在第二步统一po上来
这里签名需要noncestr、jsapi_ticket、timestamp、url 这四个参数,把这四个参数按照ASCII码从小到大书序排序拼接后进行sha1算法即可得出签名:
const crypto = require("crypto");
const conf = require("./conf");
router.get("/getJsConfig", async ctx => {
// 第一步:获取 jsapi_ticket
let res = await axios.get('https://api.weixin.qq.com/cgi-bin/ticket/getticket',{
params: {
access_token: tokenCache.access_token,
type: 'jsapi'
}
});
const jsapi_ticket = res.data.ticket;
// 计算签名
const timestamp = Date.now();
const url = ctx.query.url;
const noncestr = Math.random().toString(36).substring(2);
const string1 = `jsapi_ticket=${jsapi_ticket}&noncestr=${noncestr}×tamp=${timestamp}&url=${url}`;
const signature = crypto.createHash('sha1').update(string1).digest('hex');
// 最后把完整的配置返给前端
ctx.body = {
appid: conf.appid,
timestamp: timestamp,
noncestr: noncestr,
signature: signature,
}
});
这里就是前端比较好理解的不分了
{
mehtods: {
async getJsConfig() {
let res = await axios.get('/getJsConfig', {
params: {
url: location.href
}
})
console.log(res)
res.data.jsApiList = ['chooseImage'];
wx.config(res.data);
wx.ready(function() {
// 在这里调用api
})
},
}
}
好了,以上就是微信校验的全部内容了。如有疑问,欢迎与作者联系
最后再安利一组微信开发的库,co-wechat、co-wechat-api、co-wechat-oauth。这三个库就是三兄弟,能解决公众号开发不同阶段的问题。比如我们可能经常要手动拼接微信提供的接口和我们的参数,并且手动发送请求,重复这么弄还是挺费神的。有了这三兄弟,请求地址和发送请求一次性给你搞定,甚至连token存取到数据库的操作也帮你做了。
微信消息接口通过co-wechat实现
const wechat = require("co-wechat");
router.all(
"/wechat",
wechat(conf).middleware(async message => {
console.log("wechat", message);
return "hello world!" + message.Content;
})
);
第一部分讲到的消息接口通过co-wechat实现就这么简单
示例代码:
const wechatApi = require("co-wechat-api");
const { ServerToken } = require("./mongoose");
const conf = require("./conf.js");
const api = new wechatApi(
conf.appid,
conf.appsecret,
// 从数据库取token的方法
async () => await ServerToken.findOne(),
// 从数据库存token的方法
async token => await ServerToken.updateOne({}, token, { upsert: true })
);
router.get("/getFollowers", async ctx => {
console.log(ctx.url);
// 使用的时候直接调用方法就可以了
let res = await api.getFollowers();
res = await api.batchGetUsers(res.data.openid, "zh_CN");
console.log("followers: ", res);
ctx.body = res;
});
示例代码:
const oauth = require("co-wechat-oauth");
const client = new oauth(
conf.appid,
conf.appsecret,
// 从数据库取token的方法
async openid => {
return await ClientToken.getToken(openid);
},
// 从数据库存token的方法
async (openid, token) => {
return await ClientToken.setToken(openid, token);
}
);
router.get("/wxAuthorize", async ctx => {
const state = ctx.query.id;
const scope = "snsapi_userinfo";
console.log("href: ", ctx.href);
const path = new URL(ctx.href);
const redirectUrl = `${path.protocol}//${path.hostname}/wxCallback`;
const url = client.getAuthorizeURL(redirectUrl, state, scope);
ctx.redirect(url);
});
router.get("/wxCallback", async ctx => {
const code = ctx.query.code;
console.log("wxCallback...");
const res = await client.getAccessToken(code);
console.log("token_res: ", res);
ctx.redirect("/?openid=" + res.data.openid);
});
router.get("/getUser", async ctx => {
const openid = ctx.query.openid;
console.log("openid: " + openid);
const res = await client.getUser(openid, "zh_CN");
ctx.body = res;
});
好了,以上就是本次分享的全部内容,非常感谢您能认真阅读本文