微信公众号URL和Token配置验证

微信公众号验证URL和Token的配置,以及在项目中获取Token

进入正题首先准备一些事情;

测试的公众号:https://mp.weixin.qq.com/debug/cgi-bin/sandbox?t=sandbox/login

获得appId 和appsecret 为配置微信服务端;

配置接口信息, 稍后讲解;

为了方便微信测试 :http://www.ngrok.cc/ 看下图文字叙述,没什么可说的,

java web环境:下图采用的是 spring4MVC 、jdk1.8、 tomact8,使用的是maven管理工具;

到此准备的差不多了; 下面开始配置微信环境;

还得多说两句,微信开发主要就是通过微信的令牌获得微信的一些特权,为了达到这个效果,需要让微信知道我们的服务器, 我们服务器写一个微信接口供微信调用,同时响应微信发送的token验证,官方文档有介绍,不再赘述,上代码;

   
   
   
   
  1. package com.WeChat.verify;
  2. import java.io.IOException;
  3. import java.io.PrintWriter;
  4. import javax.servlet.ServletException;
  5. import javax.servlet.annotation.WebServlet;
  6. import javax.servlet.http.HttpServlet;
  7. import javax.servlet.http.HttpServletRequest;
  8. import javax.servlet.http.HttpServletResponse;
  9. @WebServlet("/wx")
  10. public class WeixinServlet extends HttpServlet {
  11. @Override
  12. protected void doGet(HttpServletRequest req, HttpServletResponse resp)
  13. throws ServletException, IOException {
  14. String signature = req.getParameter("signature");// 微信加密签名
  15. String timestamp = req.getParameter("timestamp");// 时间戳
  16. String nonce = req.getParameter("nonce");// 随机数
  17. String echostr = req.getParameter("echostr");// 随机字符串
  18. PrintWriter out = resp.getWriter();
  19. if(CheckUtil.checkSignature(signature, timestamp, nonce)){
  20. out.print(echostr);
  21. }
  22. }
  23. /
  24. * 消息的接收与响应
  25. /
  26. @Override
  27. protected void doPost(HttpServletRequest req, HttpServletResponse resp){
  28. }
  29. }

校验签名

   
   
   
   
  1. package com.WeChat.verify;
  2. import java.security.MessageDigest;
  3. import java.security.NoSuchAlgorithmException;
  4. import java.util.Arrays;
  5. public class CheckUtil {
  6. // 与接口配置信息中的Token要一致
  7. private static String token = "Token";
  8. /
  9. 校验签名
  10. /
  11. public static boolean checkSignature(String signature, String timestamp, String nonce) {
  12. System.out.println("signature:" + signature + "timestamp:" + timestamp + "nonc:" + nonce);
  13. String[] arr = new String[] { token, timestamp, nonce };
  14. // 将token、timestamp、nonce三个参数进行字典序排序
  15. Arrays.sort(arr);
  16. StringBuilder content = new StringBuilder();
  17. for (int i = 0; i < arr.length; i++) {
  18. content.append(arr[i]);
  19. }
  20. MessageDigest md = null;
  21. String tmpStr = null;
  22. try {
  23. md = MessageDigest.getInstance("SHA-1");
  24. // 将三个参数字符串拼接成一个字符串进行sha1加密
  25. byte[] digest = md.digest(content.toString().getBytes());
  26. tmpStr = byteToStr(digest);
  27. } catch (NoSuchAlgorithmException e) {
  28. e.printStackTrace();
  29. }
  30. content = null;
  31. // 将sha1加密后的字符串可与signature对比,标识该请求来源于微信
  32. System.out.println(tmpStr.equals(signature.toUpperCase()));
  33. return tmpStr != null ? tmpStr.equals(signature.toUpperCase()) : false;
  34. }
  35. /
  36. 将字节数组转换为十六进制字符串
  37. *
  38. * @param byteArray
  39. * @return
  40. /
  41. private static String byteToStr(byte[] byteArray) {
  42. String strDigest = "";
  43. for (int i = 0; i < byteArray.length; i++) {
  44. strDigest += byteToHexStr(byteArray[i]);
  45. }
  46. return strDigest;
  47. }
  48. /
  49. 将字节转换为十六进制字符串
  50. *
  51. * @param mByte
  52. * @return
  53. /
  54. private static String byteToHexStr(byte mByte) {
  55. char[] Digit = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F' };
  56. char[] tempArr = new char[2];
  57. tempArr[0] = Digit[(mByte >>> 4) & 0X0F];
  58. tempArr[1] = Digit[mByte & 0X0F];
  59. String s = new String(tempArr);
  60. return s;
  61. }
  62. }

至此,微信和我们的服务器,能沟通了偷笑,我们继续,,,

由于我们启动时需要加载微信的accessToken,和JSAPI接口的飞机票,JSApiTicket,下面配置web.xml

   
   
   
   
  1. InitAccessTokenServlet
  2. com.oa.wx.utils.InitAccessTokenServlet
  3. appid
  4. wx4fb90asd5015e9820
  5. appsecret
  6. f7850725c4fff0423349c2a62e2d430
  7. 2

上面配置的目的就是当项目启动时加载 InitAccessTokenServlet 获取我们想要的信息;

为了方便我们建立两个实体类用于获取参数 ,同时准备一些类供加载时调用:

   
   
   
   
  1. package com.oa.wx.utils;
  2. public class AccessToken implements java.io.Serializable {
  3. /
  4. /
  5. private static final long serialVersionUID = 1L;
  6. // 接口访问凭证
  7. private String accessToken;
  8. // 凭证有效期,单位:秒
  9. private int expiresIn;
  10. public AccessToken() {
  11. }
  12. public String getAccessToken() {
  13. return accessToken;
  14. }
  15. public void setAccessToken(String accessToken) {
  16. this.accessToken = accessToken;
  17. }
  18. public int getExpiresIn() {
  19. return expiresIn;
  20. }
  21. public void setExpiresIn(int expiresIn) {
  22. this.expiresIn = expiresIn;
  23. }
  24. }
  25. package com.oa.wx.utils;
  26. public class JsApiTicket implements java.io.Serializable {
  27. /
  28. /
  29. private static final long serialVersionUID = 1L;
  30. private String ticket;
  31. // 凭证有效期,单位:秒
  32. private int expiresIn;
  33. public JsApiTicket() {
  34. }
  35. public String getTicket() {
  36. return ticket;
  37. }
  38. public void setTicket(String ticket) {
  39. this.ticket = ticket;
  40. }
  41. public int getExpiresIn() {
  42. return expiresIn;
  43. }
  44. public void setExpiresIn(int expiresIn) {
  45. this.expiresIn = expiresIn;
  46. }
  47. }

开通了一个https协议,但是不是国际认证的,自己做的证书,访问一个接口的时候比如 https:/xx.xx.xx.xx/a?c=d的时候,如果在游览器访问,需要用户确认的。如果我要模拟服务器向这个接口发送请求,就会报错误。
我的原代码是:

1 创建信任管理器

   
   
   
   
  1. /*
  2. * 信任管理器
  3. *
  4. * @date 2013-04-10
  5. /
  6. public class MyX509TrustManager implements X509TrustManager {
  7. // 检查客户端证书
  8. public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException {
  9. }
  10. // 检查服务器端证书
  11. public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException {
  12. }
  13. // 返回受信任的X509证书数组
  14. public X509Certificate[] getAcceptedIssuers() {
  15. return null;
  16. }
  17. }

编写获取accessToken 和飞机票的接口,在网上找了个例子,编写了个工具类如下、

   
   
   
   
  1. package com.oa.wx.utils;
  2. import java.io.BufferedReader;
  3. import java.io.InputStream;
  4. import java.io.InputStreamReader;
  5. import java.io.OutputStream;
  6. import java.io.UnsupportedEncodingException;
  7. import java.net.URL;
  8. import java.security.MessageDigest;
  9. import java.security.NoSuchAlgorithmException;
  10. import java.util.Arrays;
  11. import java.util.Formatter;
  12. import java.util.HashMap;
  13. import java.util.Map;
  14. import java.util.UUID;
  15. import javax.net.ssl.HttpsURLConnection;
  16. import javax.net.ssl.SSLContext;
  17. import javax.net.ssl.SSLSocketFactory;
  18. import javax.net.ssl.TrustManager;
  19. import com.fasterxml.jackson.databind.JsonNode;
  20. import com.fasterxml.jackson.databind.ObjectMapper;
  21. public class WeiXinUtil {
  22. // 凭证获取(GET)——access_token
  23. public final static String ACCESS_TOKEN_URL = "https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=APPID&secret=APPSECRET";
  24. // 微信JSSDK的ticket请求URL地址——jsapi_ticket
  25. public final static String JSAPI_TICKET_URL = "https://api.weixin.qq.com/cgi-bin/ticket/getticket?access_token=ACCESS_TOKEN&type=jsapi";
  26. //微信APPID
  27. public final static String APP_ID="wx4fb9062502315e9820";
  28. //微信APPSECRET
  29. public final static String APPSECRET="19751e4ebd34c56e763f28b1a8c8263053";
  30. /*
  31. * 排序方法
  32. *
  33. * @param token
  34. * @param timestamp
  35. * @param nonce
  36. * @return
  37. /
  38. public static String sort(String token, String timestamp, String nonce) {
  39. String[] strArray = { token, timestamp, nonce };
  40. Arrays.sort(strArray);
  41. StringBuilder sbuilder = new StringBuilder();
  42. for (String str : strArray) {
  43. sbuilder.append(str);
  44. }
  45. return sbuilder.toString();
  46. }
  47. /*
  48. * 发送https请求
  49. *
  50. * @param requestUrl
  51. * 请求地址
  52. * @param requestMethod
  53. * 请求方式(GET、POST)
  54. * @param outputStr
  55. * 提交的数据
  56. * @return rootNode(通过rootNode.get(key)的方式获取json对象的属性值)
  57. /
  58. public static JsonNode httpsRequest(String requestUrl, String requestMethod, String outputStr) {
  59. ObjectMapper mapper = new ObjectMapper();
  60. JsonNode rootNode = null;
  61. StringBuffer buffer = new StringBuffer();
  62. try {
  63. // 创建SSLContext对象,并使用我们指定的信任管理器初始化
  64. TrustManager[] tm = { new MyX509TrustManager() };
  65. SSLContext sslContext = SSLContext.getInstance("SSL", "SunJSSE");
  66. sslContext.init(null, tm, new java.security.SecureRandom());
  67. // 从上述SSLContext对象中得到SSLSocketFactory对象
  68. SSLSocketFactory ssf = sslContext.getSocketFactory();
  69. URL url = new URL(requestUrl);
  70. HttpsURLConnection conn = (HttpsURLConnection) url.openConnection();
  71. conn.setSSLSocketFactory(ssf);
  72. conn.setDoOutput(true);
  73. conn.setDoInput(true);
  74. conn.setUseCaches(false);
  75. // 设置请求方式(GET/POST)
  76. conn.setRequestMethod(requestMethod);
  77. //conn.setRequestProperty("Content-Type", "application/x-www-form-urlencoded");
  78. if ("GET".equalsIgnoreCase(requestMethod))
  79. conn.connect();
  80. // 当outputStr不为null时向输出流写数据
  81. if (null != outputStr) {
  82. OutputStream outputStream = conn.getOutputStream();
  83. // 注意编码格式
  84. outputStream.write(outputStr.getBytes("UTF-8"));
  85. outputStream.close();
  86. }
  87. // 从输入流读取返回内容
  88. InputStream inputStream = conn.getInputStream();
  89. InputStreamReader inputStreamReader = new InputStreamReader(inputStream, "utf-8");
  90. BufferedReader bufferedReader = new BufferedReader(inputStreamReader);
  91. String str = null;
  92. while ((str = bufferedReader.readLine()) != null) {
  93. buffer.append(str);
  94. }
  95. // 释放资源
  96. bufferedReader.close();
  97. inputStreamReader.close();
  98. inputStream.close();
  99. inputStream = null;
  100. conn.disconnect();
  101. rootNode = mapper.readTree(buffer.toString());
  102. } catch (Exception e) {
  103. e.printStackTrace();
  104. }
  105. return rootNode;
  106. }
  107. /*
  108. * 获取接口访问凭证
  109. *
  110. * @param appid
  111. * 凭证
  112. * @param appsecret
  113. * 密钥
  114. * @return
  115. /
  116. public static AccessToken getAccessToken() {
  117. AccessToken accessToken = null;
  118. String requestUrl = ACCESS_TOKEN_URL.replace("APPID", APP_ID).replace("APPSECRET", APPSECRET);
  119. // 发起GET请求获取凭证
  120. JsonNode rootNode = httpsRequest(requestUrl, "GET", null);
  121. System.out.println("rootNoderootNoderootNode"+rootNode);
  122. if (null != rootNode.get("access_token")) {
  123. accessToken = new AccessToken();
  124. accessToken.setAccessToken(rootNode.get("access_token").textValue());
  125. accessToken.setExpiresIn(toInt(rootNode.get("expires_in").toString()));
  126. }
  127. return accessToken;
  128. }
  129. /*
  130. * 调用微信JS接口的临时票据
  131. *
  132. * @param access_token
  133. * 接口访问凭证
  134. * @return
  135. /
  136. public static JsApiTicket getJsApiTicket(String access_token) {
  137. String requestUrl = JSAPI_TICKET_URL.replace("ACCESS_TOKEN", access_token);
  138. // 发起GET请求获取凭证
  139. JsonNode rootNode = httpsRequest(requestUrl, "GET", null);
  140. System.out.println(rootNode.toString());
  141. JsApiTicket jsApiTicket = null;
  142. if (null != rootNode.get("ticket")) {
  143. jsApiTicket = new JsApiTicket();
  144. jsApiTicket.setTicket(rootNode.get("ticket").textValue());
  145. jsApiTicket.setExpiresIn(toInt(rootNode.get("expires_in").toString()));
  146. }
  147. return jsApiTicket;
  148. }
  149. public static Integer toInt(String str) {
  150. if (str == null || str.equals("")) {
  151. return null;
  152. }
  153. return Integer.valueOf(str);
  154. }
  155. /*
  156. * 获取接口访问凭证
  157. *
  158. * @param appid
  159. * 凭证
  160. * @param appsecret
  161. * 密钥
  162. * @return
  163. /
  164. public static String getAuthorize(String url) {
  165. AccessToken accessToken = null;
  166. String requestUrl = ACCESS_TOKEN_URL.replace("APPID", APP_ID).replace("APPURL", url);
  167. // 发起GET请求获取凭证
  168. JsonNode rootNode = httpsRequest(requestUrl, "GET", null);
  169. System.out.println(rootNode.toString());
  170. return rootNode.toString();
  171. }
  172. public static String create_nonce_str() {
  173. return UUID.randomUUID().toString();
  174. }
  175. public static String create_timestamp() {
  176. return Long.toString(System.currentTimeMillis() / 1000);
  177. }
  178. public static String byteToHex(final byte[] hash) {
  179. Formatter formatter = new Formatter();
  180. for (byte b : hash) {
  181. formatter.format("%02x", b);
  182. }
  183. String result = formatter.toString();
  184. formatter.close();
  185. return result;
  186. }
  187. public static Map<String, String> sign(String jsapi_ticket, String url) {
  188. Map<String, String> ret = new HashMap<String, String>();
  189. String nonce_str = WeiXinUtil.create_nonce_str();
  190. String timestamp = WeiXinUtil.create_timestamp();
  191. String string1;
  192. String signature = "";
  193. // 注意这里参数名必须全部小写,且必须有序
  194. string1 = "jsapi_ticket=" + jsapi_ticket +
  195. "&noncestr=" + nonce_str +
  196. "×tamp=" + timestamp +
  197. "&url=" + url;
  198. System.out.println(string1);
  199. try {
  200. MessageDigest crypt = MessageDigest.getInstance("SHA-1");
  201. crypt.reset();
  202. crypt.update(string1.getBytes("UTF-8"));
  203. signature = WeiXinUtil.byteToHex(crypt.digest());
  204. } catch (NoSuchAlgorithmException e) {
  205. e.printStackTrace();
  206. } catch (UnsupportedEncodingException e) {
  207. e.printStackTrace();
  208. }
  209. ret.put("url", url);
  210. ret.put("appId",WeiXinUtil.APP_ID);
  211. ret.put("jsapi_ticket", jsapi_ticket);
  212. ret.put("nonceStr", nonce_str);
  213. ret.put("timestamp", timestamp);
  214. ret.put("signature", signature);
  215. return ret;
  216. }
  217. }

access_token与jsapi_ticket 需要全局缓存为什么我就不说了,既然来看了就应该知道了

我用的是spring容器加载完毕后监听器,就是容器加载完毕调用 ApplicationListener

这样可以获取access_token jsapi_ticket 缓存到servletcontext中(servletcontext全局容易 全局唯一 全局共享)

首先看一下全局servletcontext;

   
   
   
   
  1. package com.oa.wx.utils;
  2. import javax.servlet.ServletContext;
  3. import org.springframework.web.context.ContextLoader;
  4. import org.springframework.web.context.WebApplicationContext;
  5. /
  6. @ClassName: ServletContextUtil
  7. * @author yangwl
  8. * @date 2016年5月11日 下午4:54:16
  9. * @Description:
  10. * 全局缓存servletcontext
  11. /
  12. public final class ServletContextUtil {
  13. private static ServletContext serveltContext = null;
  14. private ServletContextUtil(){};
  15. public synchronized static ServletContext get() {
  16. if(null == serveltContext) {
  17. WebApplicationContext webApplicationContext = ContextLoader.getCurrentWebApplicationContext();
  18. serveltContext = webApplicationContext.getServletContext();
  19. }
  20. return serveltContext;
  21. }
  22. }

由于微信的一些情况 accessToken 每天的获取数量是有限的,所以需要做缓存处理,在这里写了两个线程,一个是获取 accessToken ,一个是JsApiTicket;

   
   
   
   
  1. package com.oa.wx.utils;
  2. import javax.servlet.ServletContext;
  3. public class AccessTokenThread implements Runnable {
  4. public static String appid = "";
  5. public static String appsecret = "";
  6. public static AccessToken accessToken = null;
  7. @Override
  8. public void run() {
  9. while (true) {
  10. try {
  11. accessToken = WeiXinUtil.getAccessToken();
  12. if (null != accessToken) {
  13. System.out.println("accessToken初始化成功:" + accessToken.getAccessToken());
  14. // 全局缓存access_token
  15. ServletContext servletContext = ServletContextUtil.getServletContext();
  16. servletContext.setAttribute("access_token", accessToken.getAccessToken());
  17. // 有效期(秒)减去200秒,乘以1000(毫秒)——也就是在有效期的200秒前去请求新的accessToken
  18. Thread.sleep((accessToken.getExpiresIn() - 200) 1000);
  19. } else {
  20. // 等待一分钟,再次请求
  21. Thread.sleep(60 1000);
  22. }
  23. } catch (Exception e) {
  24. try {
  25. // 等待一分钟,再次请求
  26. Thread.sleep(60 1000);
  27. } catch (Exception ex) {
  28. ex.printStackTrace();
  29. }
  30. e.printStackTrace();
  31. }
  32. }
  33. }
  34. }
  35. package com.oa.wx.utils;
  36. import javax.servlet.ServletContext;
  37. public class JsApiTicketThread implements Runnable {
  38. @Override
  39. public void run() {
  40. while (true) {
  41. try {
  42. ServletContext servletContext = ServletContextUtil.getServletContext();
  43. String access_token = (String) servletContext.getAttribute("access_token");
  44. JsApiTicket jsApiTicket = null;
  45. if(null != access_token && !"".equals(access_token)){
  46. // 获取jsapi_ticket
  47. jsApiTicket = WeiXinUtil.getJsApiTicket(access_token);
  48. if (null != jsApiTicket) {
  49. System.out.println("jsapi_ticket获取成功:" + jsApiTicket.getTicket());
  50. // 全局缓存jsapi_ticket
  51. servletContext.setAttribute("jsapi_ticket", jsApiTicket.getTicket());
  52. Thread.sleep((jsApiTicket.getExpiresIn() - 200) 1000);
  53. }
  54. }
  55. Thread.sleep(60 1000);
  56. } catch (Exception e) {
  57. try {
  58. Thread.sleep(60 1000);
  59. } catch (Exception ex) {
  60. ex.printStackTrace();
  61. }
  62. e.printStackTrace();
  63. }
  64. }
  65. }
  66. }

最后我们编写 InitAccessTokenServlet ,首次加载时调用;

   
   
   
   
  1. package com.oa.wx.utils;
  2. import javax.servlet.ServletException;
  3. import javax.servlet.http.HttpServlet;
  4. public class InitAccessTokenServlet extends HttpServlet {
  5. /
  6. */
  7. private static final long serialVersionUID = 1L;
  8. public void init() throws ServletException {
  9. // 获取web.xml中配置的参数
  10. String WX_APPID ="wx4fb90625015e9820";
  11. String WX_APPSECRET = "19751e4ebdc56e763f28b1a8c8263053";
  12. AccessTokenThread.appid = WX_APPID;
  13. AccessTokenThread.appsecret = WX_APPSECRET;
  14. if ("".equals(AccessTokenThread.appid) || "".equals(AccessTokenThread.appsecret)) {
  15. System.out.println("appid和appsecret未给出");
  16. } else {
  17. new Thread(new AccessTokenThread()).start();
  18. new Thread(new JsApiTicketThread()).start();
  19. }
  20. }
  21. }

至此,微信服务端配置环境已结束,以上代码均在网上整理,为查阅方便整理到一起,如有侵权行为请与我联系。以上代码仅供参考,如遇问题可以留言!

你可能感兴趣的:(Spring,mybatis,springmvc)