• 微信分享(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朋友们,分享一些他们的技术见解和生活故事。

    更多信息请关注公众号:「软件老王」,关注不迷路,IT技术与相关干货分享,回复关键字获取对应干货,本文版权归作者软件老王所有,转载需注明作者、超链接,否则保留追究法律责任的权利。
  • 相关阅读:
    .emacs
    boost 程序库完全开发指南_ch3_memory_manager
    C++ 友元函数
    AutoHotKey.ini
    QQ因系统日期无法打开
    SharePoint 2007中如何更改用戶基本資料(EMail地址)
    盗版Windows XP如何安装IE7
    三人行,必有我師學會了家中布線ADSL上網
    以數據源方式讀取文本文件連接串及注事事項
    网络访问时you might not have permission to use this network resource错误解决方法
  • 原文地址:https://www.cnblogs.com/ruanjianlaowang/p/15093753.html
Copyright © 2020-2023  润新知