钉钉鉴权
前言
- 公司有项目,需要用到钉钉鉴权,还是dotnet core 版的
- 百度了一下,发现大多是水文,还有部分是误导的。
- 于是自己配置了一下
- 有几点要注意的
- 后端和前端的 sdk 都是老版本的
- 新版本的可以按官网,一步一步配置就可以了。
- ticket 还是要缓存的,频繁请求 token 会
- 在使用accessToken时,请注意:
accessToken的有效期为7200秒(2小时),有效期内重复获取会返回相同结果并自动续期,过期后获取会返回新的accessToken。
开发者需要缓存accessToken,用于后续接口的调用。因为每个应用的accessToken是彼此独立的,所以进行缓存时需要区分应用来进行存储。
不能频繁调用gettoken接口,否则会受到频率拦截。 - ticket 说明
企业内部应用是以应用维度获取jsapi_ticket的,所以在使用的时候需要将jsapi_ticket以appKey为维度进行缓存下来(设置缓存过期时间2小时),并不需要每次都通过接口拉取。
后端代码
// 应用凭证
// 必填,微应用ID
private readonly string agentId = "xxxxx";
private readonly string Appkey = "xxxx";
private readonly string Appsecret = "xxxxxxxxx";
//必填,企业ID
private readonly string corpId = "xxxxxxx";
// 获取 token
private string GetTestDingToken()
{
string token;
try
{
// 获取 access_token
IDingTalkClient client = new DefaultDingTalkClient("https://oapi.dingtalk.com/gettoken");
OapiGettokenRequest req = new OapiGettokenRequest();
req.Appkey = Appkey;
req.Appsecret = Appsecret;
req.SetHttpMethod("GET");
OapiGettokenResponse rsp = client.Execute(req);
token = rsp.AccessToken;
}
catch (Exception ex)
{
return ex.ToString();
}
return token;
}
/// <summary>
/// 获取 jsapi_ticket
/// </summary>
/// <returns></returns>
private string GetTicket()
{
string ticket;
var access_token = GetTestDingToken();
try
{
IDingTalkClient client = new DefaultDingTalkClient("https://oapi.dingtalk.com/get_jsapi_ticket");
OapiGetJsapiTicketRequest req = new OapiGetJsapiTicketRequest();
req.SetHttpMethod("GET");
OapiGetJsapiTicketResponse rsp = client.Execute(req, access_token);
//ticket = string.IsNullOrEmpty(rsp.Ticket) ? rsp.Ticket : rsp.ErrMsg + "/" + rsp.ErrCode;
ticket = rsp.Ticket; // rsp.Body;
}
catch (Exception ex)
{
ticket = ex.ToString();
}
return ticket;
}
/// <summary>
/// 获取 noncestr 随机串
/// </summary>
/// <returns></returns>
private string GetTicketRandomStr(int count)
{
string seed = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
Random random = new Random();
StringBuilder stringBuilder = new StringBuilder();
for (int i = 0; i < count; i++)
{
int number = random.Next(0, seed.Length);
stringBuilder.Append(seed[number]);
}
return stringBuilder.ToString();
}
/// <summary>
/// 获取 timeStamp 时间戳
/// </summary>
/// <returns></returns>
private long GetTimeStamp()
{
TimeSpan timteSpan = DateTime.UtcNow - new DateTime(1970, 1, 1, 0, 0, 0, 0);
return Convert.ToInt64(timteSpan.TotalSeconds);
}
/// <summary>
/// SHA1 加密方法
/// </summary>
/// <returns></returns>
private static string Sha1(string text)
{
byte[] bytes = Encoding.UTF8.GetBytes(text);
byte[] dataList = SHA1.Create().ComputeHash(bytes);
StringBuilder stringBuilder = new StringBuilder();
foreach (byte data in dataList)
{
stringBuilder.Append(data.ToString("X2"));
}
return stringBuilder.ToString();
}
/// <summary>
/// 最后在服务器端生成前端页面需要的参数信息
/// </summary>
/// <returns></returns>
[HttpPost]
[Route("PostConfig")]
[NoSign]
public async Task<IActionResult> PostConfig([FromBody] ReqConfig param)
{
ObjectResultEx obj = new ObjectResultEx(-1, null, null);
//var usname = _user.UserName;
var user = await _userSer.Get(_user.UserID);
if (user != null)
{
long timeStamp = GetTimeStamp();
string ticket = GetTicket(); // "OJXrpQ82w5ufOFSfQrJme9Al0rktlMuGcucDYkuE1gjCbI0SNvnYGMfjxW2PHa6knOcUrmRpwtlJLI9qJAOK8E"; // GetTicket();
string noncestr = GetTicketRandomStr(20);
string url = HttpUtility.UrlDecode(param.GetUrl);
string plainTex = "jsapi_ticket=" + ticket + "&noncestr=" + noncestr + "×tamp=" + timeStamp.ToString() + "&url=" + url;
string signature = Sha1(plainTex).ToLower();
// 必填,微应用ID
string agentId = agentId;
//必填,企业ID
string corpId = corpId;
obj.Value = new { timeStamp, noncestr, signature, ticket, agentId, corpId };
obj.StatusCode = 200;
}
return Ok(obj);
}
public class ReqConfig
{
public string GetUrl { get; set; }
public string Ticket { get; set; }
}
前端代码
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<script src="./vconsole.min.js"></script>
<script type="text/javascript">
var vConsole = new VConsole();
</script>
<script src="https://g.alicdn.com/dingding/dingtalk-jsapi/3.0.25/dingtalk.open.js"></script>
</head>
<body>
<div> 测试钉钉鉴权</div>
<div id="msg" style="width: 100%; overflow: auto;"></div>
<div id="url" style="width: 100%; overflow: auto;"></div>
<div id="start" style="width: 100%; height: 100px; border: 1px #ddd solid;">录音</div>
<div id="end" style="width: 100%; height: 100px; border: 1px #ddd solid; margin-top: 40px;">停止录音</div>
<div id="content"></div>
<script type="text/javascript">
var xmlhttp;
if (window.XMLHttpRequest)
{
// IE7+, Firefox, Chrome, Opera, Safari 浏览器执行代码
xmlhttp=new XMLHttpRequest();
}
else
{
// IE6, IE5 浏览器执行代码
xmlhttp=new ActiveXObject("Microsoft.XMLHTTP");
}
xmlhttp.onreadystatechange=function()
{
if (xmlhttp.readyState==4 && xmlhttp.status==200)
{
var res = JSON.parse(xmlhttp.responseText).value;
document.getElementById("msg").innerHTML = res.timeStamp;
dd.config({
agentId: res.agentId, // 必填,微应用ID
corpId: res.corpId,//必填,企业ID
timeStamp: res.timeStamp, // 必填,生成签名的时间戳
nonceStr: res.noncestr, // 必填,自定义固定字符串。
signature: res.signature, // 必填,签名
// type:0/1, //选填。0表示微应用的jsapi,1表示服务窗的jsapi;不填默认为0。该参数从dingtalk.js的0.8.3版本开始支持
jsApiList : [
// 'biz.contact.choose',
// 开始录音
'device.audio.startRecord',
// 停止录音
'device.audio.stopRecord',
// 60 秒录音停止监听
'device.audio.onRecordEnd',
// 播放录音
'device.audio.play',
// 语音转文字
'device.audio.translateVoice',
] // 必填,需要使用的jsapi列表,注意:不要带dd。
});
dd.error(function (err) {
alert('dd error: ' + JSON.stringify(err));
})//该方法必须带上,用来捕获鉴权出现的异常信息,否则不方便排查出现的问题
dd.ready(function() {
console.log('dd ready')
document.getElementById("start").addEventListener("click", function () {
console.log("start")
dd.device.audio.startRecord({
onSuccess : function () {//支持最长为300秒(包括)的音频录制,默认60秒(包括)。
console.log("success")
},
onFail : function (err) {
console.log("err")
}
});
})
document.getElementById("end").addEventListener("click", function () {
console.log("end")
dd.device.audio.stopRecord({
onSuccess : function(res){
// res.mediaId; // 返回音频的MediaID,可用于本地播放和音频下载
// res.duration; // 返回音频的时长,单位:秒
console.log(res.mediaId);
console.log(res.duration);
dd.device.audio.play({
localAudioId : res.mediaId,
onSuccess : function () {
console.log("播放")
},
onFail : function (err) {
}
});
dd.device.audio.translateVoice({
mediaId : res.mediaId,
duration : 5.0,
onSuccess : function (res) {
// res.mediaId; // 转换的语音的mediaId
// res.content; // 语音转换的文字内容
document.getElementById("content").innerHTML = res.content;
}
});
},
onFail : function (err) {
console.log(err)
}
});
})
});
}else{
document.getElementById("msg").innerHTML="请求失败";
}
}
xmlhttp.open("POST","后端地址",true);
// 添加请求头
xmlhttp.setRequestHeader('Content-Type', 'application/json');
// 按需要添加网站认证
xmlhttp.setRequestHeader('Authorization', 'Bearer xxx');
var data = JSON.stringify({
// ios 需要解码 decodeURIComponent(location.href)
getUrl: location.href,
});
xmlhttp.send(data);
</script>
</body>
</html>