当前位置:   article > 正文

企业微信 js-sdk 签名认证_企业微信签名测试

企业微信签名测试

企业微信 js-sdk 签名认证

整体开发步骤:(springboot+redis+nacos

1.首先找到企业微信API中关于JS-SDK的开发说明

链接附上:JS-SDK使用权限签名算法
这签名里插入图片描述从这个图片中我们看到生成签名需要四个参数 : jsapi_ticket、nonceStr、timestamp、url.下面我们分别说说这四个参数的获取.

2.参与签名的四个参数的获取

1.url:获取方式在下面的汇总代码里

2.timestamp: 获取方式在下面的汇总代码里

3.nonceStr:随机数,不重复就行,我这里使用的是UUID,各位看官,也可以按照自己的喜好生成随机数

4.jsapi_ticket:这个也是今天的重中之重

在这里插入图片描述从企业微信api的说明中我们知道jsapi_ticket的获取和access_token一样是有有效期,并且调用次数有限,需要缓存到自己的全局服务中的,为了满足这些要求,我们首先想到的是通过redis来实现.
在这里插入图片描述为了获取jsapi_ticket我们需要一个参数access_token。获取access_token的api链接:
获取access_token

3.获取到四个参数后 对参数按照api说明 拼接成一个完整的String,后对string进行sha1算法加密进行返回。完整代码如下:

@Slf4j
@AllArgsConstructor
@RestController
@RequestMapping("/JSApiTicketUtil")
public class JSTicketWxController {
    private final static String WORK_WECHAT_PREFIX = "wechat:work:secret:";
    private final TempUrlConfig workWechatConfig;
    private final RestTemplate restTemplate;
    private final StringRedisTemplate stringRedisTemplate;

    /**
     * 获取微信参数,因为前端传来的url为json形式,采用@ResponsBody来接收
     *
     * @param param
     * @return
     */
    @PostMapping(value = "/util")
    public AppResult<SignatureVo> getWechatParam(@RequestBody JSONObject param) {
        String urlStr = param.getString("url");
        SignatureVo signatureVo = new SignatureVo();
        String nonceStr = nonceStr();
        String timestamp = createTimestamp();
        String signature = getSignature(nonceStr, urlStr, timestamp);
        signatureVo.setNonceStr(nonceStr);
        signatureVo.setTimestamp(timestamp);
        signatureVo.setSignature(signature);
        return AppResultBuilder.success(signatureVo);
    }

    /**
     * 生成个性化签名
     *
     * @param nonceStr
     * @param urlStr
     * @param timeStamp
     * @return
     */
    private String getSignature(String nonceStr, String urlStr, String timeStamp) {
        //生成个性签名 需要四个参数
        // noncestr(随机字符串),jsapi_ticket(是H5应用调用企业微信JS接口的临时票据), timestamp(时间戳),url(当前网页的URL,不包含#及其后面部分)
        //jsapi_ticket 需要先获取accessToken  根据秘钥获取accesstoken
        //先读取jsapi_ticket  如果jsapi_ticket 为空则重新生成jsapi_ticket 如果不为空则直接使用
        String key = WORK_WECHAT_PREFIX + "jsapi_ticket";
        String getJsapiTicketUrl = stringRedisTemplate.opsForValue().get(key);
        if (StringUtils.isEmpty(getJsapiTicketUrl)) {
            String accessToken = getWorkWechatAccessToken(workWechatConfig.getCorpSecret());
            synchronized (this) {
                getJsapiTicketUrl = stringRedisTemplate.opsForValue().get(key);
                if (StringUtils.isNotEmpty(getJsapiTicketUrl)) {
                    return getJsapiTicketUrl;
                }

                JSTicketWxVo jsTicketWxVo =
                        restTemplate.getForObject(workWechatConfig.getGetJsapiTicketUrl(), JSTicketWxVo.class, accessToken);
                // 出错返回码,为0表示成功,非0表示调用失败
                if (accessToken == null || jsTicketWxVo.getErrCode() == null || jsTicketWxVo.getErrCode() != 0) {
                    throw new RuntimeException("获取微信token失败");
                }
                getJsapiTicketUrl = jsTicketWxVo.getTicket();
                stringRedisTemplate.opsForValue().set(key, getJsapiTicketUrl, 7000, TimeUnit.SECONDS);
            }
        }
        String jsapi_ticket="jsapi_ticket="+getJsapiTicketUrl+"&";
        String noncestr="noncestr="+nonceStr+"&";
        String timestamp="timestamp="+timeStamp+"&";
        String url="url="+urlStr;
        String signature = getSha1(jsapi_ticket.concat(noncestr).concat(timestamp).concat(url));
        return signature;
    }

    /**
     * 根据secret获得对应的accessToken
     *
     * @param secret 对应应用的secret
     * @return accessToken
     */
    public String getWorkWechatAccessToken(String secret) {
        String key = WORK_WECHAT_PREFIX + secret;
        //尝试先从缓存读取
        String cacheToken = stringRedisTemplate.opsForValue().get(key);
        if (StringUtils.isEmpty(cacheToken)) {
            synchronized (this) {
                cacheToken = stringRedisTemplate.opsForValue().get(key);
                if (StringUtils.isNotEmpty(cacheToken)) {
                    return cacheToken;
                }
                cacheToken = getTokenFromWechat(workWechatConfig.getCorpId(), secret);
                stringRedisTemplate.opsForValue().set(key, cacheToken, 7000, TimeUnit.SECONDS);
            }
        }
        return cacheToken;
    }

    /**
     * 调用微信接口获取token
     *
     * @return token
     */
    private String getTokenFromWechat(String key, String secret) {
        WorkWechatAccessToken accessToken =
                restTemplate.getForObject(workWechatConfig.getTokenUrl(), WorkWechatAccessToken.class, key, secret);
        if (log.isDebugEnabled()) {
            log.debug("根据corId={},secret={},accessToken信息={}",
                    workWechatConfig.getCorpId(), secret, accessToken);
        }
        // 出错返回码,为0表示成功,非0表示调用失败
        if (accessToken == null || accessToken.getErrCode() == null || accessToken.getErrCode() != 0) {
            throw new RuntimeException("获取微信token失败");
        }
        return accessToken.getAccessToken();
    }

    /**
     * 生成nonceStr字符串
     */
    private static String nonceStr() {
        UUID nonceStr = UUID.randomUUID();
        return nonceStr.toString();
    }

    /**
     * 生成时间戳
     *
     * @return
     */
    private static String createTimestamp() {
        return Long.toString(System.currentTimeMillis() / 1000);
    }

    /**
     * sha1算法
     *
     * @param str
     * @return
     */
    private static String getSha1(String str) {
        char hexDigits[] = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
                'a', 'b', 'c', 'd', 'e', 'f'};
        try {
            MessageDigest mdTemp = MessageDigest.getInstance("SHA1");
            mdTemp.update(str.getBytes("UTF-8"));
            byte[] md = mdTemp.digest();
            int j = md.length;
            char buf[] = new char[j * 2];
            int k = 0;
            for (int i = 0; i < j; i++) {
                byte byte0 = md[i];
                buf[k++] = hexDigits[byte0 >>> 4 & 0xf];
                buf[k++] = hexDigits[byte0 & 0xf];
            }
            return new String(buf);
        } catch (Exception e) {
            return null;
        }
    }
}

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113
  • 114
  • 115
  • 116
  • 117
  • 118
  • 119
  • 120
  • 121
  • 122
  • 123
  • 124
  • 125
  • 126
  • 127
  • 128
  • 129
  • 130
  • 131
  • 132
  • 133
  • 134
  • 135
  • 136
  • 137
  • 138
  • 139
  • 140
  • 141
  • 142
  • 143
  • 144
  • 145
  • 146
  • 147
  • 148
  • 149
  • 150
  • 151
  • 152
  • 153
  • 154
  • 155
  • 156
  • 157

大家在使用RestTemplate 时 启动时 可能会因为springboot版本问题报错,报找不到对应的对象,可以将RestTemplate该类的对象,手动注入,加入如下类:

@Configuration
public class WebConfig {
    /**
     * 创建rest模板
     *
     * @return rest模板
     */
    @Bean
    public RestTemplate restTemplate() {
        HttpComponentsClientHttpRequestFactory httpRequestFactory = new HttpComponentsClientHttpRequestFactory();
        RestTemplate restTemplate = new RestTemplate(httpRequestFactory);
        restTemplate.setRequestFactory(clientHttpRequestFactory());

        return restTemplate;
    }

    /**
     * 客户端请求链接策略
     *
     * @return 客户端请求工厂
     */
    private ClientHttpRequestFactory clientHttpRequestFactory() {
        HttpComponentsClientHttpRequestFactory clientHttpRequestFactory = new HttpComponentsClientHttpRequestFactory();
        clientHttpRequestFactory.setHttpClient(httpClientBuilder().build());
        // 连接超时时间/毫秒
        clientHttpRequestFactory.setConnectTimeout(60 * 1000);
        // 读写超时时间/毫秒
        clientHttpRequestFactory.setReadTimeout(60 * 1000);
        // 请求超时时间/毫秒
        clientHttpRequestFactory.setConnectionRequestTimeout(50 * 1000);
        return clientHttpRequestFactory;
    }

    /**
     * 设置HTTP连接管理器,连接池相关配置管理
     *
     * @return 客户端链接管理器
     */
    private HttpClientBuilder httpClientBuilder() {
        HttpClientBuilder httpClientBuilder = HttpClientBuilder.create();
        httpClientBuilder.setConnectionManager(poolingConnectionManager()).setConnectionManagerShared(true);
        ConnectionKeepAliveStrategy connectionKeepAliveStrategy = (httpResponse, httpContext) -> 20 * 1000;
        httpClientBuilder.setKeepAliveStrategy(connectionKeepAliveStrategy);
        httpClientBuilder.setRetryHandler(new DefaultHttpRequestRetryHandler());
        RequestConfig requestConfig = RequestConfig.custom()
                .setConnectTimeout(10 * 1000)
                .setSocketTimeout(10 * 1000)
                .setConnectionRequestTimeout(10 * 1000)
                .build();
        httpClientBuilder.setDefaultRequestConfig(requestConfig);
        return httpClientBuilder;
    }

    /**
     * 链接线程池管理,可以keep-alive不断开链接请求,这样速度会更快 MaxTotal 连接池最大连接数 DefaultMaxPerRoute
     * 每个主机的并发 ValidateAfterInactivity
     * 可用空闲连接过期时间,重用空闲连接时会先检查是否空闲时间超过这个时间,如果超过,释放socket重新建立
     *
     * @return httpClient连接管理
     */
    private HttpClientConnectionManager poolingConnectionManager() {
        PoolingHttpClientConnectionManager poolingConnectionManager = new PoolingHttpClientConnectionManager();
        poolingConnectionManager.setMaxTotal(1000);
        poolingConnectionManager.setDefaultMaxPerRoute(5000);
        poolingConnectionManager.setValidateAfterInactivity(30000);
        poolingConnectionManager.closeIdleConnections(30, TimeUnit.SECONDS);
        poolingConnectionManager.closeExpiredConnections();
        return poolingConnectionManager;
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70

因为我们使用的是nacos,所以一些链接配置 我都统一配置在了nacos的配置文件中。加一个配置类就可以读取nacos配置中心中的变量,如果你们不使用到,调用api链接时,直接在本地写调用get链接请求也行。

@Data
@Configuration
@ConfigurationProperties(prefix = "url")
public class TempUrlConfig {
    /**
     * 企业微信企业秘钥
     */
    private String corpSecret;
    /**
     * 企业微信企业id
     */
    private String corpId;
    /**
     * 获得token
     */
    private String tokenUrl;
    /**
     * 获得jsapi_ticket
     */
    private String getJsapiTicketUrl;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21

远程配置nacos加上配置如下:

url:
  corpId: wx41018ffde404497e
  corpSecret: 1qKf-AKMWpDihrzDjulGSVHbFvN44A6FaadcTtm-Y_w
  tokenUrl: https://qyapi.weixin.qq.com/cgi-bin/gettoken?corpid={corpId}&corpsecret={corpSecret}
  getJsapiTicketUrl: https://qyapi.weixin.qq.com/cgi-bin/get_jsapi_ticket?access_token={accessToken}
  • 1
  • 2
  • 3
  • 4
  • 5

然后启动就ok啦,祝你们成功,有问题欢迎评论区讨论~

声明:本文内容由网友自发贡献,转载请注明出处:【wpsshop】
推荐阅读
相关标签
  

闽ICP备14008679号