纵有疾风起
人生不言弃

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接口,代码如下:

package org.common.weixin.util;  
  
import java.security.cert.CertificateException;  
import java.security.cert.X509Certificate;  
  
import javax.net.ssl.X509TrustManager;  
  
/** 
 * 证书信任管理器(用于https请求) 
 */  
public class MyX509TrustManager implements X509TrustManager {  
  
    public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException {  
    }  
  
    public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException {  
    }  
  
    public X509Certificate[] getAcceptedIssuers() {  
        return null;  
    }  
} 

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

2. 通用的 https 请求方法:

package org.common.weixin.util;  
import java.io.BufferedReader;  
import java.io.InputStream;  
import java.io.InputStreamReader;  
import java.io.OutputStream;  
import java.net.ConnectException;  
import java.net.URL;  
  
import javax.net.ssl.HttpsURLConnection;  
import javax.net.ssl.SSLContext;  
import javax.net.ssl.SSLSocketFactory;  
import javax.net.ssl.TrustManager;  
  
import net.sf.json.JSONObject;  
  
import org.slf4j.Logger;  
import org.slf4j.LoggerFactory;  
  
/** 
 * 公众平台通用接口工具类 
 * 
 */  
public class WeixinUtil {  
    private static Logger log = LoggerFactory.getLogger(WeixinUtil.class);  
  
    /** 
     * 发起https请求并获取结果 
     *  
     * @param requestUrl 请求地址 
     * @param requestMethod 请求方式(GET、POST) 
     * @param outputStr 提交的数据 
     * @return JSONObject(通过JSONObject.get(key)的方式获取json对象的属性值) 
     */  
    public static JSONObject httpRequest(String requestUrl, String requestMethod, String outputStr) {  
        JSONObject jsonObject = null;  
        StringBuffer buffer = new StringBuffer();  
        try {  
            // 创建SSLContext对象,并使用我们指定的信任管理器初始化  
            TrustManager[] tm = { new MyX509TrustManager() };  
            SSLContext sslContext = SSLContext.getInstance("SSL", "SunJSSE");  
            sslContext.init(null, tm, new java.security.SecureRandom());  
            // 从上述SSLContext对象中得到SSLSocketFactory对象  
            SSLSocketFactory ssf = sslContext.getSocketFactory();  
  
            URL url = new URL(requestUrl);  
            HttpsURLConnection httpUrlConn = (HttpsURLConnection) url.openConnection();  
            httpUrlConn.setSSLSocketFactory(ssf);  
  
            httpUrlConn.setDoOutput(true);  
            httpUrlConn.setDoInput(true);  
            httpUrlConn.setUseCaches(false);  
            // 设置请求方式(GET/POST)  
            httpUrlConn.setRequestMethod(requestMethod);  
  
            if ("GET".equalsIgnoreCase(requestMethod))  
                httpUrlConn.connect();  
  
            // 当有数据需要提交时  
            if (null != outputStr) {  
                OutputStream outputStream = httpUrlConn.getOutputStream();  
                // 注意编码格式,防止中文乱码  
                outputStream.write(outputStr.getBytes("UTF-8"));  
                outputStream.close();  
            }  
  
            // 将返回的输入流转换成字符串  
            InputStream inputStream = httpUrlConn.getInputStream();  
            InputStreamReader inputStreamReader = new InputStreamReader(inputStream, "utf-8");  
            BufferedReader bufferedReader = new BufferedReader(inputStreamReader);  
  
            String str = null;  
            while ((str = bufferedReader.readLine()) != null) {  
                buffer.append(str);  
            }  
            bufferedReader.close();  
            inputStreamReader.close();  
            // 释放资源  
            inputStream.close();  
            inputStream = null;  
            httpUrlConn.disconnect();  
            jsonObject = JSONObject.fromObject(buffer.toString());  
        } catch (ConnectException ce) {  
            log.error("Weixin server connection timed out.");  
        } catch (Exception e) {  
            log.error("https request error:{}", e);  
        }  
        return jsonObject;  
    }  
}    

3. Pojo类 – AccessToken


调用获取凭证接口后,微信服务器会返回json格式的数据:{“access_token”:”ACCESS_TOKEN”,”expires_in”:7200},我们将其封装为一个AccessToken对象,对象有二个属性:token和expiresIn,代码如下:

public class AccessToken {  
    // 获取到的凭证  
    private String token;  
    // 凭证有效时间,单位:秒  
    private int expiresIn;  
  
    public String getToken() {  
        return token;  
    }  
  
    public void setToken(String token) {  
        this.token = token;  
    }  
  
    public int getExpiresIn() {  
        return expiresIn;  
    }  
  
    public void setExpiresIn(int expiresIn) {  
        this.expiresIn = expiresIn;  
    }  
} </span><span style="font-size: 14px;"> </span>

4. 获取 access_token 访问凭证

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

// 获取access_token的接口地址(GET) 限200(次/天)  
public final static String access_token_url = "https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=APPID&secret=APPSECRET";  
  
/** 
 * 获取access_token 
 *  
 * @param appid 凭证 
 * @param appsecret 密钥 
 * @return 
 */  
public static AccessToken getAccessToken(String appid, String appsecret) {  
    AccessToken accessToken = null;  
  
    String requestUrl = access_token_url.replace("APPID", appid).replace("APPSECRET", appsecret);  
    JSONObject jsonObject = httpRequest(requestUrl, "GET", null);  //调用通用的https请求方法
    // 如果请求成功  
    if (null != jsonObject) {  
        try {  
            accessToken = new AccessToken();  
            accessToken.setToken(jsonObject.getString("access_token"));  
            accessToken.setExpiresIn(jsonObject.getInt("expires_in"));  
        } catch (JSONException e) {  
            accessToken = null;  
            // 获取token失败  
            log.error("获取token失败 errcode:{} errmsg:{}", jsonObject.getInt("errcode"), jsonObject.getString("errmsg"));  
        }  
    }  
    return accessToken;  
} 

5. pojo 类 JsApiTicket 临时票据

public class JsApiTicket { 
    
    private String ticket;  //票据
    private int expiresIn;  <span style="font-family: Arial, Helvetica, sans-serif;">// 凭证有效时间,单位:秒</span>

    public String getTicket() {  
        return ticket;  
    }  
    public void setTicket(String ticket) {  
        this.ticket = ticket;  
    }  
    public int getExpiresIn() {  
        return expiresIn;  
    }  
    public void setExpiresIn(int expiresIn) {  
        this.expiresIn = expiresIn;  
    }   
}  

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

public final static String ticket_url = "https://api.weixin.qq.com/cgi-bin/ticket/getticket?access_token=ACCESS_TOKEN&type=jsapi"; 

public static JsApiTicket getJsApiTicket(String accessToken) { 
        JsApiTicket jsApiTicket = null;  
        //获取token 
        String acess_token= WeixinUtil.getAccessToken();           
        String requestUrl = ticket_url.replace("ACCESS_TOKEN", acess_token);
        JSONObject jsonObject = WeixinUtil.httpRequest(requestUrl, "GET", null);  
        // 如果请求成功  
        if (null != jsonObject) {  
            try {  
                jsApiTicket = new JsApiTicket();  
                jsApiTicket.setTicket(jsonObject.getString("ticket"));  
                jsApiTicket.setExpiresIn(jsonObject.getInt("expires_in"));  
            } catch (JSONException e) {  
                accessToken = null;  
                // 获取jsApiTicket失败  
                log.error("获取jsApiTicket失败 errcode:{} errmsg:{}", jsonObject.getInt("errcode"), jsonObject.getString("errmsg"));  
            }  
        }  
        return jsApiTicket;  
    }

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中。

//创建一个静态map缓存
private static Map<String,Ticket> weixinCache = new HashMap<String, Ticket>();
private static JsApiTicket getJsApiTicket(String accessToken){
		String ticket_str = null;
                JsApiTicket jsApiTicket = weixinCache.get("ticket");
		if(jsApiTicket != null){
			Date expiresIn = jsApiTicket.getExpiresIn();
			Calendar ever = Calendar.getInstance();
			ever.setTime(expiresIn);
			Calendar current = Calendar.getInstance();
			if(ever.after(current)){
				ticket_str = jsApiTicket.getTicket();
			}else{
				String request_url = "https://api.weixin.qq.com/cgi-bin/ticket/getticket?access_token=ACCESS_TOKEN&type=jsapi".replace("ACCESS_TOKEN", accessToken);
				JSONObject jsonObject = WeixinUtils.httpRequest(request_url, "GET", null);
				ticket_str = jsonObect.getString("ticket");
				weixinCache.remove("ticket");
				JsApiTicket ticket2 = new Ticket();
				ticket2.setExpirseIn(jsonObect.getString("expirseIn"));
				ticket2.setTicket(ticket_str);
				weixinCache.put("ticket", ticket2);
			}
		}else{
			//同上面的else里的code
		}
		
		return jsApiTicket;
	}

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

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

Java 签名算法 demo:

import java.util.UUID;
import java.util.Map;
import java.util.HashMap;
import java.util.Formatter;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.io.UnsupportedEncodingException;  

class Sign {
    public static void main(String[] args) {
        String jsapi_ticket = "jsapi_ticket";

        // 注意 URL 一定要动态获取,不能 hardcode
        String url = "http://example.com";
        Map<String, String> ret = sign(jsapi_ticket, url);
        for (Map.Entry entry : ret.entrySet()) {
            System.out.println(entry.getKey() + ", " + entry.getValue());
        }
    };

    public static Map<String, String> sign(String jsapi_ticket, String url) {
        Map<String, String> ret = new HashMap<String, String>();
        String nonce_str = create_nonce_str();
        String timestamp = create_timestamp();
        String string1;
        String signature = "";

        //注意这里参数名必须全部小写,且必须有序
        string1 = "jsapi_ticket=" + jsapi_ticket +
                  "&noncestr=" + nonce_str +
                  "×tamp=" + timestamp +
                  "&url=" + url;
        System.out.println(string1);

        try
        {
            MessageDigest crypt = MessageDigest.getInstance("SHA-1");
            crypt.reset();
            crypt.update(string1.getBytes("UTF-8")); //对string1 字符串进行SHA-1加密处理
            signature = byteToHex(crypt.digest());  //对加密后字符串转成16进制
        }
        catch (NoSuchAlgorithmException e)
        {
            e.printStackTrace();
        }
        catch (UnsupportedEncodingException e)
        {
            e.printStackTrace();
        }

        ret.put("url", url);
        ret.put("jsapi_ticket", jsapi_ticket);
        ret.put("nonceStr", nonce_str);
        ret.put("timestamp", timestamp);
        ret.put("signature", signature);

        return ret;
    }

    private static String byteToHex(final byte[] hash) {
        Formatter formatter = new Formatter();
        for (byte b : hash)
        {
            formatter.format("%02x", b);
        }
        String result = formatter.toString();
        formatter.close();
        return result;
    }
    //生成随机字符串
    private static String create_nonce_str() {
        return UUID.randomUUID().toString();
    }
    //生成时间戳字符串
    private static String create_timestamp() {
        return Long.toString(System.currentTimeMillis() / 1000);
    }
}

原文链接:https://blog.csdn.net/xuanjiewu/article/details/52945055

本站声明:网站内容来源于网络,如有侵权,请联系我们,我们将及时处理。

未经允许不得转载:起风网 » JS-SDK使用权限签名算法
分享到: 生成海报

评论 抢沙发

评论前必须登录!

立即登录