钉钉实现第三方登录示例(重复回调问题解析)
钉钉作为专门为企业打造的沟通协助平台,包含的功能很多,考勤打卡,审批,日记,钉盘,钉邮等。基本满足了一些中小企业的大部分工作需求。因此对接钉钉的一些功能模块业务需求在开发中也是比较常见的。钉钉的开发文档很多,因为功能多再加上文档的迭代,新旧更替,有时候你想对接某个模块功能,没准连找个文档都要半天呢
1.准备工作
正所谓工欲善其事必先利其器,本博文主要对接的是钉钉授权第三方登录,因此先找到对应的官方文档,根据文档熟悉流程。
实现登录第三方网站:实现登录第三方网站 - 钉钉开放平台
步骤一和步骤二就不多加叙述了,完成后要拿到应用的Client ID,以及完成步骤四在应用的安全配置中完成重定向域名配置。
2.钉钉扫码授权登录
这里演示代码直接用单html吧,能否快速看到效果:
ddqrcode.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>钉钉扫码登录测试</title>
<script src="https://g.alicdn.com/dingding/h5-dingtalk-login/0.21.0/ddlogin.js"></script>
<script src="https://cdn.staticfile.net/jquery/1.10.2/jquery.min.js"></script>
</head>
<body>
<div class="form-container">
<h3 style="margin: 0 auto;text-align: center;margin-bottom: 30px;">钉钉扫码登录演示</h3>
<div id="self_defined_element" class="self-defined-classname"></div>
</div>
</body>
</html>
<script>
// STEP3:在需要的时候,调用 window.DTFrameLogin 方法构造登录二维码,并处理登录成功或失败的回调。
window.DTFrameLogin(
{
id: 'self_defined_element',
width: 300,
height: 300,
},
{
redirect_uri: encodeURIComponent('http://你的回调地址'),
client_id: 'ding97xxxxxx',
scope: 'openid',
response_type: 'code',
state: 'dream',
prompt: 'consent',
},
(loginResult) => {
const {redirectUrl, authCode, state} = loginResult;
console.log('loginResult:', loginResult);
$.ajax({
url:`http://你的回调地址?authCode=${authCode}&state=${state}`,
method:'get',
contentType:'application/json',
dataType:'json',
success:function(data) {
if(data.code == 0) {
console.log("登录成功:",data);
}else {
alert(data.msg);
return;
}
},
error: function(err){
alert(err);
}
});
},
(errorMsg) => {
// 这里一般需要展示登录失败的具体原因,可以使用toast等轻提示
console.error(`errorMsg of errorCbk: ${errorMsg}`);
},
);
</script>
<style>
body {
font-family: 'Arial', sans-serif;
background-color: #f4f4f4;
display: flex;
justify-content: center;
align-items: center;
height: 100vh;
margin: 0;
}
.self-defined-classname {
width: 300px;
height: 300px;
}
</style>
这里引入了钉钉sdk ddlogin.js,直接无脑调用,填写对应的回调地址和client_id就行了,回调后拿到authCode请求到后端登录接口。这里可能有人产生疑问了?不是还有步骤3内网穿透吗?
好消息是:调用钉钉扫码授权登录第三方网站,支持内网情况下的回调,不用考虑什么内网穿透。
坏消息是:构建授权链接实现授权登录需要内网穿透,也就是必须外网能访问。
3.构造授权链接登录
钉钉的授权登录第三方网站的方式有两种,上面的那种属于调用钉钉sdk生成内嵌二维码,用钉钉app扫码后实现登录,扫完后二维码就失效了。适合作为登录用。
但是还有一种情况,假如需求是利用钉钉扫码授权实现第三方网站的签到呢,同时多人扫同一个二维码的情况。
这里就要用到第二种,通过构造授权链接。因为授权链接构造后就是一直有效的,我们可以将授权链接转化为二维码,提供给用户扫码,用户用钉钉app扫码后就相当于打开了授权链接,会触发钉钉的内部授权,点击授权后会直接重定向回调到我们配置的页面。因为是钉钉内打开的页面,所以回调的地址一定要是线上的。这也是为什么要内网穿透的原因。当然,如果我们有一个线上的域名及站点,也可以实现测试。
比如,lz构造了如下登录链接:
线上回调地址是:https://liuqingwushui.top/qrcodeauth.html (别忘了钉钉应用配置一下)
https://login.dingtalk.com/oauth2/auth?redirect_uri=https%3A%2F%2Fliuqingwushui.top%2Fqrcodeauth.html&response_type=code&client_id=ding97xxxxx&scope=openid&state=dream&prompt=consent
lz将这个路径手动转化为二维码供用户扫码。然后再写一个qrcodeauth.html 页面来接收回调的参数。
qrcodeauth.html回调页面接收参数并打印:
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>登录回调显示页面</title>
<script src="https://cdn.staticfile.net/jquery/1.10.2/jquery.min.js"></script>
</head>
<body>
<div id="params-output">
<!-- 路径参数将在这里显示 -->
</div>
</body>
<script>
// 获取当前URL的路径部分
const path = window.location.pathname;
const searchParams = new URLSearchParams(window.location.search);
// 解析路径参数
const params = {};
searchParams.forEach((value, key) => {
params[key] = value;
});
// 将参数转换为字符串
const paramsString = JSON.stringify(params, null, 2);
// 使用jQuery将参数输出到HTML中
$('#params-output').html('<pre>' + paramsString + '</pre>');
</script>
</html>
不通的用户扫码授权回调的authCode,我们在这个页面做接收处理即可,为了区别扫码来源,可以利用state做动态参数区分。如此,我们就实现了一个二维码供多个钉钉用户扫码实现签到的功能了。
4.扫码重复调用回调函数问题
在vue中调用钉钉登录扫码,会发现重复调用的问题,然后就出现401错误,提示失效。因为前面说了,每个二维码只能扫一次。这种情况是因为钉钉官方sdk中window.addEventListener 监听iframe 触发多次导致的,经过测试对比,发现直接改ddlogin.js 源码是最有效的。
将源码下载下来本地引用,然后更改:
//源代码块
window.addEventListener("message", (function(e) {
var t = e.data,
i = e.origin;
if (/login\.dingtalk\.com/.test(i) && t)
if (t.success && t.redirectUrl) {
var u = t.redirectUrl,
c = r(u, "authCode") || "",
d = r(u, "state") || "",
s = r(u, "error") || "";
c ? n && n({
redirectUrl: u,
authCode: c,
state: d
}) : o && o(s)
} else o && o(t.errorMsg)
}))) : o && o("Browser not support")) : o && o("Element not found") : o && o("Missing parameters")
//改完后的代码块
window.onmessage = function(e) {
var t = e.data,
i = e.origin;
if (/login\.dingtalk\.com/.test(i) && t)
if (t.success && t.redirectUrl) {
var u = t.redirectUrl,
c = r(u, "authCode") || "",
d = r(u, "state") || "",
s = r(u, "error") || "";
c ? n && n({
redirectUrl: u,
authCode: c,
state: d
}) : o && o(s)
} else o && o(t.errorMsg)
}) : o && o("Browser not support")) : o && o("Element not found") : o && o("Missing parameters")
实际上就是将window.addEventListener监听改成window.onmessage。
4.小结
总而言之,言而总之。在对接一些第三方时,核心要点就是去熟悉理解他们的开发文档,但是谁也不知道明天你要对接的第三方会是谁?这也是lz的博文对于技术这块都喜欢力求细致,通俗易懂,能达到上手即看效果,下手即能用的程度。