微信分享(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&timestamp=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朋友們,分享一些他們的技術見解和生活故事。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM