【Electron-vue】构建桌面应用(28)- 客户端对接钉钉扫码登录

1.前言

由于公司的工作模式都是使用的钉钉软件,所以当客户端开发完之后,就需要在内部使用,边用边测试。所以为了支持钉钉扫码登录,需要下一番功夫研究一下。

钉钉参考文档

2.钉钉扫码登录

2.1 界面设计

考虑到需要支持钉钉,以后还可能要支持企业微信…等,所以界面设计就需要考虑一下,打算通过点击的方式进行页面跳转。见下图,在最下方添加钉钉的链接,以后支持其他第三方登陆,可能就是排成一排的形式了。
【Electron-vue】构建桌面应用(28)- 客户端对接钉钉扫码登录_第1张图片
钉钉扫码页面,考虑到用户可以取消钉钉登陆方式,提供返回操作,返回到主登陆页面,使用账户/密码登陆。
【Electron-vue】构建桌面应用(28)- 客户端对接钉钉扫码登录_第2张图片
整体的界面就是这样的,那么下面我们来看一下代码。

2.2 准备工作

对接钉钉前,需要注册团队开发账户,然后创建扫码登陆应用授权
https://open-dev.dingtalk.com/#/loginMan
登陆账户后在应用开发->登陆模块中创建扫码登陆应用授权码
【Electron-vue】构建桌面应用(28)- 客户端对接钉钉扫码登录_第3张图片
官方介绍的都比较详细,需要注意的就是回调域名。这个会贯穿你的整个扫码的开发过程。

我们知道一般的web端开发都是在网页应用,基本上都是在http/https下开发的。它指向了你的页面访问路径比如http://localhost:8080/login等。

配置完成之后,会自动生成appid和appsecret

【Electron-vue】构建桌面应用(28)- 客户端对接钉钉扫码登录_第4张图片

2.3 开发钉钉扫码登陆

上面的配置完成之后,我们要开始真正的代码coding了。

官方的文档很写的很清楚,我就不详细讲解代码了。下面是我的扫码登陆的代码。

其中,http://localhost:9080/就是我的回调域名,为什么设置为这个呢?是因为在开发环境下,通过npm start或者其他命令启动服务,会给我们启动一个服务和监听的端口号,我的服务和端口号如下:

i 「wds」: Project is running at http://localhost:9080/
i 「wds」: webpack output is served from undefined
i 「wds」: Content not from webpack is served from D:\my-project\

所以这里的回调域名配置为我本地起的服务,那么在钉钉重定向的时候就能够找到这个域名,重定向到我的页面。如果你的回调域名不是真实存在的,那么钉钉没法进行重定向,导致你无法获取到钉钉给你返回的code值。没有code值,你就没法进行下去。

<template>
  <div id="dd_container"></div>
</template>

<script lang="ts">
export default {
  name: "scan",
  mounted() {
    var url = "http://localhost:9080/";
    let goto = encodeURIComponent(
      "https://oapi.dingtalk.com/connect/oauth2/sns_authorize?appid=dingoasnoyum0xfxbamoom&response_type=code&scope=snsapi_login&state=STATE&redirect_uri=" +
        url
    );

    // 创建钉钉对象
    var obj = DDLogin({
      id: "dd_container", //这里需要你在自己的页面定义一个HTML标签并设置id,例如
goto: goto, //请参考注释里的方式 style: "border:none;background-color:#ffffff;", width: "250", height: "290", }); var handleMessage = function (event) { var origin = event.origin; console.log("origin", event.origin); if (origin == "https://login.dingtalk.com") { //判断是否来自ddLogin扫码事件。 var loginTmpCode = event.data; //获取到loginTmpCode后就可以在这里构造跳转链接进行跳转了 console.log("loginTmpCode", loginTmpCode); window.location.href = "https://oapi.dingtalk.com/connect/oauth2/sns_authorize?appid=dingoasnoyum0xfxbamoom&response_type=code&scope=snsapi_login&state=STATE&redirect_uri=http://localhost:9080/&loginTmpCode=" + loginTmpCode; } }; if (typeof window.addEventListener != "undefined") { window.addEventListener("message", handleMessage, false); } else if (typeof window.attachEvent != "undefined") { window.attachEvent("onmessage", handleMessage); } }, }; </script> <style scoped> #dd_container{ text-align: center; } </style>

上面的代码scan扫码的代码,是在登录页点击钉钉扫码才显示出来的。你可以在登录页定义一个标志位,默认false即不显示扫码,点击钉钉扫码后,置为true显示扫码,隐藏登录页。做过前端的都能理解,我就不多说了。

配置之后,你启动应用,因为在electron-vue中使用的npm run dev启动服务。当你点击时,就会出现二维码。

在开发的时候可能会出现innerhtml为空或者undefined的情况,那是因为你的二维码容器dd_container还没被渲染出来,你的js代码就执行了,导致找不到二维码要渲染的容器,会报这个错误。

当你扫码,手机端确认钉钉授权登陆后,钉钉通过授权就会重定向到你配置的域名回调中,也就是前面说的http://localhost:9080,然后将codeSTATE拼接到你的回调域名中,你通过window.location.href就能获取到这个路径,然后经过一系列的处理,就能拿到code值。

 window.location.href =
          "https://oapi.dingtalk.com/connect/oauth2/sns_authorize?appid=dingoasnoyum0xfxbamoom&response_type=code&scope=snsapi_login&state=STATE&redirect_uri=http://localhost:9080/&loginTmpCode=" +
          loginTmpCode;

下面是在开发环境中启动程序时,服务和端口
【Electron-vue】构建桌面应用(28)- 客户端对接钉钉扫码登录_第5张图片
扫码后,我们看一下,下面这张图是我js端发起的链接跳转
在这里插入图片描述
我们可以看到,在钉钉那边授权通过后,会重定向到我配置的域名回调中,还多了两个参数codeSTATE
【Electron-vue】构建桌面应用(28)- 客户端对接钉钉扫码登录_第6张图片
由于重定向到我的登录页,就相当于登录页main重新渲染了一次,为了方便测试我就直接在vue的生命周期函数created中获取了window.location.href,然后进行一系列的分解操作,获取到code,将code发给服务端,服务端拿到code去钉钉做用户查询,如果能查到用户则成功,否则失败。

看到这里,你可能觉得很顺利。多么的自然…

如果这么简单就实现了,那我写这篇博客就没有什么意思了。

2.4 客户端扫码登陆

因为我们的应用时客户端,在真实的生产环境和开发环境中千差万别。当我满怀期待的将项目打包安装之后,整个人就呆住了,当我扫码后,一片空白…就跟我当时的脑袋一样。

冷静下来之后,加了打印日志,在一次打包查看一下,恍然大悟。
在这里插入图片描述
看到这,就或许就该想到了。客户端使一堆的静态文件,哪来的服务端IP和端口之类的。而且在代码中也可以看到,下面是main.js中的代码。

if (process.env.NODE_ENV !== 'development') {
  global.__static = path.join(__dirname, '/static').replace(/\\/g, '\\\\')
}


const winURL = process.env.NODE_ENV === 'development'
  ? `http://localhost:9080`
  : `file://${__dirname}/index.html`

从代码中可以看出来,当你在开发环境时,指向的是http://localhost:9080,而在生产环境中指向的就是你的本地的安装目录file://${__dirname}/index.html对应着file:///D:/exe-soft/%E8%94%B7%E8%96%87%E7%81%B5%E5%8A%A8/resources/app.asar/dist/electron/index.html#/

WR,这样一来回调域名怎么能匹配的上,匹配不上回调域名,是没法进行重定向的。也就没法拿到对应的code,那下面的流程全走不下去了。

于是乎我就抱着侥幸的心理去钉钉论坛和electron论坛,希望能找到些许曙光。但现实是残酷的,很遗憾的一无所获。

于是我就恬不知耻的给钉钉官方提了个工单,不得不说大公司就是好,回复也是超级的快。
【Electron-vue】构建桌面应用(28)- 客户端对接钉钉扫码登录_第7张图片
大体上的意思是:

我:请问,如果在客户端开发,没有所谓的回调域名,或者说没法配置回调域名我该怎么搞。
钉钉:不好意思,如果没有回调域名,那么后面的一些列的认证流程就走不通,就不支持。
我:那你们有没有支持以函数调用的形式去支持返回code码,因为在整个认证流程中我只关注code码,也只有code码是有用的。比如使用某个函数callback(“xxxxxxxxxx”),这个函数是钉钉定义的,然后又返回值,返回之中带着code值的方式,这种方式能够适应不同的场景。
钉钉:目前不支持,需要做一些…

好吧,看来想让钉钉做出让步去支持不太现实。既然用到被人的东西,就应该遵循别人的设计规则。

从昨天想到现在有这么几种思路:

  1. Electron中使用iframe去内嵌一个服务端页面,并且将回调域名指向一个在服务端存在的页面,这样回调域名就能生效了。
  2. 回调域名指向后端服务器
  3. 既然在开发环境支持,开发环境下是通过ndoejs启动了一个http服务和端口号,那么在生产环境中是不是也可以通过nodejs启动一个服务然后去监听9080这个端口,那么只要这个服务存在,重定向的时候就能找到地址,就能获取到code值。

先说说前两种方式,大同小异。难点都在如果你回调域名配置了服务端地址,那么会往服务端请求跳转,服务端需要指向一个页面,这个页面就必须托管在服务器上。否则就会显示一片空白。说白了也就是你需要在服务器上一套前端代码,才能正常显示。但是一旦你托管到服务器上,那么本地的代码实际上就不可控了。所以的操作都将会是操作服务器上的代码。那么electron客户端便不可控。这中间可能还会牵扯到跨域的问题。

第三种方式的实现原理就是:在程序启动时,去创建监听一个9080的端口,这个服务是和我的客户端同时运行的,而且可以在mian.js中执行的,属于代码可控范围。当重定向发生时,我就能监听到这个服务和端口,那么就能拿到所对应的请求连接和参数信息。然后在main.js中通过ipc的方式通信,将数据传递给渲染进程vue,vue监听到主进程发过来的事件,便可以进行处理,将地址,端口号以及获取的code值发给服务端。服务端拿到这些信息区做认证。返回认证结果,根据结果进行跳转到主页面/登录页

随之而来的就是另外一个问题:你怎么确定你的服务一定能拿到你所配置的端口呢?要是被占用了,相当于整个程序也就奔溃了。

2.5 使用nodejs启动服务

先来尝试一下,使用nodejs创建一个服务监听。

main/index.js主进程中

import express from 'express'

// 创建一个新的服务监听
appListener.get('/', function (req, res) {
  log.info("req.query", req.query)
  mainWindow.webContents.send('ddscan-login', req.query)
});
appListener.listen(9080)

ipcMain.on("ddCode-result", (event, args) => {
  log.info("钉钉扫码的结果=", args)
  event.sender.send("jumptosystem","测试成功了")
})

在渲染进程中:

// 监听扫码结果
    this.$electron.ipcRenderer.once("ddscan-login", (event, arg) => {
      this.$electron.ipcRenderer.send("ddCode-result", {
        address: this.loginVo.address,
        port: this.loginVo.port,
        code: arg.code,
      });
    });

打包后的输出结果,你猜我看到了什么…
在这里插入图片描述
这是我之前的打印
在这里插入图片描述
这样整个流程就通了,然后扫码可以成功后就可以在渲染进程中通过vue的路由功能进行跳转了。

2.6 其他实现方式

为了防止占用客户端(用户)的端口,所以决定不在前端进行跳转操作。

前端只负责拿到临时授权码,然后将临时授权码通过请求发给服务端,服务端负责后需要的操作,拿着临时授权码去认证扫码用户,并将扫码结果返回给前端,前端接受到认证结果,做进一步处理。

你可能感兴趣的:(Electron,electron客户端,钉钉扫码登陆)