企业微信系列之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×tamp=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>