钉钉扫码登录踩坑

1,896 阅读3分钟

由于业务需求,因此需要对接钉钉的扫码登录功能,具体就是这边有一个旧系统,但是需要支持用户扫码登录,并且基于用户的身份(教师/学生, 或者 职员/管理者) 跳转到不同的页面, 这不是 so-easy 嘛. 然后就踩坑了

1. 文档有两个版本

懂得使用搜索引擎的人不难搜索到钉钉的开发文档,并且也不难看出 文档有两份,
旧文档的地址
新的文档的地址

那么问题来了,按那个文档来写呢? 答案是按旧的来写,别问,问就是后端是按旧的来写的 . 其实大概看一眼文档,就知道这两者大同小异,但是 前后端需要协调一致,按照同一份文档来写.

2.钉钉开发者管理后台也有新旧之分

钉钉的开发者后台也不难找,
新版管理后台
旧版管理后台
但是这里有个隐藏的坑是 这个管理界面有两个版本新版和旧版, 并且按我的实际使用体验来看,这两者 数据并不互通, 换句话说 旧版 维护的数据 和 新版 维护的数据是两份数据, 也不知是哪位高人?
我用的是旧版

3. 大概思路

期望的页面流程如下所示:

  1. 用户打开我们的登录页面,然后点一个钉钉登录的按钮,这时页面显示一个二维码
  2. 用户掏出手机扫码,然后选择确认登录,系统 loading,判断用户身份,然后跳转到指定页面 还是so-easy

结合文档 ( 按照 旧版文档 方式二 ),这部分的具体思路就是,

  1. 首先用户打开我们的登录页/login这个地址,
  2. 点击钉钉登录 按钮, 页面此时显示钉钉二维码

4. 具体实现 (按照 旧版文档 方式二来实现)

注意目前是在登录页面,也就是一般的/login页面

  1. 如何显示二维码? 首先html 文件中引入 js
  <script src="https://g.alicdn.com/dingding/dinglogin/0.0.5/ddLogin.js"></script>

以及在 template 中增加相应 dom 节点 ,

<div id="login_container" class="self-defined-classname"></div>

以及加一个 按钮,点击该按钮之后, 二维码出现,该按钮的事件处理程序如下: 这里面的代码非常重要,也写了很多注释来解释

handleLogin(){
       // 这是  钉钉开发者后台创建的应用中的appId , 并且我是用的旧版的 开发者后台
       const appId = "dingxxxxxxxxxxxxxx";
       // 这个地址需要注意,要和 钉钉开发者后台创建的应用中的回调域名保持一致, 这个地址在开发时可以这么写
       const callbackUrl = "http://127.0.0.1:8080/#/loginRedirect";  
       var url = encodeURIComponent(callbackUrl);
       // 这么拼接是没问题的,网上众说纷坛,实践上这么写没问题 
       let goto = encodeURIComponent(
         `https://oapi.dingtalk.com/connect/oauth2/sns_authorize?appid=${appId}&response_type=code&scope=snsapi_login&state=STATE&redirect_uri=${url}`
       );
       // 这里可以选择给 dingObj 赋值,不赋值应该也可以, 如果报错DDLogin未定义,使用 window.DDLogin
       this.dingObj = DDLogin({
         id: "login_container", // 和上面的 dom 的 id 保持一致
         goto: goto, 
         style: "border:none;background-color:#FFFFFF;",
         width: "365",
         height: "400",
       });
       // 这个回调函数中可以写我们自己的一些处理逻辑,如何处理  loginTmpCode  
       var handleMessage = function (event) {
         var origin = event.origin;
         if (origin == "https://login.dingtalk.com") {  //判断是否来自ddLogin扫码事件。
           var loginTmpCode = event.data;  //获取到loginTmpCode后就可以在这里构造跳转链接进行跳转了
           // 这个 href 地址不能写错了,不然可能无法 302 重定向
           console.log("loginTmpCode", loginTmpCode);
           let href = `https://oapi.dingtalk.com/connect/oauth2/sns_authorize?appid=${appId}&response_type=code&scope=snsapi_login&state=STATE&redirect_uri=${url}&loginTmpCode=${loginTmpCode}`;
           // 这里就是修改当前页面的 url,改成拼接而成的这个 url 的地址
           // 执行之后,页面会302 重定向到 callbackUrl 这个地址,并且后面带有?code=24llfksdjflksjlfjs 这个参数
           // 因此需要在前端页面中另外写一个页面,路由和callbackUrl一致,在这个页面中处理后续的逻辑,见下方
           window.location.href = href;
         }
       };
       if (typeof window.addEventListener != "undefined") {
         window.addEventListener("message", handleMessage, false);
       } else if (typeof window.attachEvent != "undefined") {
         window.attachEvent("onmessage", handleMessage);
       }
}

callbackUrl 对应的前端页面中的逻辑

这个页面由 /login 跳转而来,并且这页面的url 类似 callbackUrl + ?code=xxxxxxxxxxxxxxx&state=XXXX , 即 http://127.0.0.1:8080/#/loginRedirect?code=xxxxxxxx&state=XXXX 在这个页面中,则写一些我们自己的业务逻辑,例如根据身份跳转到不同页面,等等,大概例子如下

mounted() {
  const { code } = this.$route.query;
  console.log(44, code, this.$route);
  if (code) {
    //登录接口
    dingdingLogin({ code: code })
        .then((res) => {
          const { userName: username, realPassword: password } = res.data;
          const data = {
            password,
            username,
            flag: "0", // 1 是正常登录, 0 是钉钉登录
            code: "11", //验证码 此时随便写
          };
          this.$store
              .dispatch("Login", data)
              .then((data) => {
                this.loading = false;
                this.$router.push({ path: "/" }).catch(() => {});
              })
              .catch((err) => {});
        })
        .catch((err) => {});
  }
},

appid 以及 回调域名在哪儿配置?

image.png

这个图里的回调域名应该是 http://127.0.0.1:8080/#/loginRedirect, 不过写错了 似乎也能用,我迷茫了

总结

总结一下就是 这个东西有坑啊,裂开,供大家参考,官方文档我感觉也未必准确,许多细节,文字不好讲清楚,还是要看代码