赞
踩
企业微信 js-sdk 签名认证
链接附上:JS-SDK使用权限签名算法
从这个图片中我们看到生成签名需要四个参数 : jsapi_ticket、nonceStr、timestamp、url.下面我们分别说说这四个参数的获取.
从企业微信api的说明中我们知道jsapi_ticket的获取和access_token一样是有有效期,并且调用次数有限,需要缓存到自己的全局服务中的,为了满足这些要求,我们首先想到的是通过redis来实现.
为了获取jsapi_ticket我们需要一个参数access_token。获取access_token的api链接:
获取access_token
@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; } } }
大家在使用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; } }
因为我们使用的是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; }
远程配置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}
然后启动就ok啦,祝你们成功,有问题欢迎评论区讨论~
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。