钉钉第三方扫码登陆nodeJs+vue

准备工作

  • 你需要一个自己的团队,或则某个团队你有管理权限,否则注册个吧,注册钉钉的账号,以获取APPID;https://oa.dingtalk.com/register.html),

  • 获取AAPID

    • 使用旧版本的页面钉钉第三方扫码登陆nodeJs+vue_第1张图片

    • 移动接入应用 → 登陆 → 创建扫码登陆应用授权
      钉钉第三方扫码登陆nodeJs+vue_第2张图片

    • 确认后
      钉钉第三方扫码登陆nodeJs+vue_第3张图片

前端代码相关

官方文档地址:https://developers.dingtalk.com/document/app/scan-qr-code-to-log-on-to-third-party-websites?spm=a2q3p.21071111.0.0.18711cfa4kzCXi

APPID:为上图创建的appld

REDIRECT_URI:为回调域名
随便建一个index.htm文件(下面有vue工程实践完整代码,以及nodeJs接口的实现)

DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Documenttitle>
    <script src="http://g.alicdn.com/dingding/dinglogin/0.0.5/ddLogin.js">script>
head>

<body>
    <div id="login_container">div>

    <script>
        let APPID = "dingoarrgitdv8zv6z9l4r"
        // let REDIRECT_URI = encodeURIComponent("http://127.0.0.1:8080/")
        let REDIRECT_URI = "http://127.0.0.1:8080/"
        let goto = `https://oapi.dingtalk.com/connect/oauth2/sns_authorize?appid=${APPID}&response_type=code&scope=snsapi_login&state=STATE&redirect_uri=${REDIRECT_URI}`
        console.log('goto',goto)
        var obj = DDLogin({
            id: "login_container",
            goto: encodeURIComponent(goto),
            style: "border:none;background-color:#FFFFFF;",
            width: "365",
            height: "400"
        });
        var hanndleMessage = 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 = goto + '&loginTmpCode='+loginTmpCode
            }
        };
        if (typeof window.addEventListener != 'undefined') {
            window.addEventListener('message', hanndleMessage, false);
        } else if (typeof window.attachEvent != 'undefined') {
            window.attachEvent('onmessage', hanndleMessage);
        }
    script>
body>

html>

postmen工具测试钉钉接口

  • 获取access_token(get访问)
https://oapi.dingtalk.com/sns/gettoken?appid=APPID&appsecret=APPSECRET

钉钉第三方扫码登陆nodeJs+vue_第4张图片
接口反馈:

{
    "errcode": 0,
    "access_token": "9c971e5a98839cda1fb33359bda8ade7",
    "errmsg": "ok"
}
  • 获取永久授权码(post访问)
https://oapi.dingtalk.com/sns/get_persistent_code?access_token=ACCESS_TOKEN
ACCESS_TOKEN ==> 9c971e5a98839cda1fb33359bda8ade7
↓
https://oapi.dingtalk.com/sns/get_persistent_code?access_token=9c971e5a98839cda1fb33359bda8ade7

post参数:

{
    "tmp_auth_code": "2d53880fa733d1218ca43301af99df60"
}

//2d53880fa733d1218ca43301af99df60为跳转后url中的code值

在这里插入图片描述
反馈结果

{
    "errcode": 0,
    "errmsg": "ok",
    "unionid": "Wplnhq6S5AiEiEl109aXgNmiPF9",
    "openid": "dHUTIdKEiEiu0s4nD1yWMz9iJQi",
    "persistent_code": "lABB9Jqul44p3rYf8fck2vWAmunia0ptigj8XEV82c2oYYCPxUNYD17CHR_UBkRv"
}
  • 获取用户授权的SNS_TOKEN。以post请求
https://oapi.dingtalk.com/sns/get_sns_token?access_token=ACCESS_TOKEN
#ACCESS_TOKEN跟上面一样

post参数:

{
    "openid": "dHUTIdKEiEiu0s4nD1yWMz9iJQi",
    "persistent_code": "lABB9Jqul44p3rYf8fck2vWAmunia0ptigj8XEV82c2oYYCPxUNYD17CHR_UBkRv
}

反馈结果

{
    "errcode": 0,
    "errmsg": "ok",
    "sns_token": "258a8bbe0c3f8e998e4771903b4d3bdf",
    "expires_in": 7200
}
  • 用户信息。以get请求
https://oapi.dingtalk.com/sns/getuserinfo?sns_token=SNS_TOKEN
#SNS_TOKEN 258a8bbe0c3f8e998e4771903b4d3bdf

反馈结果

{
    "errcode": 0,
    "errmsg": "ok",
    "user_info": {
        "nick": "李三",
        "unionid": "F9hgNplni6S5Al10miP9aXqEiEW",
        "dingId": "$:LWCP_v1:$XhiZhvS6S1Bl/J9A7JrVZuP8usS0/3pi",
        "openid": "dHUEiyWMz9TIdKEiiu0s4nD1JQi"
    }
}

nodeJs后台接口

简单搭建一个node服务器,调用钉钉接口返回信息
需要安装:

npm i express #node框架
npm i axios	#xhr
npm i cors #跨域
const express = require('express')
const axios = require('axios');
const app = express()
//解决跨域
const cors = require('cors');
app.use(cors())

const api = axios.create({
    baseURL: 'https://oapi.dingtalk.com/sns'
})

//钉钉接口
//获取access_token
const gettoken = params => {
    return api({
        url: '/gettoken',
        method: 'get',
        params
    });
};
//获取授权码
const get_persistent_code = ({ access_token, tmp_auth_code }) => {
    return api({
        url: '/get_persistent_code?access_token=' + access_token,
        method: 'post',
        data: {
            tmp_auth_code
        }
    });
};
//获取用户授权的SNS_TOKEN
const get_sns_token = ({ access_token, openid, persistent_code }) => {
    return api({
        url: '/get_sns_token?access_token=' + access_token,
        method: 'post',
        data: {
            openid,
            persistent_code
        }
    });
};
//用户信息
const getuserinfo = params => {
    return api({
        url: '/getuserinfo',
        method: 'get',
        params
    });
};

//变量声明
const appid = "dingoarrgitdv8zv6z9l4r"
const appsecret = "_7ru7LMqtWNvvAsXxKzGhQkvwqYB24PecCnjB0vZ_GY5Pt9YXtKpkDeVFZSC-eu0"

app.get('/', (req, res, next) => {
    const { tmp_auth_code } = req.query
    gettoken({ appid, appsecret })
        .then(response => {
            const { access_token } = response.data
            console.log('access_token:', access_token)
            return get_persistent_code({
                access_token,
                tmp_auth_code
            })
        })
        .then(response => {
            // console.log(response)
            const access_token = response.config.url.split('=')[1]
            const { openid, persistent_code } = response.data
            console.log('access_token:', access_token)
            console.log('openid:', openid)
            console.log('persistent_code:', persistent_code)
            return get_sns_token({ openid, persistent_code, access_token })
        })
        .then(response => {
            const { sns_token } = response.data
            console.log('sns_token:', sns_token)
            return getuserinfo({ sns_token })
        })
        .then(response => {
            console.log(response.data)
            // const { nick, unionid, dingId, openid } = response.data
            res.send(response.data.user_info)
        })
        .catch(error => {
            console.log(error)
        })
})
app.listen(3000, () => {
    console.log('3000端口服务器启动成功')
})
#启动服务器
node app.js

简单搭建vue工程实践

vue-cli简单搭建一个vue工程,vue-cli自己看下文档创建
将上面的index.html整合到工程里
安装:

npm i axios

vuecli2直接在vue.config.js添加如下内容即可,vueCli3 webpack配置信息都被隐藏了,所以在src同级目录下创建vue.config.js内容如下:
配置cdn引入

module.exports = {
    
    configureWebpack: {
        externals: {
            'DDLogin': 'DDLogin',
        }
    },
};

public文件下的index.html需要引入钉钉的DDLogin方法

DOCTYPE html>
<html lang="">
  <head>
    <meta charset="utf-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width,initial-scale=1.0">
    <link rel="icon" href="<%= BASE_URL %>favicon.ico">
    <title><%= htmlWebpackPlugin.options.title %>title>
  head>
  <body>
    <noscript>
      <strong>We're sorry but <%= htmlWebpackPlugin.options.title %> doesn't work properly without JavaScript enabled. Please enable it to continue.strong>
    noscript>
    <div id="app">div>
    
    <script src="https://g.alicdn.com/dingding/dinglogin/0.0.5/ddLogin.js">script>
  body>
html>

在vue工程中的app.vue文件内容如下

<template>
  <div id="app">
    <div id="login_container">div>
    <div  v-if="JSON.stringify(res) != '{}'">
      <div v-if ="res.errcode == undefined">
        dingId:{{ res.nick }}
        <hr />
        nick:{{ res.dingId }}
        <hr />
        openid:{{ res.openid }}
        <hr />
        unionid:{{ res.unionid }}
        <hr />
      div>
      <div v-else>
        errcode:{{ res.errcode }}
        <hr />
        error:{{ res.errmsg }}
        <hr />
      div>
    div>
  div>
template>

<script>
import axios from "axios";
// import HelloWorld from './components/HelloWorld.vue'
const api = axios.create({
  baseURL: "http://127.0.0.1:3000/",
});

const getUserInfo = (params) => {
  return api({
    url: "/",
    method: "get",
    params,
  });
};

export default {
  name: "App",
  data() {
    return {
      res: {},
    };
  },
  mounted() {
    this.loginInto();
  },
  methods: {
    loginInto() {
      /**
       * 如果初次开启该页面的话,url参数没有code的值,因此此次进入页面为扫码
       * 当扫码成功,再次登陆该页面的时候,url会携带code参数,此次为扫码登陆后,则展示个人信息
       */
      //判断路径中是否含有code
      const hrefIndex = window.location.href.indexOf("code=");
      if (hrefIndex === -1) {
        //首次进入页面,扫码
        //扫码相关
        let APPID = "dingoarrgitdv8zv6z9l4r";
        // let REDIRECT_URI = encodeURIComponent("http://127.0.0.1:8080/")
        let REDIRECT_URI = "http://127.0.0.1:8080/";
        // REDIRECT_URI = window.location.href
        let goto = `https://oapi.dingtalk.com/connect/oauth2/sns_authorize?appid=${APPID}&response_type=code&scope=snsapi_login&state=STATE&redirect_uri=${REDIRECT_URI}`;
        console.log("goto", goto);
        window.DDLogin({
          id: "login_container",
          goto: encodeURIComponent(goto),
          style: "border:none;background-color:#FFFFFF;",
          width: "365",
          height: "400",
        });
        var hanndleMessage = 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 = goto + "&loginTmpCode=" + loginTmpCode;
          }
        };
        if (typeof window.addEventListener != "undefined") {
          window.addEventListener("message", hanndleMessage, false);
        } else if (typeof window.attachEvent != "undefined") {
          window.attachEvent("onmessage", hanndleMessage);
        }
      } else {
        //再次进入该页面,调用钉钉接口展示个人信息
        //获取路径中code参数
        // console.log('window.location.search',window.location.search)
        const tmp_auth_code = window.location.search
          .split("&")[0]
          .split("=")[1];
        // console.log('tmp_auth_code',tmp_auth_code)
        //调用接口
        getUserInfo({ tmp_auth_code })
          .then((response) => {
            console.log("response", response);
            // this.res = response.data;
            const { errcode, user_info } = response.data;
            if (errcode == 0) {
              this.res = user_info;
            } else {
              this.res = response.data;
            }
          })
          .catch((error) => {
            this.res = error;
          });
      }
    },
  },
};
script>

<style  scoped>
.heng {
  color: red;
}
style>

关于官方文档直接使用DDLogin({}),而我的使用window.DDLogin({}),是因为汇报这个错误:
钉钉第三方扫码登陆nodeJs+vue_第5张图片
解决思路是:我浏览器看了https://g.alicdn.com/dingding/dinglogin/0.0.5/ddLogin.js下的代码:

!function (window, document) {
    function d(a) {
        var e, c = document.createElement("iframe"),
            d = "https://login.dingtalk.com/login/qrcode.htm?goto=" + a.goto ;
        d += a.style ? "&style=" + encodeURIComponent(a.style) : "",
            d += a.href ? "&href=" + a.href : "",
            c.src = d,
            c.frameBorder = "0",
            c.allowTransparency = "true",
            c.scrolling = "no",
            c.width =  a.width ? a.width + 'px' : "365px",
            c.height = a.height ? a.height + 'px' : "400px",
            e = document.getElementById(a.id),
            e.innerHTML = "",
            e.appendChild(c)
    }
    window.DDLogin = d
}(window, document);

window.DDLogin = dDDLogin被挂到了window上面了,所以。。。。。具体情况看你们自己了

实操结果

启动vue工程
钉钉第三方扫码登陆nodeJs+vue_第6张图片
手机钉钉扫码:
钉钉第三方扫码登陆nodeJs+vue_第7张图片

扫码后,页面调用接口,返回信息
钉钉第三方扫码登陆nodeJs+vue_第8张图片

当再次刷新页面时:
由于code时一次性的,所以会报错
钉钉第三方扫码登陆nodeJs+vue_第9张图片

扫码报错相关

"对不起 你无权限查看该页面 redirect_url不能为空"

原因:
 1. 只对redirect_url编码,而生成二维码时需要对整个gotoUrl进行编码
 2. appid参数值后有空格,编码后空格编码成"+"号

"对不起,你无权限查看该页面,redirect_url的域名不在appid安全域名内"

原因:
  1. 应用配置域名和二维码绘制时使用的redirect_url不是同一个域名
     如
         应用域名:  http://ji.wicp.vip/jsoa/dingdingScanLoginAction.do
         二维码域名:http://ji.wicp1.vip/jsoa/dingdingScanLoginAction.do     
  2. 相同域名,但是应用回调域名中的path和后台配置的path不一致时,不影响用户扫码登录
     如
         应用域名:  http://ji.wicp.vip/XXXX  
         二维码域名:http://ji.wicp.vip/js/dingdingScanLoginAction.do

用户登录扫码后,什么反应也没有,事件监听函数var hanndleMessage = function (event) {}没有捕获到事件

原因:
  1. state后面有空格或者redirect_url中有空格,此时IOS和安卓用户扫码后,可以点击登陆但是点击登录后无响应,页面接收不到回调

你可能感兴趣的:(钉钉相关,web)