微信分享(JS-SDK許可權簽名演算法)-Java實現
1、問題描述
公眾號中的H5有個業務場景,要分享頁面給好友,但是因為是在微信中分享,分享的連結微信是不認的,需要首先使用簽名認證,認證後才能分享,按照微信官網api,首先需要獲取token,然後再根據token獲取jsapiticket,然後再將隨機數、時間戳、url等按照keyvalue排序加密去認證,java後端實現了下,分享下程式碼,給需要的朋友。
2、解決方案
2.1 官方文件
官方文件才是yyds,首先檢視微信開發者文件(https://developers.weixin.qq.com/doc/offiaccount/OA_Web_Apps/JS-SDK.html),如下:
附錄1-JS-SDK使用許可權簽名演算法 jsapi_ticket 生成簽名之前必須先了解一下jsapi_ticket,jsapi_ticket是公眾號用於呼叫微信JS介面的臨時票據。正常情況下,jsapi_ticket的有效期為7200秒,通過access_token來獲取。由於獲取jsapi_ticket的api呼叫次數非常有限,頻繁重新整理jsapi_ticket會導致api呼叫受限,影響自身業務,開發者必須在自己的服務全域性快取jsapi_ticket 。 參考以下文件獲取access_token(有效期7200秒,開發者必須在自己的服務全域性快取access_token):https://developers.weixin.qq.com/doc/offiaccount/Basic_Information/Get_access_token.html 用第一步拿到的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=Wm3WZYTPz0wzccnW jsapi_ticket=sM4AOVdWfPE4DxkXGEs8VMCPGGVi4C3VM0P37wVUCFvkVAy_90u5h9nbSlYy3-Sl-HhTdfl2fzFy1AOcHKP7qg timestamp=1414587457 url=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 注意事項 簽名用的noncestr和timestamp必須與wx.config中的nonceStr和timestamp相同。 簽名用的url必須是呼叫JS介面頁面的完整URL。 出於安全考慮,開發者必須在伺服器端實現簽名的邏輯。 如出現invalid signature 等錯誤詳見附錄5常見錯誤及解決辦法。
官網文件簡要來說有幾點:
(1)獲取jsapi_ticket
首先拿appid與secret換取全域性的access_token,然後通過access_token獲取jsapi_ticket,這兩個值的時效都是是7200秒(2個小時),access_token是全域性的,簡單說類似管理員許可權,每天呼叫次數有限,2000次/日,這個提到全域性的,就有區域性的,使用過網頁許可權的(自定義選單,H5跳轉),也有個access_token,呼叫次數不限;因為access_token和jsapi_ticket每日呼叫次數有限,需要把這個值進行快取;
(2)簽名加密
排序、加密,需要對noncestr(隨機字串)、 有效的jsapi_ticket, timestamp(時間戳)、 url,對所有待簽名引數按照欄位名的ASCII 碼從小到大排序(字典序)後,使用URL鍵值對的格式(即key1=value1&key2=value2…)拼接成字串string1,然後sha1加密;
2.2 程式碼實現
一步一步來說吧,首先說下關於快取token和jsapi_ticket的問題,很多方案都是直接放到redis中,但是目前專案比較簡單,類似前置機的概念,專案週期也短,就這2個值,還的部署下redis,感覺划不來,就直接庫放資料中,弄個定時的標籤,整點刷一下,一天刷12次,24個值,穩定可靠,關於儲存資料庫和重新整理就不在這裡介紹了。
(1)首先獲取access_token
/** * 獲取token * @return */ @PostMapping("/getWXaccessToken") public String getWXaccessToken() { String url ="https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid="+appId+"&secret=" +secret; String resp = restTemplate.getForObject(url, String.class); JSONObject resJson = JSONObject.parseObject(resp); return resJson.getString("access_token"); }
簡要說明:
這裡就是首先new RestTemplate(),然後拼接下appId和secret,就獲取access_token,然後把這個access_token存到資料庫中,然後提供查詢就好了;
(2)獲取jsapi_ticket
/**
* 入參為token,返回ticket
* @param token
* @return
*/
@PostMapping("/getWXJsapiTicket")
public String getWXJsapiTicket(String token) {
String ticket = null;
if (StringUtils.isBlank(ticket)) {
String url ="https://api.weixin.qq.com/cgi-bin/ticket/getticket?access_token=" + token +"&type=jsapi";
String resp = restTemplate.getForObject(url, String.class);
JSONObject resJson = JSONObject.parseObject(resp);
return resJson.getString("ticket");
}
return ticket;
}
拼接下access_token,然後把這個access_token存到資料庫中,然後提供查詢就好了;
(3)隨機數
/**
* 獲取隨機數
* @param length
* @return
*/
@PostMapping("/getRandomStr")
public String getRandomStr(int length) {
String base ="fdajfkdajsklfjafdkjxjkljfadnfdnamn12687";
int randomNum;
char randomChar;
Random random =new Random();
StringBuffer str =new StringBuffer();
for (int i =0; i < length; i++) {
randomNum = random.nextInt(base.length());
randomChar = base.charAt(randomNum);
str.append(randomChar);
}
return str.toString();
}
(4) 獲取簽名,完結
/**
* 入參為url
* @param reqJson
* @return
*/
@PostMapping("/getWXSign")
public String getWXSign(@RequestBody JSONObject reqJson) {
String url = reqJson.getString("url");
long timeStampSec = System.currentTimeMillis() /1000;
String timestamp = String.format("%010d", timeStampSec);
String nonceStr = getRandomStr(8);
String[] urls = url.split("#");
String newUrl = urls[0];
JSONObject respJson =new JSONObject();
String[] signArr =new String[]{"url=" + newUrl,"jsapi_ticket=" + getWXJsapiTicket(getWXaccessToken()),"noncestr=" + nonceStr,"timestamp=" + timestamp};
Arrays.sort(signArr);
String signStr = StringUtils.join(signArr,"&");
String resSign = DigestUtils.sha1Hex(signStr);
respJson.put("appId", appId);
respJson.put("timestamp", timestamp);
respJson.put("nonceStr", nonceStr);
respJson.put("signature", resSign);
return respJson.toJSONString();
}
簡要說明:簡單來說就是獲取jsapiticket,然後值排序,做下sha1加密就可以了。
完整類:
測試,使用的使用根據自己業務場景,稍作改動吧
@RestController
@RequestMapping("/api/wx")
@Api(value = "測試")
public class WXShareController {
private static final Logger logger = LoggerFactory.getLogger(WXShareController.class);
private static final String appId ="ruanjianlaowang";
private static final String secret ="dashuaige";
RestTemplate restTemplate = new RestTemplate();
/**
* 入參為url
* @param reqJson
* @return
*/
@PostMapping("/getWXSign")
public String getWXSign(@RequestBody JSONObject reqJson) {
String url = reqJson.getString("url");
long timeStampSec = System.currentTimeMillis() /1000;
String timestamp = String.format("%010d", timeStampSec);
String nonceStr = getRandomStr(8);
String[] urls = url.split("#");
String newUrl = urls[0];
JSONObject respJson =new JSONObject();
String[] signArr =new String[]{"url=" + newUrl,"jsapi_ticket=" + getWXJsapiTicket(getWXaccessToken()),"noncestr=" + nonceStr,"timestamp=" + timestamp};
Arrays.sort(signArr);
String signStr = StringUtils.join(signArr,"&");
String resSign = DigestUtils.sha1Hex(signStr);
respJson.put("appId", appId);
respJson.put("timestamp", timestamp);
respJson.put("nonceStr", nonceStr);
respJson.put("signature", resSign);
return respJson.toJSONString();
}
/**
* 入參為token,返回ticket
* @param token
* @return
*/
@PostMapping("/getWXJsapiTicket")
public String getWXJsapiTicket(String token) {
String ticket = null;
if (StringUtils.isBlank(ticket)) {
String url ="https://api.weixin.qq.com/cgi-bin/ticket/getticket?access_token=" + token +"&type=jsapi";
String resp = restTemplate.getForObject(url, String.class);
JSONObject resJson = JSONObject.parseObject(resp);
return resJson.getString("ticket");
}
return ticket;
}
/**
* 獲取token
* @return
*/
@PostMapping("/getWXaccessToken")
public String getWXaccessToken() {
String url ="https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid="+appId+"&secret=" +secret;
String resp = restTemplate.getForObject(url, String.class);
JSONObject resJson = JSONObject.parseObject(resp);
return resJson.getString("access_token");
}
/**
* 獲取隨機數
* @param length
* @return
*/
@PostMapping("/getRandomStr")
public String getRandomStr(int length) {
String base ="abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
int randomNum;
char randomChar;
Random random =new Random();
StringBuffer str =new StringBuffer();
for (int i =0; i < length; i++) {
randomNum = random.nextInt(base.length());
randomChar = base.charAt(randomNum);
str.append(randomChar);
}
return str.toString();
}
}
更多資訊請關注公眾號:「軟體老王」,關注不迷路,軟體老王和他的IT朋友們,分享一些他們的技術見解和生活故事。
更多資訊請關注公眾號:「軟體老王」,關注不迷路,IT技術與相關乾貨分享,回覆關鍵字獲取對應乾貨,本文版權歸作者軟體老王所有,轉載需註明作者、超連結,否則保留追究法律責任的權利。