微信开发-回调模式
Posted Season
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了微信开发-回调模式相关的知识,希望对你有一定的参考价值。
前言
越来越多的企业借助微信平台做开发,下面记录最近开发微信项目(企业号)一些关键设置、原理及代码
一:添加应用
关注企业微信号后,点击企业号,能看到该企业号下的应用列表,它类似我们常见后台中的模块或栏目,首先我们要创建自己的应用。
二:设置应用为回调模式
创建好应用后,把应用设置成回调模式,按要求设置回调URL及密钥。保存时它会访问URL,只有URL能正确访问信息时才能保存成功,否则一直会提示失败
原理
以下摘自微信开发文档:
验证URL有效性
当你提交以上信息时,企业号将发送GET请求到填写的URL上,GET请求携带四个参数,企业在获取时需要做urldecode处理,否则会验证不成功。
参数 | 描述 | 是否必带 |
---|---|---|
msg_signature | 微信加密签名,msg_signature结合了企业填写的token、请求中的timestamp、nonce参数、加密的消息体 | 是 |
timestamp | 时间戳 | 是 |
nonce | 随机数 | 是 |
echostr | 加密的随机字符串,以msg_encrypt格式提供。需要解密并返回echostr明文,解密后有random、msg_len、msg、$CorpID四个字段,其中msg即为echostr明文 | 首次校验时必带 |
企业通过参数msg_signature对请求进行校验,如果确认此次GET请求来自企业号,那么企业应用对echostr参数解密并原样返回echostr明文(不能加引号,不能带bom头,不能带换行符),则接入验证生效,回调模式才能开启。
实操
为了能正确验证URL有效性,还需要以下操作
首先,我们需要一台服务器,并且有一个域名指向了该服务器,在该服务器上配置好网站。把验证URL的代码放置在该服务器上。wxpush代码如下:
package com.bf.weixin; import java.io.IOException; import java.io.PrintWriter; import javax.servlet.ServletException; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import com.bf.meal.entity.App; import com.qq.weixin.mp.aes.AesException; import com.qq.weixin.mp.aes.WXBizMsgCrypt; public class wxpush extends HttpServlet { public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { WXBizMsgCrypt wxcpt = null; try { wxcpt = new WXBizMsgCrypt(App.getToken(), App.getEncodingAESKey(), App.getCorpID()); } catch (AesException e1) { e1.printStackTrace(); } // 解析出url上的参数值如下: String sVerifyMsgSig = request.getParameter("msg_signature"); String sVerifyTimeStamp = request.getParameter("timestamp"); String sVerifyNonce = request.getParameter("nonce"); String sVerifyEchoStr = request.getParameter("echostr"); App.logger.info("url:" + request.getQueryString()); String sEchoStr; //需要返回的明文 try { sEchoStr = wxcpt.VerifyURL(sVerifyMsgSig, sVerifyTimeStamp, sVerifyNonce, sVerifyEchoStr); App.logger.info("verifyurl echostr: " + sEchoStr); response.setContentType("text/html"); PrintWriter out = response.getWriter(); out.println(sEchoStr); out.flush(); out.close(); } catch (Exception e) { App.logger.error(e); e.printStackTrace(); } } }
注:需要到微信官网上下载
com.qq.weixin.mp.aes.AesException;
com.qq.weixin.mp.aes.WXBizMsgCrypt;
当然web.xml中还得有如下节点
<servlet-mapping> <servlet-name>wxpush</servlet-name> <url-pattern>/servlet/wxpush</url-pattern> </servlet-mapping>
配置好后,应该能正确的访问URL(http://*.*.com.cn/meal/servlet/wxpush),把该URL作为微信后台填写的值,此时应该能正确保存了。
三:获取用户帐号信息
要针对企业号关注用户做一些应用,最重要一点是要识别该用户,以便能正确区分不同的用户。
根据文档OAuth2.0验证接口说明, 建议的方案
1、企业应用中的URL链接直接填写企业自己的页面地址
2、成员跳转到企业页面时,企业校验是否有代表成员身份的cookie,此cookie由企业生成
3、如果没有获取到cookie,重定向到OAuth验证链接,获取成员身份后,由企业生成代表成员身份的cookie
4、根据cookie获取成员身份,进入相应的页面
我的做法是,配置Filter拦截所有jsp或.do文件的请求,在Filter中先去Cookie,如果Cookie不存在,则调用OAuth2.0获取信息,具体还是看关键代码
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws ServletException, IOException { HttpServletRequest request = (HttpServletRequest) req; HttpServletResponse response = (HttpServletResponse) res; String userId = getUserId(request, response); ……
private String getUserId( HttpServletRequest request, HttpServletResponse response) throws IOException{ Cookie cookieUser = CookieHelper.getCookieByName(request, App.COOKIE_USER); String userId = ""; if (cookieUser != null) { userId = cookieUser.getValue(); } else { String code = request.getParameter("code"); //企业code if (code == null) //为空时,需 构造OAuth链接,OAuth返回时会带上code { String url = request.getRequestURL().toString(); url = OAuthHelper.OAuth(java.net.URLEncoder.encode(url, "UTF-8")); response.sendRedirect(url); return ""; } try { userId = OAuthHelper.getUserByCode(code); } catch (Exception e) { } if (userId == null) { return ""; } CookieHelper.addCookie(response, App.COOKIE_USER, userId, App.Cookie_Age); } return userId; }
下面为封装的工具类,与项目无关
public class OAuthHelper { static Logger logger = Logger.getLogger(OAuthHelper.class.getName()); private static String Token_URL = "https://qyapi.weixin.qq.com/cgi-bin/gettoken?corpid=%s&corpsecret=%s"; private static String OAuth_URL = "https://open.weixin.qq.com/connect/oauth2/authorize?appid=%s&redirect_uri=%s&response_type=code&scope=snsapi_base&state=ATTENDANCE#wechat_redirect"; private static String GetUserInfoUrl = "https://qyapi.weixin.qq.com/cgi-bin/user/getuserinfo?access_token=%s&code=%s"; public static String getUserByCode(String code) throws Exception{ String userId = null; String token = getToken(); String url = String.format(GetUserInfoUrl, token, code); OAuth oauthInfo = HttpHelper.GetOAuthInfo(url); if (oauthInfo != null) { userId = oauthInfo.getUserId(); } return userId; } public static String OAuth(String url){ String fullUrl = String.format(OAuth_URL, App.getCorpID(), url); return fullUrl; } private static String getToken() throws Exception{ String url = String.format(Token_URL, App.getCorpID(), App.getCorpSecret()); return HttpHelper.GetTokenInfo(url).getAccessToken(); } }
下面最好是用泛型消除重复代码
import com.google.gson.Gson; import com.google.gson.GsonBuilder; public class HttpHelper { public static OAuth GetOAuthInfo(String url) throws IOException{ CloseableHttpClient httpclient = HttpClients.createDefault(); HttpGet httpget = new HttpGet(url); ResponseHandler<OAuth> rh = new ResponseHandler<OAuth>() { @Override public OAuth handleResponse(final HttpResponse response) throws IOException { StatusLine statusLine = response.getStatusLine(); HttpEntity entity = response.getEntity(); if (statusLine.getStatusCode() >= 300) { throw new HttpResponseException( statusLine.getStatusCode(), statusLine.getReasonPhrase()); } if (entity == null) { throw new ClientProtocolException("Response contains no content"); } Gson gson = new GsonBuilder().create(); ContentType contentType = ContentType.getOrDefault(entity); Reader reader = new InputStreamReader(entity.getContent(), contentType.getCharset()); return gson.fromJson(reader, OAuth.class); } }; OAuth myjson = httpclient.execute(httpget, rh); return myjson; } public static WeChartToken GetTokenInfo(String url) throws Exception{ SslHelper.ignoreSsl(); CloseableHttpClient httpclient = HttpClients.createDefault(); HttpGet httpget = new HttpGet(url); ResponseHandler<WeChartToken> rh = new ResponseHandler<WeChartToken>() { @Override public WeChartToken handleResponse(final HttpResponse response) throws IOException { StatusLine statusLine = response.getStatusLine(); HttpEntity entity = response.getEntity(); if (statusLine.getStatusCode() >= 300) { throw new HttpResponseException( statusLine.getStatusCode(), statusLine.getReasonPhrase()); } if (entity == null) { throw new ClientProtocolException("Response contains no content"); } Gson gson = new GsonBuilder().create(); ContentType contentType = ContentType.getOrDefault(entity); Reader reader = new InputStreamReader(entity.getContent(), contentType.getCharset()); return gson.fromJson(reader, WeChartToken.class); } }; WeChartToken myjson = httpclient.execute(httpget, rh); return myjson; } }
import java.security.cert.CertificateException; import java.security.cert.X509Certificate; import javax.net.ssl.HostnameVerifier; import javax.net.ssl.HttpsURLConnection; import javax.net.ssl.SSLContext; import javax.net.ssl.SSLSession; import javax.net.ssl.TrustManager; import javax.net.ssl.X509TrustManager; public class SslHelper { private static void trustAllHttpsCertificates() throws Exception { TrustManager[] trustAllCerts = new TrustManager[1]; TrustManager tm = new miTM(); trustAllCerts[0] = tm; SSLContext sc = SSLContext.getInstance("SSL"); sc.init(null, trustAllCerts, null); HttpsURLConnection.setDefaultSSLSocketFactory(sc.getSocketFactory()); } static class miTM implements TrustManager,X509TrustManager { public X509Certificate[] getAcceptedIssuers() { return null; } public boolean isServerTrusted(X509Certificate[] certs) { return true; } public boolean isClientTrusted(X509Certificate[] certs) { return true; } public void checkServerTrusted(X509Certificate[] certs, String authType) throws CertificateException { return; } public void checkClientTrusted(X509Certificate[] certs, String authType) throws CertificateException { return; } } /** * 忽略HTTPS请求的SSL证书,必须在openConnection之前调用 * @throws Exception */ public static void ignoreSsl() throws Exception{ HostnameVerifier hv = new HostnameVerifier() { public boolean verify(String urlHostName, SSLSession session) { System.out.println("Warning: URL Host: " + urlHostName + " vs. " + session.getPeerHost()); return true; } }; trustAllHttpsCertificates(); HttpsURLConnection.setDefaultHostnameVerifier(hv); } }
public class App { private static Properties config; private App() {} private static Properties getInstance(){ if( config == null ) { config = new Properties(); try { config.load(App.class.getClassLoader().getResourceAsStream("/config.properties")); } catch (IOException e) { e.printStackTrace(); } } return config; } public static String getToken() { return getInstance().getProperty("Token"); } public static String getCorpID() { return getInstance().getProperty("CORP_ID"); } public static String getCorpSecret() { return getInstance().getProperty("CORP_SECRET"); } public static String getEncodingAESKey() { return getInstance().getProperty("EncodingAESKey"); } public static String getSERVICE_URL() { return getInstance().getProperty("SERVICE_URL"); } }
最后是src/config.properties配置文件:
Token = *** CORP_ID = *** CORP_SECRET = *** EncodingAESKey = ***
以及实体类
public class OAuth { private String UserId; public String getUserId() { return UserId; } public void setUserId(String userId) { UserId = userId; } } public class WeChartToken { private String access_token; public String getAccessToken() { return access_token; } public void setAccessToken(String access_token) { this.access_token = access_token; } }
补充
问题一:今天在做新的微信项目时,以上代码一直提示解密失败
尝试获取参数方式为:
String sVerifyMsgSig = URLDecoder.decode(request.getParameter("msg_signature"),"utf-8"); String sVerifyTimeStamp = URLDecoder.decode(request.getParameter("timestamp"),"utf-8"); String sVerifyNonce = URLDecoder.decode(request.getParameter("nonce"),"utf-8"); String sVerifyEchoStr = URLDecoder.decode(request.getParameter("echostr"),"utf-8");
本地显示解密成功后,上传到服务器提示解密失败。
根据 http://blog.csdn.net/omsvip/article/details/40380465,去Oracle官网下载jce_policy-8.zip,把解压后得到的两个jar包,
覆盖C:\\Program Files\\Java\\jre1.8.0_65\\lib\\security目录下原有文件即可。
问题二:redirect_uri参数错误
加解密问题解决后,通过微信打开链接一直报"redirect_uri参数错误"。
原因:没有设置好可信域名。
注意:有两个地方可以设置可信域名,一个是在应用中心->自建应用,模式选择上面,还有一处是在设置->功能设置处,之所以出错是我在第二处设置了而非第一处。
最后放出基于Spring MVC的web.xml
<web-app xmlns="http://java.sun.com/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd" version="2.5"> <display-name>wim</display-name> <description>wim</description> <filter> <filter-name>WeChartFilter</filter-name> <filter-class>com.XX.weixin.WeChartFilter</filter-class> </filter> <filter-mapping> <filter-name>WeChartFilter</filter-name> <url-pattern>*</url-pattern> /*这里设置filter是要让每个页面访问之前,先去取微信用户ID*/ </filter-mapping> <!-- For web context --> <servlet> <servlet-name>spring-dispatcher</servlet-name> <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class> <init-param> <param-name>contextConfigLocation</param-name> <param-value>/WEB-INF/spring-mvc-config.xml</param-value> </init-param> <load-on-startup>1</load-on-startup> </servlet> <servlet-mapping> <servlet-name>spring-dispatcher</servlet-name> <url-pattern>/</url-pattern> </servlet-mapping> <servlet> <servlet-name>WeChatCallback</servlet-name> <servlet-class>com.XX.weixin.WeChatCallback</servlet-class> /*回调地址设置*/ </servlet> <servlet-mapping> <servlet-name>WeChatCallback</servlet-name> <url-pattern>/callback</url-pattern> </servlet-mapping> <!-- For root context --> <listener> <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class> </listener> <listener> <listener-class>org.springframework.web.context.request.RequestContextListener</listener-class> </listener> <context-param> <param-name>contextConfigLocation</param-name> <param-value>/WEB-INF/spring-core-config.xml</param-value> </context-param> </web-app>
以上是关于微信开发-回调模式的主要内容,如果未能解决你的问题,请参考以下文章