微信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
        + "×tamp=" + 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 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 test(HttpServletRequest requesturl) throws Exception {
        String ticket = weiXinRequest.getWeiXinTicket();

        // 注意 URL 一定要动态获取,不能 hardcode
        String url = requesturl.getRequestURL().toString();
        Map 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 sign(String jsapi_ticket, String url) {
        Map ret = new HashMap();
        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;

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

你可能感兴趣的:(web前端)