JS-SDK使用权限签名算法

jsapi_ticket

生成签名之前必须先了解一下jsapi_ticket,jsapi_ticket是公众号用于调用微信JS接口的临时票据。正常情况下,jsapi_ticket的有效期为7200秒(2小时),通过access_token来获取。由于获取jsapi_ticket的api调用次数非常有限,频繁刷新jsapi_ticket会导致api调用受限,影响自身业务,开发者必须在自己的服务全局缓存jsapi_ticket 。

  1. 参考以下文档获取access_token(有效期7200秒,开发者必须在自己的服务全局缓存access_token): http://mp.weixin.qq.com/wiki/15/54ce45d8d30b6bf6758f68d2e95bc627.html
  2. 用第一步拿到的access_token 采用http GET方式请求获得jsapi_ticket(有效期7200秒,开发者必须在自己的服务全局缓存jsapi_ticket):https://api.weixin.qq.com/cgi-bin/ticket/getticket?access_token=ACCESS_TOKEN&type=jsapi
Token也就是用户身份凭证的意思。access_token 是公众号的全局唯一票据,公众号调用各接口时都需使用access_token。 开发者需要进行妥善保存。access_token的存储至少要保留512个字符空间。access_token的有效期目前为2个小时,需定时刷新,重复获取将导致上次获取的access_token失效。  

公众号可以使用AppID和AppSecret调用本接口来获取access_token。AppID和AppSecret可在微信公众平台官网-开发者中心页中获得(需要已经成为开发者,且帐号没有异常状态)。注意调用所有微信接口时均需使用https协议。
接口调用请求说明:
http请求方式: GET
https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=APPID&secret=APPSECRET

1. 证书信任管理器

对于https请求,我们需要一个证书信任管理器,这个管理器类需要自己定义,但需要实现X509TrustManager接口,代码如下:

[java]  view plain  copy
  1. package org.common.weixin.util;    
  2.     
  3. import java.security.cert.CertificateException;    
  4. import java.security.cert.X509Certificate;    
  5.     
  6. import javax.net.ssl.X509TrustManager;    
  7.     
  8. /**  
  9.  * 证书信任管理器(用于https请求)  
  10.  */    
  11. public class MyX509TrustManager implements X509TrustManager {    
  12.     
  13.     public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException {    
  14.     }    
  15.     
  16.     public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException {    
  17.     }    
  18.     
  19.     public X509Certificate[] getAcceptedIssuers() {    
  20.         return null;    
  21.     }    
  22. }   

这个证书管理器的作用就是让它信任我们指定的证书,上面的代码意味着信任所有证书,不管是否权威机构颁发。

2. 通用的 https 请求方法:

[java]  view plain  copy
  1. package org.common.weixin.util;    
  2. import java.io.BufferedReader;    
  3. import java.io.InputStream;    
  4. import java.io.InputStreamReader;    
  5. import java.io.OutputStream;    
  6. import java.net.ConnectException;    
  7. import java.net.URL;    
  8.     
  9. import javax.net.ssl.HttpsURLConnection;    
  10. import javax.net.ssl.SSLContext;    
  11. import javax.net.ssl.SSLSocketFactory;    
  12. import javax.net.ssl.TrustManager;    
  13.     
  14. import net.sf.json.JSONObject;    
  15.     
  16. import org.slf4j.Logger;    
  17. import org.slf4j.LoggerFactory;    
  18.     
  19. /**  
  20.  * 公众平台通用接口工具类  
  21.  *  
  22.  */    
  23. public class WeixinUtil {    
  24.     private static Logger log = LoggerFactory.getLogger(WeixinUtil.class);    
  25.     
  26.     /**  
  27.      * 发起https请求并获取结果  
  28.      *   
  29.      * @param requestUrl 请求地址  
  30.      * @param requestMethod 请求方式(GET、POST)  
  31.      * @param outputStr 提交的数据  
  32.      * @return JSONObject(通过JSONObject.get(key)的方式获取json对象的属性值)  
  33.      */    
  34.     public static JSONObject httpRequest(String requestUrl, String requestMethod, String outputStr) {    
  35.         JSONObject jsonObject = null;    
  36.         StringBuffer buffer = new StringBuffer();    
  37.         try {    
  38.             // 创建SSLContext对象,并使用我们指定的信任管理器初始化    
  39.             TrustManager[] tm = { new MyX509TrustManager() };    
  40.             SSLContext sslContext = SSLContext.getInstance("SSL""SunJSSE");    
  41.             sslContext.init(null, tm, new java.security.SecureRandom());    
  42.             // 从上述SSLContext对象中得到SSLSocketFactory对象    
  43.             SSLSocketFactory ssf = sslContext.getSocketFactory();    
  44.     
  45.             URL url = new URL(requestUrl);    
  46.             HttpsURLConnection httpUrlConn = (HttpsURLConnection) url.openConnection();    
  47.             httpUrlConn.setSSLSocketFactory(ssf);    
  48.     
  49.             httpUrlConn.setDoOutput(true);    
  50.             httpUrlConn.setDoInput(true);    
  51.             httpUrlConn.setUseCaches(false);    
  52.             // 设置请求方式(GET/POST)    
  53.             httpUrlConn.setRequestMethod(requestMethod);    
  54.     
  55.             if ("GET".equalsIgnoreCase(requestMethod))    
  56.                 httpUrlConn.connect();    
  57.     
  58.             // 当有数据需要提交时    
  59.             if (null != outputStr) {    
  60.                 OutputStream outputStream = httpUrlConn.getOutputStream();    
  61.                 // 注意编码格式,防止中文乱码    
  62.                 outputStream.write(outputStr.getBytes("UTF-8"));    
  63.                 outputStream.close();    
  64.             }    
  65.     
  66.             // 将返回的输入流转换成字符串    
  67.             InputStream inputStream = httpUrlConn.getInputStream();    
  68.             InputStreamReader inputStreamReader = new InputStreamReader(inputStream, "utf-8");    
  69.             BufferedReader bufferedReader = new BufferedReader(inputStreamReader);    
  70.     
  71.             String str = null;    
  72.             while ((str = bufferedReader.readLine()) != null) {    
  73.                 buffer.append(str);    
  74.             }    
  75.             bufferedReader.close();    
  76.             inputStreamReader.close();    
  77.             // 释放资源    
  78.             inputStream.close();    
  79.             inputStream = null;    
  80.             httpUrlConn.disconnect();    
  81.             jsonObject = JSONObject.fromObject(buffer.toString());    
  82.         } catch (ConnectException ce) {    
  83.             log.error("Weixin server connection timed out.");    
  84.         } catch (Exception e) {    
  85.             log.error("https request error:{}", e);    
  86.         }    
  87.         return jsonObject;    
  88.     }    
  89. }      
3. Pojo类 - AccessToken
调用获取凭证接口后,微信服务器会返回json格式的数据:{"access_token":"ACCESS_TOKEN","expires_in":7200},我们将其封装为一个AccessToken对象,对象有二个属性:token和expiresIn,代码如下:

[java]  view plain  copy
  1. public class AccessToken {    
  2.     // 获取到的凭证    
  3.     private String token;    
  4.     // 凭证有效时间,单位:秒    
  5.     private int expiresIn;    
  6.     
  7.     public String getToken() {    
  8.         return token;    
  9.     }    
  10.     
  11.     public void setToken(String token) {    
  12.         this.token = token;    
  13.     }    
  14.     
  15.     public int getExpiresIn() {    
  16.         return expiresIn;    
  17.     }    
  18.     
  19.     public void setExpiresIn(int expiresIn) {    
  20.         this.expiresIn = expiresIn;    
  21.     }    
  22. } </span><span style="font-size: 14px;"> </span>  

4. 获取 access_token 访问凭证

在前面的第2步中的通用Util 类 WeixinUtil 里 添加获取 token 的方法:

[java]  view plain  copy
  1. // 获取access_token的接口地址(GET) 限200(次/天)    
  2. public final static String access_token_url = "https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=APPID&secret=APPSECRET";    
  3.     
  4. /**  
  5.  * 获取access_token  
  6.  *   
  7.  * @param appid 凭证  
  8.  * @param appsecret 密钥  
  9.  * @return  
  10.  */    
  11. public static AccessToken getAccessToken(String appid, String appsecret) {    
  12.     AccessToken accessToken = null;    
  13.     
  14.     String requestUrl = access_token_url.replace("APPID", appid).replace("APPSECRET", appsecret);    
  15.     JSONObject jsonObject = httpRequest(requestUrl, "GET"null);  //调用通用的https请求方法  
  16.     // 如果请求成功    
  17.     if (null != jsonObject) {    
  18.         try {    
  19.             accessToken = new AccessToken();    
  20.             accessToken.setToken(jsonObject.getString("access_token"));    
  21.             accessToken.setExpiresIn(jsonObject.getInt("expires_in"));    
  22.         } catch (JSONException e) {    
  23.             accessToken = null;    
  24.             // 获取token失败    
  25.             log.error("获取token失败 errcode:{} errmsg:{}", jsonObject.getInt("errcode"), jsonObject.getString("errmsg"));    
  26.         }    
  27.     }    
  28.     return accessToken;    
  29. }   

5. pojo 类 JsApiTicket 临时票据

[java]  view plain  copy
  1. public class JsApiTicket {   
  2.       
  3.     private String ticket;  //票据  
  4.     private int expiresIn;  <span style="font-family: Arial, Helvetica, sans-serif;">// 凭证有效时间,单位:秒</span>  
  5.   
  6.     public String getTicket() {    
  7.         return ticket;    
  8.     }    
  9.     public void setTicket(String ticket) {    
  10.         this.ticket = ticket;    
  11.     }    
  12.     public int getExpiresIn() {    
  13.         return expiresIn;    
  14.     }    
  15.     public void setExpiresIn(int expiresIn) {    
  16.         this.expiresIn = expiresIn;    
  17.     }     
  18. }    

6.  根据前面获取到的 access_token 访问凭证来获取 jsapi_ticket 临时票据

[java]  view plain  copy
  1. public final static String ticket_url = "https://api.weixin.qq.com/cgi-bin/ticket/getticket?access_token=ACCESS_TOKEN&type=jsapi";   
  2.   
  3. public static JsApiTicket getJsApiTicket(String accessToken) {   
  4.         JsApiTicket jsApiTicket = null;    
  5.         //获取token   
  6.         String acess_token= WeixinUtil.getAccessToken();             
  7.         String requestUrl = ticket_url.replace("ACCESS_TOKEN", acess_token);  
  8.         JSONObject jsonObject = WeixinUtil.httpRequest(requestUrl, "GET"null);    
  9.         // 如果请求成功    
  10.         if (null != jsonObject) {    
  11.             try {    
  12.                 jsApiTicket = new JsApiTicket();    
  13.                 jsApiTicket.setTicket(jsonObject.getString("ticket"));    
  14.                 jsApiTicket.setExpiresIn(jsonObject.getInt("expires_in"));    
  15.             } catch (JSONException e) {    
  16.                 accessToken = null;    
  17.                 // 获取jsApiTicket失败    
  18.                 log.error("获取jsApiTicket失败 errcode:{} errmsg:{}", jsonObject.getInt("errcode"), jsonObject.getString("errmsg"));    
  19.             }    
  20.         }    
  21.         return jsApiTicket;    
  22.     }  

7. 全局缓存 access_token 和 jsapi_ticket

参考实现:

(1).存到 servletcontext 中
http://blog.csdn.NET/cnm_1314/article/details/51383411
(2).缓存到 map 中
http://www.cnblogs.com/setukirin/p/5718059.html
(3).缓存到 xml 中
http://www.cnblogs.com/superstar/p/5159936.html

除此之外,还可以放到数据库中,只不过放到数据库中就是每次还的建立数据库连接再读取,略有麻烦;也还可以放到 Memcache 中(SAE为开发者提供的分布式缓存服务,用来以共享的方式缓存用户的少量数据,用户可以使用标准的memcache函数读写Memcache),但 memcache需要先安装才能使用。所有个人认为最简单的就是缓存在静态变量map中。

[java]  view plain  copy
  1. //创建一个静态map缓存  
  2. private static Map<String,Ticket> weixinCache = new HashMap<String, Ticket>();  
  3. private static JsApiTicket getJsApiTicket(String accessToken){  
  4.         String ticket_str = null;  
  5.                 JsApiTicket jsApiTicket = weixinCache.get("ticket");  
  6.         if(jsApiTicket != null){  
  7.             Date expiresIn = jsApiTicket.getExpiresIn();  
  8.             Calendar ever = Calendar.getInstance();  
  9.             ever.setTime(expiresIn);  
  10.             Calendar current = Calendar.getInstance();  
  11.             if(ever.after(current)){  
  12.                 ticket_str = jsApiTicket.getTicket();  
  13.             }else{  
  14.                 String request_url = "https://api.weixin.qq.com/cgi-bin/ticket/getticket?access_token=ACCESS_TOKEN&type=jsapi".replace("ACCESS_TOKEN", accessToken);  
  15.                 JSONObject jsonObject = WeixinUtils.httpRequest(request_url, "GET"null);  
  16.                 ticket_str = jsonObect.getString("ticket");  
  17.                 weixinCache.remove("ticket");  
  18.                 JsApiTicket ticket2 = new Ticket();  
  19.                 ticket2.setExpirseIn(jsonObect.getString("expirseIn"));  
  20.                 ticket2.setTicket(ticket_str);  
  21.                 weixinCache.put("ticket", ticket2);  
  22.             }  
  23.         }else{  
  24.             //同上面的else里的code  
  25.         }  
  26.           
  27.         return jsApiTicket;  
  28.     }  


尊重他人劳动成果,以前有部分内容参考自:http://blog.csdn.Net/gebitan505/article/details/24559181



以下来自微信JSSDK官方说明文档demo:http://demo.open.weixin.qq.com/jssdk/sample.zip

Java 签名算法 demo:

[java]  view plain  copy
  1. import java.util.UUID;  
  2. import java.util.Map;  
  3. import java.util.HashMap;  
  4. import java.util.Formatter;  
  5. import java.security.MessageDigest;  
  6. import java.security.NoSuchAlgorithmException;  
  7. import java.io.UnsupportedEncodingException;    
  8.   
  9. class Sign {  
  10.     public static void main(String[] args) {  
  11.         String jsapi_ticket = "jsapi_ticket";  
  12.   
  13.         // 注意 URL 一定要动态获取,不能 hardcode  
  14.         String url = "http://example.com";  
  15.         Map<String, String> ret = sign(jsapi_ticket, url);  
  16.         for (Map.Entry entry : ret.entrySet()) {  
  17.             System.out.println(entry.getKey() + ", " + entry.getValue());  
  18.         }  
  19.     };  
  20.   
  21.     public static Map<String, String> sign(String jsapi_ticket, String url) {  
  22.         Map<String, String> ret = new HashMap<String, String>();  
  23.         String nonce_str = create_nonce_str();  
  24.         String timestamp = create_timestamp();  
  25.         String string1;  
  26.         String signature = "";  
  27.   
  28.         //注意这里参数名必须全部小写,且必须有序  
  29.         string1 = "jsapi_ticket=" + jsapi_ticket +  
  30.                   "&noncestr=" + nonce_str +  
  31.                   "×tamp=" + timestamp +  
  32.                   "&url=" + url;  
  33.         System.out.println(string1);  
  34.   
  35.         try  
  36.         {  
  37.             MessageDigest crypt = MessageDigest.getInstance("SHA-1");  
  38.             crypt.reset();  
  39.             crypt.update(string1.getBytes("UTF-8")); //对string1 字符串进行SHA-1加密处理  
  40.             signature = byteToHex(crypt.digest());  //对加密后字符串转成16进制  
  41.         }  
  42.         catch (NoSuchAlgorithmException e)  
  43.         {  
  44.             e.printStackTrace();  
  45.         }  
  46.         catch (UnsupportedEncodingException e)  
  47.         {  
  48.             e.printStackTrace();  
  49.         }  
  50.   
  51.         ret.put("url", url);  
  52.         ret.put("jsapi_ticket", jsapi_ticket);  
  53.         ret.put("nonceStr", nonce_str);  
  54.         ret.put("timestamp", timestamp);  
  55.         ret.put("signature", signature);  
  56.   
  57.         return ret;  
  58.     }  
  59.   
  60.     private static String byteToHex(final byte[] hash) {  
  61.         Formatter formatter = new Formatter();  
  62.         for (byte b : hash)  
  63.         {  
  64.             formatter.format("%02x", b);  
  65.         }  
  66.         String result = formatter.toString();  
  67.         formatter.close();  
  68.         return result;  
  69.     }  
  70.     //生成随机字符串  
  71.     private static String create_nonce_str() {  
  72.         return UUID.randomUUID().toString();  
  73.     }  
  74.     //生成时间戳字符串  
  75.     private static String create_timestamp() {  
  76.         return Long.toString(System.currentTimeMillis() / 1000);  
  77.     }  
  78. }  
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值