【链接】微信公众平台接口测试帐号申请
http://mp.weixin.qq.com/debug/cgi-bin/sandboxinfo?action=showinfo&t=sandbox/index
首先获取access_token,然后获取jsapi_ticket,最后获取签名
jsapi_ticket
生成签名之前必须先了解一下jsapi_ticket,jsapi_ticket是公众号用于调用微信JS接口的临时票据。正常情况下,jsapi_ticket的有效期为7200秒,通过access_token来获取。由于获取jsapi_ticket的api调用次数非常有限,频繁刷新jsapi_ticket会导致api调用受限,影响自身业务,开发者必须在自己的服务全局缓存jsapi_ticket 。
1.参考以下文档获取access_token(有效期7200秒,开发者必须在自己的服务全局缓存access_token):获取access_token
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
成功返回如下JSON:
{"errcode":0,"errmsg":"ok","ticket":"bxLdikRXVbTPdHSM05e5u5sUoXNKd8-41ZO3MhKoyN5OfkWITDGgnr2fwJ0m9E8NYzWKVZvdVtaUgWvsdshFKA","expires_in":7200}
获得jsapi_ticket之后,就可以生成JS-SDK权限验证的签名了。
签名算法
签名生成规则如下:参与签名的字段包括noncestr(随机字符串), 有效的jsapi_ticket, timestamp(时间戳), url(当前网页的URL,不包含#及其后面部分) 。对所有待签名参数按照字段名的ASCII 码从小到大排序(字典序)后,使用URL键值对的格式(即key1=value1&key2=value2…)拼接成字符串string1。这里需要注意的是所有参数名均为小写字符。对string1作sha1加密,字段名和字段值都采用原始值,不进行URL 转义。
即signature=sha1(string1)。 示例:
noncestr=Wm3WZYTPz0wzccnWjsapi_ticket=sM4AOVdWfPE4DxkXGEs8VMCPGGVi4C3VM0P37wVUCFvkVAy_90u5h9nbSlYy3-Sl-HhTdfl2fzFy1AOcHKP7qgtimestamp=1414587457url=http://mp.weixin.qq.com?params=value
步骤1. 对所有待签名参数按照字段名的ASCII 码从小到大排序(字典序)后,使用URL键值对的格式(即key1=value1&key2=value2…)拼接成字符串string1:
jsapi_ticket=sM4AOVdWfPE4DxkXGEs8VMCPGGVi4C3VM0P37wVUCFvkVAy_90u5h9nbSlYy3-Sl-HhTdfl2fzFy1AOcHKP7qg&noncestr=Wm3WZYTPz0wzccnW×tamp=1414587457&url=http://mp.weixin.qq.com?params=value
步骤2. 对string1进行sha1签名,得到signature:
0f9de62fce790f9a083d5c99e95740ceb90c27ed
注意事项
1.签名用的noncestr和timestamp必须与wx.config中的nonceStr和timestamp相同。
2.签名用的url必须是调用JS接口页面的完整URL。
3.出于安全考虑,开发者必须在服务器端实现签名的逻辑。
如出现invalid signature 等错误详见附录5常见错误及解决办法。
Java代码如下:
//需要准备APP_ID 和SECRET
private static final String GET_TOKEN_URL = "https://api.weixin.qq.com/cgi-bin/token";
private static final String GET_SIGNATURE_URL = "https://api.weixin.qq.com/cgi-bin/ticket/getticket";
private static final String APP_ID = "******";
private static final String SECRET = "*****";
//提供的主方法
@RequestMapping(value = "/getSignature", method = RequestMethod.POST)
@ResponseBody
public Map WeixinController(HttpServletRequest request, HttpServletResponse response) {
response.setHeader("Access-Control-Allow-Origin", "*");
Map ret = new HashMap();
//获取前台传来的三个参数
String timestamp = request.getParameter("timestamp");
String nonce_str = request.getParameter("nonce_str");
String url = request.getParameter("url");
System.out.println("url"+url+"==============="+nonce_str+"============"+timestamp);
String accessToken = null;
// accessToken = redisTemplate.opsForValue().get("accessToken")+"";
accessToken = this.getToken(GET_TOKEN_URL, APP_ID, SECRET);// 获取token
String ticket = this.getTicket(GET_SIGNATURE_URL,accessToken); // 获取ticket
String signature = this.getSignature(ticket,url,nonce_str,timestamp); //获取签名
ret.put("nonceStr", nonce_str);
ret.put("timestamp", timestamp);
ret.put("signature", signature);
return ret;
}
工具类(获取token、ticket、sign方法)
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.codec.digest.DigestUtils;
import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.HttpStatus;
import org.apache.http.client.HttpClient;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.util.EntityUtils;
import java.io.UnsupportedEncodingException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
/**
* @FileName: WeChatSignUtil
* @Description: 微信签名的工具类
* @author: <a href="mailto: muyuanpei@camelotchina.com">myp</a>
* @create: 2019-07-17 15:56
* @Copyright: (c) 2018年 北京柯莱特科技有限公司
*/
@Slf4j
public class WeChatSignUtil {
// 获取access_token
public static String getToken(String apiurl, String appid, String secret){
//拼接访问地址
String turl = String.format("%s?grant_type=client_credential&appid=%s&secret=%s", apiurl,appid, secret);
HttpClient client = new DefaultHttpClient();
//get请求
HttpGet get = new HttpGet(turl);
// 初始化解析json格式的对象
JsonParser jsonparer = new JsonParser();
String result = null;
try
{
HttpResponse res = client.execute(get);
String responseContent = null; // 初始化响应内容
HttpEntity entity = (HttpEntity) res.getEntity();
//设置编码格式
responseContent = EntityUtils.toString((org.apache.http.HttpEntity) entity, "UTF-8");
// 将json字符串转换为json对象
JsonObject json = jsonparer.parse(responseContent).getAsJsonObject();
if (res.getStatusLine().getStatusCode() == HttpStatus.SC_OK)
{
if (json.get("errcode") != null){
// 错误时微信会返回错误码等信息,{"errcode":40013,"errmsg":"invalid appid"}
}
else{
// 正常情况下{"access_token":"ACCESS_TOKEN","expires_in":7200}
result = json.get("access_token").getAsString();
}
}
}
catch (Exception e){
e.printStackTrace();
}
finally{
// 关闭连接 ,释放资源
client.getConnectionManager().shutdown();
return result;
}
}
// 获取getTicket
public static String getTicket(String apiurl,String access_token){
String turl = String.format("%s?access_token=%s&type=jsapi",apiurl,access_token);
HttpClient client = new DefaultHttpClient();
HttpGet get = new HttpGet(turl);
JsonParser jsonparer = new JsonParser();// 初始化解析json格式的对象
String result = null;
try
{
HttpResponse res = client.execute(get);
String responseContent = null; // 响应内容
HttpEntity entity = res.getEntity();
responseContent = EntityUtils.toString(entity, "UTF-8");
JsonObject json = jsonparer.parse(responseContent).getAsJsonObject();
// 将json字符串转换为json对象
if (res.getStatusLine().getStatusCode() == HttpStatus.SC_OK){
if (json.get("errcode") == null){
// 错误时微信会返回错误码等信息,{"errcode":40013,"errmsg":"invalid appid"}
}
else{
// 正常情况下{"access_token":"ACCESS_TOKEN","expires_in":7200}
result = json.get("ticket").getAsString();
}
}
}
catch (Exception e)
{
e.printStackTrace();
}
finally
{
// 关闭连接 ,释放资源
client.getConnectionManager().shutdown();
return result;
}
}
/**
* <p>Discription: [生成签名]</p>
*
* @param ticket jsapi_ticket
* @param url url
* @param nonce_str 随机字符串
* @param timestamp 时间戳
* @return 签名结果
*/
public static String getSignature(String ticket,String url,String nonce_str,String timestamp) {
Map<String, String> signParams = new HashMap<>();
signParams.put("noncestr",nonce_str);
signParams.put("jsapi_ticket",ticket);
signParams.put("timestamp",timestamp);
signParams.put("url", url);
// 对请求参数排序,并生成签名原文
String original = sort(signParams);
// 使用sha1生成签名
String signStr = DigestUtils.shaHex(original);
return signStr;
}
/***
* MD5
* @param original 原文
* @return 哈希结果
*/
private static String md5(String original) throws NoSuchAlgorithmException, UnsupportedEncodingException {
MessageDigest messageDigest;
messageDigest = MessageDigest.getInstance("MD5");
messageDigest.update(original.getBytes("UTF-8"));
return byte2hex(messageDigest.digest());
}
/***
* sha1
* @param original 原文
* @return 加密结果
*/
private static String sha1(String original){
String sign = DigestUtils.shaHex(original);
return sign;
}
/**
* <p>Discription: [转换为16进制]</p>
*
* @param bytes 二进制
* @return 16进制
*/
private static String byte2hex(byte[] bytes) {
StringBuilder sign = new StringBuilder();
for (int i = 0; i < bytes.length; i++) {
String hex = Integer.toHexString(bytes[i] & 0xFF);
if (hex.length() == 1) {
sign.append("0");
}
sign.append(hex.toUpperCase());
}
return sign.toString();
}
/**
* <p>Discription: [字典排序]</p>
*
* @param params 参数集合
* @return 排序后的字符串,格式为:a=value1&b=value2&Secret
*/
private static String sort(Map<String, String> params) {
String[] keys = params.keySet().toArray(new String[0]);
Arrays.sort(keys);
StringBuilder query = new StringBuilder();
for (String key : keys) {
if (query.length() > 0) {
query.append("&");
}
String value = params.get(key);
query.append(key).append("=").append(value);
}
return query.toString();
}
}
网友评论