前言
嗨喽,各位小伙伴,美好的一天从耀哥开始~ 今天给大家分享微信公众号H5开发中的网页授权。
「微信官方文档·公众号_网页授权」 >> https://developers.weixin.qq.com/doc/offiaccount/Getting_Started/Overview.html
开始之前,我们先了解几个概念:
「OpenID & UnionID」
OpenID 和 UnionID 为用户的唯一标识。其中:
OpenID:表示微信用户对于某个
特定
小程序或者公众号的唯一标识,开发者可以通过这个标识识别用户。UnionID:表示微信用户对于
同一商户主体
下的微信小程序或者公众号的唯一标识。开发者需要在微信开放平台绑定相同账号的主体。通过 UnionID,可实现多个小程序、公众号、甚至APP之间的数据互通。
可能有的小伙伴比较费解,既然已经有了标识用户的 OpenID,为什么还需要 UnionID 呢?据官网介绍,如果开发者拥有多个移动应用、网站应用和公众帐号,可通过获取用户基本信息中的UnionID来区分用户的唯一性,因为同一用户,对同一个微信开放平台下的不同应用(移动应用、网站应用和公众帐号),UnionID是相同的。为了帮助大家理解,我给大家举个例子:
比如我有A/B/C三个公众号商城,小明进入商城后会产出3个不同的OpenID:
公众号 | 唯一标识 |
---|---|
A | OpenID__A |
B | OpenID__B |
C | OpenID__C |
那岂不是有3个小明用户?简单想像一下,你从公众号里进入一个网页版的拼夕夕商城,账号里有10个拼豆,但是进入小程序版的拼夕夕商城,账号里却只有1个拼豆,是不是很茫然?为了防止这样的“怪异”现象发生,我们要解决的问题是:要如何确定小明这个人在进入“同一个”商城中,是“同一个”用户呢?那就是使用 UnionID
啦。
「应用授权作用域」
网页授权scope分两种:
- snsapi_base:不弹出授权页面,直接跳转,即静默授权,但只能获取用户OpenID。
- snsapi_userinfo:弹出授权页面,获取用户基本信息,需用户手动同意授权,一旦同意授权,即可在不关注公众号的情况下获取用户信息。
❝提示:
❞
- 对于已关注公众号的用户,如果用户从公众号的会话或者自定义菜单进入本公众号的网页授权页,即使是scope为snsapi_userinfo,也是静默授权,用户无感知。
- 用户管理类接口中的“获取用户基本信息接口”,是在用户和公众号产生消息交互或关注事件推送后,才能根据用户OpenID来获取用户基本信息。这个接口,包括其他微信接口,都是需要该用户(即openid)关注了公众号后,才能调用成功的。
「相关域名介绍」
- 业务域名:项目部署域名,如果不设置,微信会提示不安全,“For security,never share your password.”
- JS接口安全域名:调用JS-SDK时配置;
- 网页授权域名:获取用户唯一标识(
OpenID
)或用户信息时配置;
准备
首先注册公众号,然后在公众号后台 “「开发 - 基本配置」 ” 中获取
appID
和appsecret
。接下来你需要在公众后台 “「设置 - 公众号设置 - 功能设置」” 中完善
业务域名
、JS接口安全域名
和网页授权域名
配置项。
❝提示:
❞
- 如果项目中没有用到JS-SDK,则
JS接口安全域名
可不配置。- 由于
业务域名
、JS接口安全域名
和网页授权域名
配置项在设置时有修改次数及配置域名个数限制。所以在开发阶段我们可以使用测试号(测试号领取链接:https://mp.weixin.qq.com/debug/cgi-bin/sandbox?t=sandbox/login)开发,但需要注意的是,测试号无法获取用户的UnionID
以及拉起微信支付弹框等操作。
思路
网页授权流程大致分为四步:
第一步:用户同意授权,获取code
第二步:通过code换取网页授权access_token(后端实现)
第三步:刷新access_token(如果需要)(后端实现)
第四步:拉取用户信息(需scope为 snsapi_userinfo)(后端实现)
其中,第二步到第四步由后端实现,所以前端只需要拿到用户授权code之后发送给后端即可。
❝提示:code作为换取access_token的票据,每次用户授权带上的code将不一样,code只能使用一次,5分钟未被使用自动过期。
❞
实际开发中,我们需结合实际场景处理网页授权。所以,思路应该是这样的:在公众号H5入口页判断本地是否存在token,如果存在则校验token是否过期,倘若token不存在,或者token已过期则直接跳转至授权页,再执行登陆操作。否则直接跳转至主页。一般来说,如果token存在我们也可以不用去校验token是否过期,因为当我们拿着过期的token去请求数据时,后端会返回表示token过期的状态码,此时我们可以在ajax请求拦截中做处理。
实现
这里主要以伪代码实现,仅供大家参考,首先专门定义一个路由 /auth/:type
处理授权,由于网页授权还需一个回调页,为了便于集中处理,我一般是将授权和回调逻辑写在一个路由中的,即:
/auth/jump
:授权跳转页;
/auth/callback
:授权回调页;
接下来,我们一起看看具体实现方式。
「1. 校验是否授权」
为了不让用户每次访问公众号页面时就去拉一次授权,所以我们需在项目入口文件中判断本地是否存在用户上一次授权登陆之后保存下来的token,如果存在,还得判断token是否过期,当token不存在或者已过期时,我们才会去拉授权,伪代码如下:
// 判断本地是否存在token
if(localStorage.getItem('token')) {
// 存在:判断token是否过期
const isExpire = checkToken();
if(isExpire) {
// token已过期:跳转授权页
history.replace('/auth/jump');
}else {
// token未过期:跳转至首页
history.replace('/index');
}
} else {
// 不存在:跳转授权页
history.replace('/auth/jump');
}
「2. 处理授权」
由于我习惯性将授权和回调写在一个文件里,所以我们可以根据path参数判断是处理授权还是处理回调。
我们先来看看如何处理跳转微信授权页:
const handleJump = () => {
// 授权后重定向的回调链接地址
const redirect_uri = encodeURIComponent(`${window.location.origin}/auth/callback`);
const appId = 'wx169565989539bf7d';
// 重定向后会带上state参数
const state = 'Muzili';
// 拼接微信授权地址
const url = `https://open.weixin.qq.com/connect/oauth2/authorize?appid=${appId}&redirect_uri=${redirect_uri}&response_type=code&scope=snsapi_userinfo&state=${state}#wechat_redirect`;
// 跳转至微信授权页
window.location.replace(url);
}
如果用户同意授权,页面将跳转至 redirect_uri/?code=CODE&state=STATE (携带code及state参数),接下来我们处理回调:
const handleCallback = () => {
// 解析query参数
const { code, state } = Tools.query();
// 调用登录接口,将code交由后台解析用户信息
Api.user.login({
code
}).then(res => {
// 存储token
localStorage.setItem('token', res.token);
// 跳转至首页
history.replace('/index');
})
}
*上述代码中的 Tools
为耀哥封装的工具类 lg-tools
,已发布至npm。
以上代码仅供参考,给大家一个实现思路,大家可据此一试。
扩展
「1. 授权登陆之后,由于长时间没有操作,token过期了怎么办?」
这种情况是比较普遍的,为了安全考虑,后端一般会给token设置有效期,如果你在有效期内请求了接口,后端会自动续期(根据后端策略),那如果用户长时没有操作,token就会失效,此时用户再次发起请求时后端就会返回token过期的信息,显然得重新拉一次授权进行登录。当页面和请求较多时,这个问题可以在请求响应拦截器里面处理,通常后端会返回一个状态码,比如token过期的状态码是 -10
,当你判断后端返回的状态码为 -10
时直接跳转至 /auth
授权页就行了,这一系列操作想必各位已经比较熟悉了。但是目前存在的问题是,假设我当前在/details
页面中,当我跳转至 /auth
授权页授权登陆之后,我又如何回到 /details
路由呢?这个问题其实特别容易解决,我们只需在拦截器里跳转至授权页时将当前页的路由作为参数传递给 /auth/jump
页,如下所示:
// 响应拦截
service.interceptors.response.use(async (response, options) => {
const res = await response.clone().json();
// 为了避免在一个页面中同时发起多次请求跳转多次授权页
// 所以判断:如果当前已经在授权页才接收到另一次请求的过期状态则不作处理
if(res.code === -10 && !/auth/.test(location.href)) {
// token 过期/重新授权
history.replace(`/auth/jump?from=${encodeURIComponent(location.href.replace(location.origin, ''))}`)
}
return res;
});
然后再将其作为 state 值一并发送给微信授权即可:
const handleJump = () => {
const redirect_uri = encodeURIComponent(`${window.location.origin}/auth/callback`);
const appId = 'wx169565989539bf7d';
// 获取跳转链接/如果不存在则默认跳转至首页
const state = Tools.query('from') || '/index';
const url = `https://open.weixin.qq.com/connect/oauth2/authorize?appid=${appId}&redirect_uri=${redirect_uri}&response_type=code&scope=snsapi_userinfo&state=${state}#wechat_redirect`;
window.location.replace(url);
}
当授权回调时,会将code和state作为query参数一并返回,所以你只需要在授权回调页拿到state作为跳转路由即可。
const handleCallback = () => {
const { code, state } = Tools.query();
Api.user.login({
code
}).then(res => {
localStorage.setItem('token', res.token);
// 跳转至指定路由
history.replace(state);
})
}
尾言
好啦,各位小伙伴,今天的分享就到这里啦,是不是很简单呢?赶快去试试吧。如果您喜欢耀哥的分享,还请点一波关注,您的关注与支持,是我唯一写作下去的动力。