微信公众号通过第三方平台完成授权前,第三方平台与公众号绑定关系。
为什么要使用第三方平台来完成授权,公众号本身授权也可以,但是微信平台会认为你具有开发能力,免费提供给你的自定义菜单以及其他功能将不能再使用(亲身经历的一个坑,开始使用公众号授权,结束后发现部分功能不能使用)。
这个地方就是公众号自己授权需要先向微信提供服务器地址,微信会把公众号的一些关注事件,推送的消息等。传递参数到这个地址,我们需要在5s内响应字符串:success给微信,否则微信认为该服务器有问题而不会推送参数过来。当然就是这个服务器地址启用后,会导致公众号本身提供的自定义菜单,消息回复功能等失效。
所以采用了第三方平台来完成公众号的授权,其实授权流程跟公众号授权差不多。首先需要跟公众号一样需要申请一个第三方平台账户(需要200块)。
在第三方平台创建审核通过后,微信服务器会向其 ”授权事件接收URL” 每隔 10 分钟以 POST 的方式推送 component_verify_ticket,
接收 POST 请求后,只需直接返回字符串 success。
第三方平台需要填写 :
1.授权事件接收URL
2.公众号的appid
3.ip白名单
其他需要填写的具体看需要那些。
授权事件接收URL会接收到微信推送的xml字符串,需要将xml解析成json:
public static JSONObject xml2Json(String xmlStr) throws DocumentException {
Document doc= DocumentHelper.parseText(xmlStr);
JSONObject json=new JSONObject();
dom4j2Json(doc.getRootElement(), json);
return json;
}
public static void dom4j2Json(Element element, JSONObject json) {
//如果是属性
for (Object o : element.attributes()) {
Attribute attr = (Attribute) o;
if (StringUtils.isNotEmpty(attr.getValue())) {
json.put("@" + attr.getName(), attr.getValue());
}
}
List<Element> childrenElement = element.elements();
if (CollectionUtils.isEmpty(childrenElement) && StringUtils.isNotEmpty(element.getText())) {//如果没有子元素,只有一个值
json.put(element.getName(), element.getText());
}
for (Element e : childrenElement) {//有子元素
if (CollectionUtils.isNotEmpty(e.elements())) {//子元素也有子元素
JSONObject childJson = new JSONObject();
dom4j2Json(e, childJson);
Object o = json.get(e.getName());
if (o != null) {
JSONArray jsona = null;
if (o instanceof JSONObject) {//如果此元素已存在,则转为jsonArray
JSONObject jsono = (JSONObject) o;
json.remove(e.getName());
jsona = new JSONArray();
jsona.add(jsono);
jsona.add(childJson);
}
if (o instanceof JSONArray) {
jsona = (JSONArray) o;
jsona.add(childJson);
}
json.put(e.getName(), jsona);
} else {
if (!childJson.isEmpty()) {
json.put(e.getName(), childJson);
}
}
} else {//子元素没有子元素
for (Object o : element.attributes()) {
Attribute attr = (Attribute) o;
if (StringUtils.isNotEmpty(attr.getValue())) {
json.put("@" + attr.getName(), attr.getValue());
}
}
if (StringUtils.isNotEmpty(e.getText())) {
json.put(e.getName(), e.getText());
}
}
}
}
从解析的json中拿到字符串 Encrypt ,timestamp,nonce,msg_signature:
使用AES解密:
public class GetMsessage{
public void getThridMse(JSONObject getData) {
String encrypt = json.getString("Encrypt");
String timeStamp = json.getString("timestamp");
String nonce = json.getString("nonce");
String msgSignature = json.getString("msg_signature");
String componentAppId = "第三方平台appid";
String appSecret = "第三方平台Secret";
String encodingAesKey = "第三方平台的AES密钥";
String token = "第三方平台认证服务时填写的一个字符串";
JSONObject resJson= getCryptMsg(token,encodingAesKey,componentAppId,encrypt,msgSignature,timeStamp,nonce);
String verifyTicket = resJson.getString("ComponentVerifyTicket");
String eventKey = resJson.getString("EventKey");
String event = resJson.getString("Event");
String toUserName = resJson.getString("ToUserName");
String fromUserName = resJson.getString("FromUserName");
String ticket = resJson.getString("Ticket");
}
public JSONObject getCryptMsg(String token,String encodingAesKey,String appId,String encrypt,String msgSignature,String timeStamp,String nonce){
BaseLog.getDailyLog().info("token[{}] encodingAesKey[{}] appId[{}] encrypt[{}] msgSignature[{}] timeStamp[{}] nonce[{}]",
token, encodingAesKey, appId, encrypt, msgSignature, timeStamp, nonce);
WXBizMsgCrypt wxBizMsgCrypt = null;
try {
wxBizMsgCrypt = new WXBizMsgCrypt(token,encodingAesKey,appId);
} catch (Exception e) {
e.printStackTrace();
}
JSONObject json = new JSONObject();
try {
String xml = wxBizMsgCrypt.decryptMsg(msgSignature,timeStamp,nonce,encrypt);
try {
json = xml2Json(xml);
} catch (DocumentException e) {
e.printStackTrace();
}
BaseLog.getDailyLog().error("解析xml为Json的内容为:"+json.toJSONString());
return json;
} catch (AesException e) {
e.printStackTrace();
BaseLog.getErrorLog().error(e.getMessage(), e);
throw new ServiceException(OAuth2ErrorCodeEnum.FAILED_TO_PARSING_XML.getCode(), OAuth2ErrorCodeEnum.FAILED_TO_PARSING_XML.getMsg());
}
}
}
String verifyTicket = resJson.getString(“ComponentVerifyTicket”);
拿到票据后去拿第三方平台的令牌
public JSONObject getComponentAccessToken(String appId,String appSecret,String componentVerifyTicket,String componentAccessTokenUrl){
/* https://api.weixin.qq.com/cgi-bin/component/api_component_token */
JSONObject object = new JSONObject();
object.put("component_appid",appId);
object.put("component_appsecret",appSecret);
object.put("component_verify_ticket",componentVerifyTicket);
String result = doPost(componentAccessTokenUrl, object.toString(), "utf-8");
BaseLog.getDailyLog().error("获取第三方令牌结果;"+result);
JSONObject resultJson=JSONObject.parseObject(result);
return resultJson;
}
component_appid是第三方平台的appid,component_appsecret是第三平台的secret。
上面post请求返回的结果里面有 component_access_token,第三方平台 access_token用来获取预授权码:
public JSONObject getPreAuthCode(String preAuthCodeUrl,String componentToken,String appId){
/*
POST https://api.weixin.qq.com/cgi-bin/component/api_create_preauthcode
?component_access_token=COMPONENT_ACCESS_TOKEN
*/
StringBuilder sb = new StringBuilder(preAuthCodeUrl);
String url = sb.append("?").append("component_access_token=").append(componentToken).toString();
JSONObject object = new JSONObject();
object.put("component_appid",appId);
String result = doPost(url, object.toString(), "utf-8");
BaseLog.getDailyLog().error("获取第三方预授码结果;"+result);
JSONObject resultJson=JSONObject.parseObject(result);
return resultJson;
}
从上面拿到的预授权码 pre_auth_code 后:
组装一个授权的链接,访问这个链接会生成一个二维码,用公众号拥有者扫描去给第三方平台授权。
然后登录公众号里面在这个地方给第三方平台需要用到的权限。
到此,是把公众号跟第三方平台绑定成功,给第三方平台授权完成(并不是真正的公众号授权拿到用户信息)。
从上面可以看出,整个代码过程没有用到公众号的信息,所以第三方平台可以给跟个公众号绑定,完成授权。
再后面就是授权了,按公众号授权一样了,先拿到code,再拿token,就有openid然后就可以拿用户信息了。