前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >企业微信系列之JSSDK使用权限签名对接

企业微信系列之JSSDK使用权限签名对接

作者头像
SmileNicky
修改2022-07-18 14:14:29
2.2K1
修改2022-07-18 14:14:29
举报
文章被收录于专栏:Nicky's blogNicky's blog

企业微信系列之JSSDK使用权限签名对接

最近在对接企业微信,要将H5页面嵌在APP里,所以得根据企业微信官网规范,先对接JS-SDK使用权限签名 官网:JS-SDK使用权限签名算法

引用官方文档的说法:

签名生成规则如下: 参与签名的参数有四个: noncestr(随机字符串), jsapi_ticket(如何获取参考“获取企业jsapi_ticket”以及“获取应用的jsapi_ticket接口”), timestamp(时间戳), url(当前网页的URL, 不包含#及其后面部分)

将这些参数使用URL键值对的格式 (即 key1=value1&key2=value2…)拼接成字符串string1。 有两个注意点:1. 字段值采用原始值,不要进行URL转义;2. 必须严格按照如下格式拼接,不可变动字段顺序。 jsapi_ticket=JSAPITICKET&noncestr=NONCESTR&timestamp=TIMESTAMP&url=URL 然后对string1作sha1加密即可。

根据官网提示,我们其实主要获取 jsapi_ticket这个ticket,里面分为企业的jsapi_ticket和应用的jsapi_ticket

在这里插入图片描述
在这里插入图片描述
代码语言:javascript
复制
/**
 * <h2>获取JS SDK签名ticket</h2>
 * @Author nicky
 * @Date 2021/04/25 20:23
 * @return java.lang.String
 */
public String getJsapiTicket() throws Exception {
    String token = obtainAccessToken();
    Assert.hasText(token, "数广AccessToken获取不到");
    String ticket = "";
    Object cache = CacheManager.get(WX_TICKET_CACHE_KEY);
    if (cache != null) {
        ticket = cache.toString();
        log.info("从缓存读取Ticket:{}",ticket);
        if (StringUtils.isNotBlank(ticket)) {
            return ticket;
        }
    }
    // ticket获取URI
    String getTicketUri = wxParam.getTickeUri();
    // 拼装ticket URL
    String getTicketUrl = new StringBuffer().append(wxParam.getDomain()).append(getTicketUri).toString();
    log.info("getTicketUrl:{}", getTicketUrl);
    // 构建请求参数
    Map<String, String> params = new HashMap<String, String>(2);
    params.put("access_token" , token);

    // 远程调用
    String result = doGet(getTicketUrl , params , NumConstant.COMMON_NUM_ONE);
    JSONObject retJson = JSONObject.parseObject(result);
    log.info("ticket接口返回:{}",result);
    if(NumConstant.COMMON_NUM_ZERO.equals(retJson.getString("errcode"))){
        log.info("errcode:{}",retJson.getString("errcode"));
        log.info("ticket:{}",retJson.getString("ticket"));
        ticket = retJson.getString("ticket");
        CacheManager.put(WX_TICKET_CACHE_KEY , ticket, wxParam.getExpire());
    }
    return ticket;
}
在这里插入图片描述
在这里插入图片描述

代码参考:

代码语言:javascript
复制
/**
 * 获取AccessToken <br>
 * @Author nicky
 * @Date 2021/04/25 20:19
 * @return java.lang.String
 */
public String obtainAccessToken() throws Exception{
    String accessToken = "";
    Object cache = CacheManager.get(WX_TOKEN_CACHE_KEY);
    if (cache != null) {
        accessToken = cache.toString();
        log.info("从缓存读取AccepToken:{}",accessToken);
        if (StringUtils.isNotBlank(accessToken)) {
            return accessToken;
        }
    }
    // 企业ID
    String corpid = wxParam.getCorpid();
    // 应用的凭证密钥
    String corpsecret = wxParam.getCorpsecret();
    // accessToken获取URI
    String getTokenUri = wxParam.getTokenUri();
    // 拼装accessToken URL
    String getTokenUrl = new StringBuffer().append(wxParam.getDomain()).append(getTokenUri).toString();
    log.info("obtainAccessToken方法参数打印, corpid:{},corpsecret:{} ", corpid, corpsecret);
    log.info("getTokenUrl:{}", getTokenUrl);
    // 构建请求参数
    Map<String, String> params = new HashMap<String, String>(2);
    params.put("corpid" , corpid);
    params.put("corpsecret" , corpsecret);

    // 远程调用
    String result = doGet(getTokenUrl , params , NumConstant.COMMON_NUM_ONE);
    JSONObject retJson = JSONObject.parseObject(result);
    log.info("token接口返回:{}",result);
    if(NumConstant.COMMON_NUM_ZERO.equals(retJson.getString("errcode"))){
        log.info("errcode:{}",retJson.getString("errcode"));
        log.info("accessToken:{}",retJson.getString("access_token"));
        accessToken = retJson.getString("access_token");
        CacheManager.put(WX_TOKEN_CACHE_KEY , accessToken, wxParam.getExpire());
    }
    return accessToken;
}

步骤1. 对所有待签名参数按照字段名的ASCII 码从小到大排序(字典序)后,使用URL键值对的格式 (即key1=value1&key2=value2…)拼接成字符串string1

代码语言:javascript
复制
/**
 * <h2>获取数广JS-SDK配置参数</h2>
 * @Author nicky
 * @Date 2021/04/28 17:29
 * @return com.module.thirdParty.digitalgd.model.vo.AgentConfigParam
 */
public AgentConfigParam obtainConfigParam(String url) throws Exception {
    log.info("url:{}",url);
    String ticket = this.getJsapiTicket();
    Assert.hasText(ticket , "Ticket获取不到");

    String corpid = wxParam.getCorpid();
    String nonceStr = RandomStringUtils.randomAlphanumeric(10);
    String timeStamp = Long.toString(System.currentTimeMillis() / 1000);
    log.info("corpid:{},nonceStr:{},timeStamp:{}", corpid , nonceStr , timeStamp);

    SortedMap<String, String> params = new TreeMap<String, String>();
    params.put("noncestr" , nonceStr);
    params.put("jsapi_ticket" , ticket);
    params.put("timestamp" , timeStamp);
    params.put("url" , url);
    String signature = sortSignByASCII(params);
    log.info("step1:待签名参数按照字段名的ASCII码从小到大排序:{}",signature);
    signature = sha1Digest(signature);
    log.info("step2:对string1进行sha1签名,得到signature:{}",signature);

    AgentConfigParam configParam = AgentConfigParam.builder()
            .corpid(corpid)
            .nonceStr(nonceStr)
            .timestamp(timeStamp)
            .signature(signature)
            .build();
    return configParam;
}
代码语言:javascript
复制
 /**
  * <h2>对所有待签名参数按照字段名的ASCII 码从小到大排序</h2>
  * @Author nicky
  * @Date 2021/04/25 20:22
  * @Param [params]
  * @return java.lang.String
  */
 public static String sortSignByASCII(SortedMap<String , String> parameters) {
     // 以k1=v1&k2=v2...方式拼接参数
     StringBuilder builder = new StringBuilder();
     for (Map.Entry<String, String> s : parameters.entrySet()) {
         String k = s.getKey();
         String v = s.getValue();
         // 过滤空值
         if (StringUtils.isBlank(v)) {
             continue;
         }
         builder.append(k).append("=").append(v).append("&");
     }
     if (!parameters.isEmpty()) {
         builder.deleteCharAt(builder.length() - 1);
     }
     return builder.toString();
 }

步骤2. 对string1进行sha1签名,得到signature

代码语言:javascript
复制
/**
 * sha1加密 <br>
 * @Author nicky
 * @Date 2021/04/26 10:22
 * @Param [str]
 * @return java.lang.String
 */
public static String sha1Digest(String str) {
    try {
        // 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();
    } catch (Exception e) {
        e.printStackTrace();
        return null;
    }
}

测试页面:

代码语言:javascript
复制
<!doctype html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport"
          content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <script src="/axios.min.js"></script>
    <script src="/eruda.js"></script>
    <script src="//res.wx.qq.com/open/js/jweixin-1.2.0.js"></script>
    <title>Document</title>
</head>

<body>
<script>
        window.onload = function () {
            /*调试工具而已*/
            eruda.init();

            let url = location.href.split("#")[0]
            axios.get('/getWxConfig?url=' + decodeURI(url)).then(response => {
                let configParam = response.data;  // console.log(configParam)
                wx.config({
                    beta: true,// 调用wx.invoke形式的接口值时,该值必须为true。
                    debug: true, // 开启调试模式,调用的所有api的返回值会在客户端alert出来,若要查看传入的参数,可以在pc端打开,参数信息会通过log打出,仅在pc端时才会打印。
                    appId: configParam.appId, // 必填cropID
                    timestamp: Number.parseInt(configParam.timestamp), // 必填,生成签名的时间戳
                    nonceStr: configParam.nonceStr, // 必填,生成签名的随机串
                    signature: configParam.signature,// 必填,签名,见附录1
                    jsApiList: ["getNetworkType", "agentConfig","selectEnterpriseContact"] // 必填,需要使用的JS接口列表,所有JS接口列表见附录2
                    // 用到了三个 selectEnterpriseContact和getNetworkType和agentConfig 都配置了
                });

                wx.ready(function (res) {
                    // config信息验证后会执行ready方法,所有接口调用都必须在config接口获得结果之后,config是一个客户端的异步操作,所以如果需要在页面加载时就调用相关接口,则须把相关接口放在ready函数中调用来确保正确执行。对于用户触发时才调用的接口,则可以直接调用,不需要放在ready函数中。
                    console.log("=================config成功ready函数中=============")

                    // 不是jssdk所有的函数都需要agentConfig的,跟用户相关的操作可能需要,具体请参考文档
                    axios.get('/agentConfig?url=' + decodeURI(url)).then(response => {
                        let agentConfigParam = response.data; //console.log(agentConfigParam)
                        wx.invoke("agentConfig", {
                            agentid: agentConfigParam.agentid, // 必填,单位应用的agentid
                            corpid: agentConfigParam.corpid,  // 必填的corpid
                            timestamp: Number.parseInt(agentConfigParam.timestamp), // 必填,生成签名的时间戳,int类型, 如 1539100800
                            nonceStr: agentConfigParam.nonceStr, // 必填,生成签名的随机串
                            signature: agentConfigParam.signature,// 必填,签名,见附录5
                        }, function (res) {
                            alert(JSON.stringify(res))
                            console.log(res)
                            if (res.err_msg != "agentConfig:ok" && res.errMsg != "agentConfig:ok") {
                                return;
                            }
                            // 下面是另外一个组件具体用法参考文档
                            wx.invoke("selectEnterpriseContact", {
                                "fromDepartmentId": -1,// 必填,-1表示打开的通讯录从自己所在部门开始展示, 0表示从最上层开始
                                "mode": "multi",// 必填,选择模式,single表示单选,multi表示多选
                                "type": ["user"],// 必填,选择限制类型,指定department、user中的一个或者多个
                            },function(res){
                                alert(JSON.stringify(res))
                            })
                        });
                    }).catch(function (error) { // 请求失败处理
                        console.log(error);
                    });

                    wx.getNetworkType({
                        success: function (res) {
                            console.log(res)
                        }
                    });
                })

                wx.error(function (res) {
                    // config信息验证失败会执行error函数,如签名过期导致验证失败,具体错误信息可以打开config的debug模式查看,也可以在返回的res参数中查看,对于SPA可以在这里更新签名。
                    console.log("====================config失败error函数中===============================")
                });

            }).catch(function (error) { // 请求失败处理
                console.log(error);
            });

        }
    </script>
</body>

</html>
本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2021-05-14 ,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档