前言
名词解释:
微信:就是我们常用的个人微信
企微:有企业资质认证的企业微信,可以与个人微信信息互通
首先了解下工作原理
微信授权
- 授权时序图
- 关键参数说明
/**
* 微信授权请求链接地址
* appid: 微信公众号appid
* redirectUri: 授权成功后的重定向网页地址
* scope: snsapi_base | snsapi_userinfo
* extraParam: 额外的参数,会在微信授权成功后,拼接在重定向地址后面
*/
const wxAuthUrl = `https://open.weixin.qq.com/connect/oauth2/authorize
?appid=${appid}
&redirect_uri=${redirectUri}
&response_type=code
&scope=${scope}
&state=${extraParam}
#wechat_redirect`;
/**
* 微信授权成功后的重定向地址
* code: 微信授权成功后,返回的临时code口令,后续授权接口请求的参数需要用到
* extraParam: 上一步发起授权时拼接的参数
*/
const wxAuthRedirectUrl = `https://api.xxx.com/wx/page
?code=${code}
&state=${extraParam}`;
/**
* 微信获取用户信息的API接口,由于参数涉及到非公开的密钥,所以最好在服务器内发起请求
* 此接口,仅能获取到用户access_token, openid,
* 其中unionid(只有当 scope 为"snsapi_userinfo"时才有返回值)
* appid: 微信公众号appid
* secret: 微信公众号密钥,从微信公众号管理后台配置页面获取
* code: 上一步微信授权成功后的重定向地址拼接的临时code
*/
const api = `https://api.weixin.qq.com/sns/oauth2/access_token
?appid=${appid}
&secret=${secret}
&code=${code}
&grant_type=authorization_code`
/**
* 获取用户详细信息
* accessToken: 上一步获取到的accessToken
* openid: 上一步获取到的用户openid(用户在当前公众号内的唯一标识)
*/
const userInfoApi = `https://api.weixin.qq.com/sns/userinfo
?access_token=${accessToken}
&openid=${openid}
&lang=zh_CN`
企微授权
- 授权时序图,几乎和微信授权逻辑一致。
下面是官方提供的授权时序图
- 在关键参数上略有差异
/**
* 企业微信授权请求链接地址
* corpId: 企微的corpId
* redirectUri: 授权成功后的重定向网页地址
* scope: snsapi_base
* extraParam: 额外的参数,会在微信授权成功后,拼接在重定向地址后面
*/
const wxAuthUrl = `https://open.weixin.qq.com/connect/oauth2/authorize
?appid=${corpId}
&redirect_uri=${redirectUri}
&response_type=code
&scope=${scope}
&state=${extraParam}
#wechat_redirect`;
/**
* 企业微信授权成功后的重定向地址
* code: 微信授权成功后,返回的临时code口令,后续授权接口请求的参数需要用到
* extraParam: 上一步发起授权时拼接的参数
*/
const wxAuthRedirectUrl = `https://xxxxx.com/wx/page
?code=${code}
&state=${extraParam}`;
/**
* 企业微信获取用户信息的API接口,由于参数涉及到非公开的密钥,所以最好在服务器内发起请求
* 此接口,仅能获取到用户access_token, openid, unionid
* corpId: 企业微信的corpId
* secret: 企业微信账号的密钥,从企微管理后台获取
*
* return access_token
*/
const getAccessTokenApi = `https://qyapi.weixin.qq.com/cgi-bin/gettoken
?corpid=${corpId}
&corpsecret=${secret}`
/**
* 企业微信获取用户信息的API接口,由于参数涉及到非公开的token,所以最好在服务器内发起请求
* 此接口,仅能获取到用户的userid
* accessToken: 上一步获取到的access_token
* code: 上面重定向Url后携带的code
*
* return access_token
*/
const getUserIdApi = `https://qyapi.weixin.qq.com/cgi-bin/user/getuserinfo
?access_token=${accessToken}
&code=${code}`
/**
* 获取用户详细信息
* accessToken: 上一步获取到的accessToken
* userid: 上一步获取到的用户userid(用户在当前企业微信内的唯一标识)
*/
const getUserInfoApi = `https://qyapi.weixin.qq.com/cgi-bin/user/get
?access_token=${accessToken}
&userid=${userid}`
微信SDK注册
1. 引入JS文件
// 标签方式,在入口index.html中引入
// 备用地址//res2.wx.qq.com/open/js/jweixin-1.6.0.js
<script src="//res.wx.qq.com/open/js/jweixin-1.6.0.js"></script>
// 或者使用动态创建script的方式引入
const script = document.createElement('script');
script.src = '';
2. JSSDK加载完成后,进行初始配置
wx.config({
debug: true, // 开启调试模式,调用的所有 api 的返回值会在客户端 alert 出来
appId: '', // 必填,公众号的唯一标识
timestamp: , // 必填,生成签名的时间戳,由后端服务定义好
nonceStr: '', // 必填,生成签名的随机串,由后端服务定义好
signature: '',// 必填,签名,后端接口,通过timestamp,nonceStr调用微信API生成
jsApiList: [] // 必填,需要使用的 JS 接口列表
});
3. 通过 ready 接口处理成功验证
wx.ready(function(){
// config信息验证后会执行 ready 方法,所有接口调用都必须在 config 接口获得结果之后,config是一个客户端的异步操作,所以如果需要在页面加载时就调用相关接口,则须把相关接口放在 ready 函数中调用来确保正确执行。对于用户触发时才调用的接口,则可以直接调用,不需要放在 ready 函数中。
});
4. 通过 error 接口处理失败验证
wx.error(function(res){
// config信息验证失败会执行 error 函数,如签名过期导致验证失败,具体错误信息可以打开 config 的debug模式查看,也可以在返回的 res 参数中查看,对于 SPA 可以在这里更新签名。
});
企微SDK注册
与微信SDK注册方式完全一致
区别:
1. 初始化配置的签名接口不同
2. 可使用的API是独立的,参考官方文档使用
正确的使用姿势
微信授权
- 前端页面重定向至 -> 后端A接口
// 微信回调后端的请求地址
const redirectUrl = location.href;
// 跳转后端授权地址
location.href = `https://api.xxxx.com/wxauth
?redirectUrl=${encodeURIComponent(redirectUrl)}`;
- 后端A接口 -> 重定向微信授权网址 -> 回调到后端B接口->通过授权token获取微信用户信息 -> 后端B接口重定向前端页面地址,URL后面拼接用户unionid/sessionid
// 字符串拼接用的是JS写法(偷个懒)
// 接口A
@RequestMapping(value = "/wxauth",method = RequestMethod.GET)
private RES wxauth(@RequestParam("redirectUrl") String redirectUrl){
// 缓存redirectUrl
Redis.set(uuid, redirectUrl);
String redi = `https://open.weixin.qq.com/connect/oauth2/authorize
?appid=${appid}
&redirect_uri='/wxlogin' // 接口B
&response_type=code
&scope=${scope}
&state=${uuid}
#wechat_redirect
`
// 跳转微信授权页面
return 'redirect:' + redirectUrl;
}
// 接口B 来自微信的回调,获取到临时code参数
@RequestMapping(value = "/wxlogin",method = RequestMethod.GET)
private RES wxlogin(@RequestParam("code") String code, @RequestParam("state") String state){
//1.登录凭证验证,获取access_token、openid
String tokenRes = HttpUtil.sendGet(
`https://api.weixin.qq.com/sns/oauth2/access_token
?appid=${appid}
&secret=${secret}
&code=${code}
&grant_type=authorization_code`);
//2.获取用户的unionid、头像、昵称信息(前提:网页授权作用域必须为snsapi_userinfo)
String userInfoRes = HttpUtil.sendGet(
`https://api.weixin.qq.com/sns/userinfo
?access_token=${accessToken}
&openid=${openid}
&lang=zh_CN`);
try {
WxMiniUser wxMiniUser = JSON.parseObject(userInfoRes, WxMiniUser.class);
return 'redirect:' + redirectUrl + '?sessionid=' + sessionId;
}catch (Exception e){
e.printStackTrace();
}
}
- 页面入口处,获取URL上的参数unionid/sessionid,请求接口获取当前用户相关的业务信息
setup(){
const {sessionid} = route.query
const userInfo = getUserInfo(sessionid);
return ()=> <div>{userInfo.nickname}</div>
}
企微授权
总体流程和微信授权一致,注意区分appid、corpId
微信/企微SDK的使用
SDK的使用,主要关注一个调用时机问题。
- 页面加载完成后,立即要调用的API,统一放到ready的回调中去
-
由用户行为:点击、滑动...交互操作,可以直接调用,做好异常处理就行(比如SDK注册失败后,页面是否允许用户正常浏览,相关的API调用直接跳过,还是....)
经验汇总
微信授权快照说明与方案
当开发者在网页中在不规范使用发起 snsapi_userinfo 网页授权时,微信将默认打开网页快照页模式进行基础浏览。
如上图所示,当我们在已进入页面,且用户还未产生任何交互时,进行了微信授权跳转,微信就会生成快照页来阻止我们获取用户信息。
注意:此时我们在快照页内所获取的头像、昵称、openId、unionId 均为虚拟账号数据,可能会对产生垃圾数据
不规范的行为有:
- 强制登录: 在用户打开网页时立即要求用户授权,用户拒绝后无法使用网页提供的服务;
- 违规收集个人信息: 未在网页提前告知使用个人信息的目的、方式和范围;
- 非必要收集: 非必要获取用户信息的网页,如文章、视频等,要求用户在浏览内容前登录;
- 差别对待微信用户: 同样的网页在浏览器内可以无需登录直接访问,在微信内却要求用户先登录才可访问。
目前能规避微信H5页面生成快照的方式
-
自定义游客模式页面,即用户每次进入页面时,都会进入无用户信息的游客页面,用户需要通过页面中的按钮等引导操作,触发微信授权后,才能进行后续的操作。(如果已授权的用户,除了没授权弹窗外,其他流程和游客模式一致)
-
静默授权与信息授权搭配使用,即通过静默授权获取到openid,进行接口请求,判断是否已存在当前用户的unionid,如果有,则直接返回当前用户的信息,如果没有,再跳转至游客模式页面,进行重新授权。
ps: 这种方案,需要后端存储一份openid -> unionid的映射表,且之前是有存储过用户对应此公众号的openid的。
微信内置浏览器的localStorage、cookie 都存在不稳定的情况,无法长期存储、或切换微信账号后用户信息混乱问题,在微信还未提供明确的官方说明时,不推荐使用。