微信JSSDK之簽名生成

最近做一個小遊戲,需要使用微信分享,經查詢,無法直接在網頁中直接添加分享按鈕進行添加,需調用微信接口定製微信的分享按鈕,具體步驟詳見微信JSSDK開發文檔,通過查找資料,實踐如下:
1.在微信公衆平臺(需通過認證)中,按照開發文檔步驟,添加js域名,因爲本文的地址端口不是默認80端口,因爲域名還需帶上端口號,不然會提示域名錯誤
2.頁面引入微信js文件:

<script type="text/javascript" src="http://res.wx.qq.com/open/js/jweixin-1.1.0.js"></script>

3.根據需要,添加自己的js功能,完整頁面JS代碼如下:

<script type="text/javascript">
    var id = '${id}';//服務端設置的id,用於下面拼接生成需要分享的link
    var timestamp = parseInt('${ret.timestamp}');//因爲服務端是String類型,此處轉化成數值類型
    var nonceStr = '${ret.nonceStr}';
    var signature = '${ret.signature}';
    wx.config({
        debug: true, // 開啓調試模式,調用的所有api的返回值會在客戶端alert出來,若要查看傳入的參數,可以在pc端打開,參數信息會通過log打出,僅在pc端時纔會打印。
        appId: 'wxa034b7003154ee6c', // 必填,公衆號的唯一標識
        timestamp: timestamp, // 必填,生成簽名的時間戳
        nonceStr: nonceStr, // 必填,生成簽名的隨機串
        signature: signature,// 必填,簽名,見附錄1
        jsApiList: ['onMenuShareTimeline', 'onMenuShareAppMessage'] // 必填,需要使用的JS接口列表,所有JS接口列表見附錄2
    });

    wx.ready(function(){
        // config信息驗證後會執行ready方法,所有接口調用都必須在config接口獲得結果之後,config是一個客戶端的異步操作,所以如果需要在頁面加載時就調用相關接口,則須把相關接口放在ready函數中調用來確保正確執行。對於用戶觸發時才調用的接口,則可以直接調用,不需要放在ready函數中。
        wx.onMenuShareTimeline({
            title: 'xxxxxxxxxxxxx', // 分享標題
            link: 'http://www.xxxxx.com:8082/xxx/xxx.do?id='+id, // 分享鏈接
            imgUrl: 'http://www.xxxxx.com:8080/xxx/xxx.jpg', // 分享圖標
            success: function () { 
                // 用戶確認分享後執行的回調函數
            },
            cancel: function () { 
                // 用戶取消分享後執行的回調函數
            }
        });

        wx.onMenuShareAppMessage({
            title: 'xxxxxxx', // 分享標題
            desc: 'xxxxxxx', // 分享描述
            link: 'http://www.xxxxx.com:8082/xxx/xxx.do?id='+id, // 分享鏈接
            imgUrl: 'http://www.xxxxx.com:8080/xxx/xxx.jpg', // 分享圖標
            type: '', // 分享類型,music、video或link,不填默認爲link
            dataUrl: '', // 如果type是music或video,則要提供數據鏈接,默認爲空
            success: function () { 
                // 用戶確認分享後執行的回調函數
            },
            cancel: function () { 
                // 用戶取消分享後執行的回調函數
            }
        });
    });

</script>

頁面通過config進行初始化,config中需要提供timestamp、nonceStr、signature以及jsApiList,其中timestamp、nonceStr以及jsApiList都很容易添加,關鍵是signature簽名,經實踐,生成簽名步驟如下:
1. 獲取access_token, 因爲一般有效期是7200s,所以添加到了ehcache中,默認先從緩存中獲取,失效後再發送GET請求獲取:

Object act = EhcacheUtil.getInstance().get("weixin_jsapi", "access_token");
Object apiticket = EhcacheUtil.getInstance().get("weixin_jsapi", "ticket");
logger.debug("[act] = " + act + " [apiticket] = " + apiticket);
if (null == act) {

    String url = "https://api.weixin.qq.com/cgi-bin/token";
    String jsonStrToken = Tools.sendGet(url, "grant_type=client_credential&appid="+ appId + "&secret=" + appSecret);

    logger.debug("[jsonStrToken] = " + jsonStrToken);

    JSONObject json = JSONObject.fromObject(jsonStrToken);

    access_token = (String) json.getString("access_token");
    if (access_token == null) {
        return null;
    }
    EhcacheUtil.getInstance().put("weixin_jsapi", "access_token", access_token);
} else {
    access_token = (String) act;
}

2.根據上一步中的access_token獲取jsapi_ticket:

if (null == apiticket) {
    String url = "https://api.weixin.qq.com/cgi-bin/ticket/getticket";
    String jsonStrTicket = Tools.sendGet(url, "access_token=" + access_token + "&type=jsapi");

    logger.debug("[jsonStrTicket] = " + jsonStrTicket);

    JSONObject json = JSONObject.fromObject(jsonStrTicket);
    ticket = (String) json.get("ticket");

} else {
    ticket = (String) apiticket;
}

3.獲取當前需要分享的網頁的URL:

//獲取URL
String url = request.getRequestURL().toString();

4.最終生成簽名:
(1)獲取時間戳:

private static String create_timestamp() {
    return Long.toString(System.currentTimeMillis() / 1000);
}

(2)獲取隨機字符串:

private static String create_nonce_str() {
    return UUID.randomUUID().toString();
}

(3)將jsapi_ticket、noncestr、timestamp、url拼接,使用SHA1加密算法,最終生成簽名

// 注意這裏參數名必須全部小寫,且必須有序
string1 = "jsapi_ticket=" + jsapi_ticket + "&noncestr=" + nonce_str
        + "&timestamp=" + timestamp + "&url=" + url;

logger.debug("[string1] = " + string1);

try {
    MessageDigest crypt = MessageDigest.getInstance("SHA-1");
    crypt.reset();
    crypt.update(string1.getBytes("UTF-8"));
    signature = byteToHex(crypt.digest());

    logger.debug("[signature] = " + signature);

} catch (NoSuchAlgorithmException e) {
    e.printStackTrace();
} catch (UnsupportedEncodingException e) {
    e.printStackTrace();
}

5.將服務端生成的noncestr、timestamp、signature設置到model(使用的是spring mvc)中,返回到頁面:

Map<String, String> ret = sign.sign(jsapi_ticket, url);
mav.addObject("ret", ret);

var timestamp = parseInt('${ret.timestamp}');
var nonceStr = '${ret.nonceStr}';
var signature = '${ret.signature}';
wx.config({
    debug: true, // 開啓調試模式,調用的所有api的返回值會在客戶端alert出來,若要查看傳入的參數,可以在pc端打開,參數信息會通過log打出,僅在pc端時纔會打印。
    appId: 'wxa034b7003154ee6c', // 必填,公衆號的唯一標識
    timestamp: timestamp, // 必填,生成簽名的時間戳
    nonceStr: nonceStr, // 必填,生成簽名的隨機串
    signature: signature,// 必填,簽名,見附錄1
    jsApiList: ['onMenuShareTimeline', 'onMenuShareAppMessage'] // 必填,需要使用的JS接口列表,所有JS接口列表見附錄2
});

完整服務端代碼如下:
1.請求action:

//獲取ticket
String jsapi_ticket = weiXinRequest.getWeiXinTicket();

logger.debug("[jsapi_ticket] = " + jsapi_ticket);

//獲取URL
String url = request.getRequestURL().toString();

Map<String, String> ret = sign.sign(jsapi_ticket, url);
mav.addObject("ret", ret);
logger.debug("[ret] = " + ret);
return mav;

2.weixinrequest類:

import net.sf.json.JSONObject;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.stereotype.Component;

@Component("WeiXinRequest")
public class WeiXinRequest {

    private String appId = "wxa034b7003154ee6c";
    private String appSecret = "7fce4f1ee0f63b62f20fe8321a31dea8";

    private Log logger = LogFactory.getLog(WeiXinRequest.class);

    public String getWeiXinTicket() throws Exception {
        String access_token = "";
        String ticket = "";
        Object act = EhcacheUtil.getInstance().get("weixin_jsapi", "access_token");
        Object apiticket = EhcacheUtil.getInstance().get("weixin_jsapi", "ticket");
        logger.debug("[act] = " + act + " [apiticket] = " + apiticket);
        if (null == act) {

            String url = "https://api.weixin.qq.com/cgi-bin/token";
            String jsonStrToken = Tools.sendGet(url, "grant_type=client_credential&appid="+ appId + "&secret=" + appSecret);

            logger.debug("[jsonStrToken] = " + jsonStrToken);

            JSONObject json = JSONObject.fromObject(jsonStrToken);

            access_token = (String) json.getString("access_token");
            if (access_token == null) {
                return null;
            }
            EhcacheUtil.getInstance().put("weixin_jsapi", "access_token", access_token);
        } else {
            access_token = (String) act;
        }

        if (null == apiticket) {
            String url = "https://api.weixin.qq.com/cgi-bin/ticket/getticket";
            String jsonStrTicket = Tools.sendGet(url, "access_token=" + access_token + "&type=jsapi");

            logger.debug("[jsonStrTicket] = " + jsonStrTicket);

            JSONObject json = JSONObject.fromObject(jsonStrTicket);
            ticket = (String) json.get("ticket");

        } else {
            ticket = (String) apiticket;
        }

        return ticket;
        // 斷開連接

    }
}

3.Sign簽名類:

import java.io.UnsupportedEncodingException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.Arrays;
import java.util.Formatter;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
import javax.servlet.http.HttpServletRequest;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

@Component("Sign")
public class Sign {

    @Autowired
    private WeiXinRequest weiXinRequest;

    private Log logger = LogFactory.getLog(Sign.class);

    public Map<String, String> test(HttpServletRequest requesturl) throws Exception {
        String ticket = weiXinRequest.getWeiXinTicket();

        // 注意 URL 一定要動態獲取,不能 hardcode
        String url = requesturl.getRequestURL().toString();
        Map<String, String> ret = sign(ticket, url);
        for (Map.Entry entry : ret.entrySet()) {
            System.out.println(entry.getKey() + ", " + entry.getValue());
        }
//      ret.put("appId", weiXinRequest.appId);
        return ret;
    };

    public 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
                + "&timestamp=" + timestamp + "&url=" + url;

        logger.debug("[string1] = " + string1);

        try {
            MessageDigest crypt = MessageDigest.getInstance("SHA-1");
            crypt.reset();
            crypt.update(string1.getBytes("UTF-8"));
            signature = byteToHex(crypt.digest());

            logger.debug("[signature] = " + signature);

        } 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);

        logger.debug("[ret] = " + ret);

        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);
    }

    /**
     * 用SHA1算法生成安全簽名
     * 
     * @param token
     *            票據
     * @param timestamp
     *            時間戳
     * @param nonce
     *            隨機字符串
     * @param encrypt
     *            密文
     * @return 安全簽名
     * @throws NoSuchAlgorithmException
     * @throws AesException
     */
    public String getSHA1(String token, String timestamp, String nonce)
            throws NoSuchAlgorithmException {
        String[] array = new String[] { token, timestamp, nonce };
        StringBuffer sb = new StringBuffer();
        // 字符串排序
        Arrays.sort(array);
        for (int i = 0; i < 3; i++) {
            sb.append(array[i]);
        }
        String str = sb.toString();
        // SHA1簽名生成
        MessageDigest md = MessageDigest.getInstance("SHA-1");
        md.update(str.getBytes());
        byte[] digest = md.digest();

        StringBuffer hexstr = new StringBuffer();
        String shaHex = "";
        for (int i = 0; i < digest.length; i++) {
            shaHex = Integer.toHexString(digest[i] & 0xFF);
            if (shaHex.length() < 2) {
                hexstr.append(0);
            }
            hexstr.append(shaHex);
        }
        return hexstr.toString();
    }
}

4.java發送http請求的方法:

/**
     * 向指定URL發送GET方法的請求
     * 
     * @param url
     *            發送請求的URL
     * @param param
     *            請求參數,請求參數應該是 name1=value1&name2=value2 的形式。
     * @return URL 所代表遠程資源的響應結果
     */
    public static String sendGet(String url, String param) {
        String result = "";
        BufferedReader in = null;
        try {
            String urlNameString = url + "?" + param;
            URL realUrl = new URL(urlNameString);
            // 打開和URL之間的連接
            URLConnection connection = realUrl.openConnection();
            // 設置通用的請求屬性
            connection.setRequestProperty("accept", "*/*");
            connection.setRequestProperty("connection", "Keep-Alive");
            // 建立實際的連接
            connection.connect();
            // 獲取所有響應頭字段
            Map<String, List<String>> map = connection.getHeaderFields();
            // 遍歷所有的響應頭字段
            for (String key : map.keySet()) {
                System.out.println(key + "--->" + map.get(key));
            }
            // 定義 BufferedReader輸入流來讀取URL的響應
            in = new BufferedReader(new InputStreamReader(
                    connection.getInputStream()));
            String line;
            while ((line = in.readLine()) != null) {
                result += line;
            }

        } catch (Exception e) {
            System.out.println("發送GET請求出現異常!" + e);
            e.printStackTrace();
        }
        // 使用finally塊來關閉輸入流
        finally {
            try {
                if (in != null) {
                    in.close();
                }
            } catch (Exception e2) {
                e2.printStackTrace();
            }
        }
        return result;
    }

    /**
     * 向指定 URL 發送POST方法的請求
     * 
     * @param url
     *            發送請求的 URL
     * @param param
     *            請求參數,請求參數應該是 name1=value1&name2=value2 的形式。
     * @return 所代表遠程資源的響應結果
     */
    public static String sendPost(String url, String param) {
        PrintWriter out = null;
        BufferedReader in = null;
        String result = "";
        try {
            URL realUrl = new URL(url);
            // 打開和URL之間的連接
            URLConnection conn = realUrl.openConnection();
            // 設置通用的請求屬性
            conn.setRequestProperty("accept", "*/*");
            conn.setRequestProperty("connection", "Keep-Alive");
            conn.setRequestProperty("user-agent",
                    "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1;SV1)");
            // 發送POST請求必須設置如下兩行
            conn.setDoOutput(true);
            conn.setDoInput(true);
            // 獲取URLConnection對象對應的輸出流
            out = new PrintWriter(conn.getOutputStream());
            // 發送請求參數
            out.print(param);
            // flush輸出流的緩衝
            out.flush();
            // 定義BufferedReader輸入流來讀取URL的響應
            in = new BufferedReader(
                    new InputStreamReader(conn.getInputStream()));
            String line;
            while ((line = in.readLine()) != null) {
                result += line;
            }
        } catch (Exception e) {
            System.out.println("發送 POST 請求出現異常!"+e);
            e.printStackTrace();
        }
        //使用finally塊來關閉輸出流、輸入流
        finally{
            try{
                if(out!=null){
                    out.close();
                }
                if(in!=null){
                    in.close();
                }
            }
            catch(IOException ex){
                ex.printStackTrace();
            }
        }
        return result;
    }
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章