JustAuth实现多个钉钉扫码登录

news2025/1/17 21:54:06

需求:

实现多个钉钉组织的用户绑定和扫码登录。

JustAuth框架实现钉钉扫码登录用到的dingTalk接口:

  • https://oapi.dingtalk.com/connect/qrconnect
  • https://oapi.dingtalk.com/connect/oauth2/sns_authorize
  • 根据sns临时授权码获取用户信息 https://oapi.dingtalk.com/sns/getuserinfo_bycode
  • 根据unionid获取用户userid https://oapi.dingtalk.com/topapi/user/getbyunionid
  • 根据userid获取用户详情 https://oapi.dingtalk.com/topapi/v2/user/get

实现原理:

    1. 加载多个钉钉组织的账号信息并缓存(appKey,appSecret,agentId);

可参考RuoYi的ruoyi-common-social模块的实现,用一个map存储多个钉钉的配置信息;

public class SocialProperties {

    /**
     * 是否启用
     */
    private Boolean enabled;

    /**
     * 授权类型
     */
    private Map<String, SocialLoginConfigProperties> type;
    /**
     * 前端外网访问地址
     */
    private String address;

}

扩展:

钉钉可做到页面维护,入库保存,服务启动时可从数据库加载至缓存,有修改时更新缓存,类似下图:

登录应用系统绑定钉钉:
    1. 钉钉扫码,二维码为“钉钉认证二维码”部分的内嵌二维码方式登录授权;
    2. 授权成功后钉钉回调回调地址并追加code和state;
    3. 拿到code后调用根据sns临时授权码获取用户信息;
    4. 保存openId或unionId和userId的关系;
扫码登录应用系统:
    1. 创建钉钉认证二维码见下文(钉钉认证二维码部分,2种方式都可以);
    2. 钉钉扫码后会追加code和state至回调地址;
    3. 拿到code后调用根据sns临时授权码获取用户信息;
    4. 比对用户信息的openId或unionId,匹配则登录成功跳转至应用系统首页,不匹配则跳转至登录页并做出失败提醒;

钉钉认证二维码:

使用钉钉提供的页面登录授权:
    1. 构造认证请求:
AuthRequest authRequest = new AuthDingTalkRequest(AuthConfig.builder()
                .clientId("Client ID")
                .clientSecret("Client Secret")
                .redirectUri("应用回调地址")
                .build());
    1. 生成授权地址:
String authorizeUrl = authRequest.authorize(AuthStateUtils.createState());
    1. 访问授权地址:

这个链接我们可以直接后台重定向跳转,也可以返回到前端后,前端控制跳转。前端控制的好处就是,可以将第三方的授权页嵌入到iframe中,适配网站设计。

    1. 效果图:

内嵌二维码方式登录授权:
    1. 页面中引入钉钉扫码登录JSSDK
<script src="https://g.alicdn.com/dingding/h5-dingtalk-login/0.21.0/ddlogin.js"></script>
    1. 在需要扫码登录的地方,调用如下方法:
<!-- STEP1:在HTML中添加包裹容器元素 -->
<div id="self_defined_element" class="self-defined-classname"></div>
<style>
    /* STEP2:指定这个包裹容器元素的CSS样式,尤其注意宽高的设置 */
    .self-defined-classname {
        width: 300px;
        height: 300px;
    }
</style>
<script>
    // STEP3:在需要的时候,调用 window.DTFrameLogin 方法构造登录二维码,并处理登录成功或失败的回调。
    window.DTFrameLogin(
        {
            id: 'self_defined_element',
            width: 300,
            height: 300,
        },
        {
            redirect_uri: encodeURIComponent('http://www.aaaaa.com/a/b/'),
            client_id: 'dingxxxxxxxxxxxx',
            scope: 'openid',
            response_type: 'code',
            state: 'xxxxxxxxx',
            prompt: 'consent',
        },
        (loginResult) => {
            const {redirectUrl, authCode, state} = loginResult;
            // 这里可以直接进行重定向
            window.location.href = redirectUrl;
            // 也可以在不跳转页面的情况下,使用code进行授权
            console.log(authCode);
        },
        (errorMsg) => {
            // 这里一般需要展示登录失败的具体原因,可以使用toast等轻提示
            console.error(`errorMsg of errorCbk: ${errorMsg}`);
        },
    );
</script>
    1. 参数说明((TypeScript语言描述)):
// ********************************************************************************
// window.DTFrameLogin方法定义
// ********************************************************************************
window.DTFrameLogin: (
  frameParams: IDTLoginFrameParams, // DOM包裹容器相关参数
  loginParams: IDTLoginLoginParams, // 统一登录参数
  successCbk: (result: IDTLoginSuccess) => void, // 登录成功后的回调函数
  errorCbk?: (errorMsg: string) => void,         // 登录失败后的回调函数
) => void;

// ********************************************************************************
// DOM包裹容器相关参数
// ********************************************************************************
// 注意!width与height参数只用于设置二维码iframe元素的尺寸,并不会影响包裹容器尺寸。
// 包裹容器的尺寸与样式需要接入方自己使用css设置
interface IDTLoginFrameParams {
  id: string;      // 必传,包裹容器元素ID,不带'#'
  width?: number;  // 选传,二维码iframe元素宽度,最小280,默认300
  height?: number; // 选传,二维码iframe元素高度,最小280,默认300
}

// ********************************************************************************
// 统一登录参数
// ********************************************************************************
// 参数意义与“拼接链接发起登录授权”的接入方式完全相同(缺少部分参数)
// 增加了isPre参数来设定运行环境
interface IDTLoginLoginParams {
  redirect_uri: string;     // 必传,注意url需要encode
  response_type: string;    // 必传,值固定为code
  client_id: string;        // 必传
  scope: string;            // 必传,如果值为openid+corpid,则下面的org_type和corpId参数必传,否则无法成功登录
  prompt: string;           // 必传,值为consent。
  state?: string;           // 选传
  org_type?: string;        // 选传,当scope值为openid+corpid时必传
  corpId?: string;          // 选传,当scope值为openid+corpid时必传
  exclusiveLogin?: string;  // 选传,如需生成专属组织专用二维码时,可指定为true,可以限制非组织帐号的扫码
  exclusiveCorpId?: string; // 选传,当exclusiveLogin为true时必传,指定专属组织的corpId
}

// ********************************************************************************
// 登录成功后返回的登录结果
// ********************************************************************************
interface IDTLoginSuccess {
  redirectUrl: string;   // 登录成功后的重定向地址,接入方可以直接使用该地址进行重定向
  authCode: string;      // 登录成功后获取到的authCode,接入方可直接进行认证,无需跳转页面
  state?: string;        // 登录成功后获取到的state
}
    1. 效果图:

前端生成授权页面,可以自己放置到任何需要的地方,相比jssdk生成的比钉钉提供的授权要漂亮很多,再也没有蓝底的加持。

附内嵌二维码的源码(Vue3):

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

<script setup name="dingTalkAuthQrcode">
import { ref, onMounted } from "vue";
import { generateState } from "@/api/system/social/auth";

const props = defineProps({
  appKey: {
    type: String
  },
  redirectUrl: {
    type: String
  },
  appCode: {
    type: String
  }
});
const emits = defineEmits(["callback"]);
// 假设你已经有了钉钉的 appKey 和 appSecret
const appKey = props.appKey || "your dingtalk appKey";
const redirectUrl = props.redirectUrl || '从配置文件中读取';
const stateCode = ref(null);
const handleMessage = async (event) => {
  var origin = event.origin;
  if (origin === "https://login.dingtalk.com") { //判断是否来自ddLogin扫码事件。
    const loginTmpCode = event.data;
    //获取到loginTmpCode后就可以在这里构造跳转链接进行跳转了,generateState()生成了state
    const res = await generateState();
    if (res && res.code === 200) {
      stateCode.value = res.data;
    }
    // 使用代理避免跨域请求,代理了/connect
    const dingTalkForwardUrl = `/connect/connect/oauth2/sns_authorize?appid=${appKey}&response_type=code&scope=snsapi_login&state=` + stateCode.value + `&redirect_uri=${redirectUrl}` + "&loginTmpCode=" + loginTmpCode;
    // 模拟请求网页跳转
    const response = await fetch(dingTalkForwardUrl);
    // 解析网页302成功后的code 和 state
    const urlParams = new URLSearchParams(new URL(response.url).search);
    const code = urlParams.get("code");
    const state = urlParams.get("state");
    if (code && state) {
      emits("callback", { "code": code, "state": state });
    }
  }
};
// 在组件挂载后调用生成二维码的函数
onMounted(() => {
  var obj = DDLogin({
    id: "login_container",
    goto: encodeURIComponent(`https://oapi.dingtalk.com/connect/oauth2/sns_authorize?appid=${appKey}&response_type=code&scope=snsapi_login&state=STATE&redirect_uri=${redirectUrl}`),
    style: "border:none;background-color:#FFFFFF;",
    width: "365",
    height: "400"
  });
  if (typeof window.addEventListener != "undefined") {
    window.addEventListener("message", handleMessage);
  }
});
onUnmounted(() => {
  window.removeEventListener("message", handleMessage)
});
</script>

总结:

  1. JustAuth框架实现钉钉授权登录是比较容易实现的,按照官方教程操作即可。本文介绍的是多个钉钉组织,实现思想等价于钉钉+企业微信+gitee这样;
  2. 使用临时码免登的方式再dingTalk开放平台已经被标记为过时,新的方法是使用tmpCode换取用户信息和现在的JustAuth是不兼容的,后续可期待下JustAuth是否做兼容。

参考文档:

  1. 钉钉扫码登录详细流程可参考JustAuth的官方文档:钉钉登录 | JustAuth
  2. 钉钉开放平台文档:扫码登录第三方网站 - 钉钉开放平台
  3. 钉钉开发平台新的身份验证(免登)文档:概述 - 钉钉开放平台

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1910810.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

websockt初始化,创建一个webSocket示例

写文思路&#xff1a; 以下主要从几个方面着手写websocket相关&#xff0c;包括以下&#xff1a;什么是webSocket&#xff0c;webSocket的优点和劣势&#xff0c;webSocket工作原理&#xff0c;webSocket握手示例&#xff0c;如何使用webSocket(使用webSocket的一个示例)&#…

进度条提示-在python程序中使用避免我误以为挂掉了

使用库tqdm 你还可以手写一点&#xff0c;反正只要是输出点什么东西都可以&#xff1b; Demo from chatgpt import time from tqdm import tqdm# 示例函数&#xff0c;模拟长时间运行的任务 def long_running_task():total_steps 100for step in tqdm(range(total_steps), …

平安养老险安徽分公司“7·8保险宣传”走进企业

近日&#xff0c;平安养老保险股份有限公司&#xff08;以下简称“平安养老险”&#xff09;安徽分公司以“78全国公众宣传日”“保险&#xff0c;让每一步前行更有底气”为主题&#xff0c;走进某大型企业开展消费者权益保护专题教育宣传活动。 本次活动旨在向企业员工普及金…

【AI技术的未来之路】从模型到应用,跨越超级应用陷阱,迈向个性化智能体

&#x1f493; 博客主页&#xff1a;倔强的石头的CSDN主页 &#x1f4dd;Gitee主页&#xff1a;倔强的石头的gitee主页 ⏩ 文章专栏&#xff1a;《热点时事》 期待您的关注 ​ 目录 引言 一、AI技术应用场景探索&#xff1a; 二、避免超级应用陷阱的策略&#xff1a; 三、个…

windows驱动开发基础-环境篇

前言 Windows上无论是用户模式下还是内核模式下&#xff0c;有关驱动的开发都有可能影响系统稳定性&#xff0c;所以我们首先要准备一个专用的测试环境&#xff0c;可以使用VM等虚拟机方便环境修复和还原 测试模式 开启测试模式&#xff1a;cmd 命令 bcdedit /set testsign…

Websocket 替代方案:如何使用 Firestore 监听实时事件

大家好,我是CodeQi! 一位热衷于技术分享的码仔。 ​在现代 Web 开发中,实时更新功能对于许多应用程序(如聊天应用、协作工具和在线游戏)都是必不可少的。虽然 WebSocket 是一种常用的实时通信技术,但 Google 的 Firestore 也提供了一种强大的替代方案,使得实时监听变得…

苍穹外卖前后端搭建

文章目录 参考开发环境搭建前端环境搭建1、 前端工程基于 nginx2、启动nginx,访问测试后端环境搭建1、从资料中找到后端初始工程:2、用 IDEA 打开初始工程,了解项目的整体结构:数据库环境搭建前后端联调nginx反向代理和负载均衡1、nginx反向代理2、nginx 负载均衡完善登录功…

线程交互现象

线程交互现象 小明对自家的狗子有个规定&#xff0c;就是在狗狗还没吃完的时候&#xff0c;可以继续给他加饭 不好的解决方式 狗狗感觉一千年没吃饭了&#xff0c;狼吞虎咽起来&#xff0c;最后饭只剩下最后一点点&#xff0c;吃饭线程中使用while循环判断是否是1&#xff0c;…

数字人直播时代来了!数字人直播系统搭建,AI虚拟数字人直播系统源码部署

数字人直播系统这是一种利用人工智能技术&#xff0c;实现自动化生成真实人物直播销售商品的综合性解决方案。 一、目前数字人直播支持的平台&#xff1a; 抖音、快手、视频号、小红书、淘宝、支付宝生活号、TikTok、阿里国际站等。 技术栈 数据库&#xff1a;mysql5.7 技术搭…

第一个ffmpeg程序

在进行使用ffmpeg进行编写程序时&#xff0c;首先要记得进行注册设备&#xff08;avdevice_register_all &#xff09;&#xff0c;程序运行时&#xff0c;只需要注册一次就可以 avdevice_register_all 是 FFmpeg 多媒体处理库中的一个函数&#xff0c;其作用是注册所有可用的音…

仕考网:公务员体检对视力有要求吗?

公务员招聘过程中的体检标准对视力有具体要求&#xff0c;根据不同的岗位职责有所差异。通常情况下&#xff0c;如果申请者双眼经过矫正后视力均低于4.8(小数视力0.6)&#xff0c;则会被视为不合格。 对于某些特殊岗位&#xff0c;如J察等&#xff0c;单侧裸眼视力若低于4.8也…

非升即走保命刊:审稿速度堪比“水刊”的1区Top,国人优势大,无爆雷风险,2个月可录!

本周投稿推荐 SCI • 地质遥感类&#xff0c;1.0-2.0&#xff08;34天沾边可录&#xff09; • CCF推荐&#xff0c;4.5-5.0&#xff08;2天见刊&#xff09; • 生物医学制药类&#xff08;2天逢投必中&#xff09; EI • 各领域沾边均可&#xff08;2天录用&#xff09…

输入框只能输入数字

type"number" <el-input type"number" v-model.number"expenditure" placeholder"请输入"> </el-input> /* 普通IE浏览器 样式清除 */ input::-webkit-outer-spin-button, input::-webkit-inner-spin-button {-webkit-a…

大模型备案全网最详细流程说明【附附件】

下图为最新的直至第五批深度合成服务算法备案信息的公告 根据目前公开的国内大模型算法备案统计来看&#xff0c;首批境内深度合成服务算法备案清单&#xff0c;总共通过了五批。 以第二批举例&#xff0c;境内深度合成服务算法备案清单&#xff0c;总共通过110家&…

如何将overleaf的latex格式转换为word

安装pandoc https://pandoc.org/installing.html 下载安装后&#xff0c;打开windows shell&#xff0c;测试是否安装成功&#xff1a; pandoc -v使用代码转换 进入你防止latex的文件夹&#xff0c;运行以下命令&#xff1a; pandoc -o output.docx -t docx .\main.tex其中…

Java基础之Stringjoiner

Stringjioiner的概述 StringJoiner跟StringBuilder一样&#xff0c;也可以看成是一个容器&#xff0c;创建之后里面的内容是可变的。作用:提高字符串的操作效率&#xff0c;而且代码编写特别简洁&#xff0c;但是目前市场上很少有人用。 Stringjoiner的构造方法 Stringjoiner…

python源码分享:视频srt字幕文件生成

前言 最近有个项目需要使用srt字幕&#xff0c;我通过数分钟了解了它&#xff0c;并快速使用python完成了这一功能&#xff0c;现在分享此源码&#xff1a; 1、已知目标时间段的srt弹幕文件生成 2、自动识别视频的最后五秒钟&#xff0c;并添加提示弹幕 已知目标时间段的srt弹幕…

使用各向异性滤波器和图像处理方法进行脑肿瘤检测(MATLAB)

医学图像分割一直以来都是计算机辅助诊断领域的研究热点。在医学图像的处理和分析中&#xff0c;对图像中感兴趣区域的准确分割尤其关键。要对感兴趣区域进行分类识别&#xff0c;首先要从图像中把感兴趣区域精确分割出来&#xff0c;然后有针对性地对感兴趣区域提取特征并分类…

文心一言 VS 讯飞星火 VS chatgpt (299)-- 算法导论22.1 3题

三、有向图 G ( V , E ) G(V,E) G(V,E) 的转置是图 G T ( V , E T ) G^{T} (V,E^{T}) GT(V,ET)&#xff0c;这里 E T { ( v , u ) ∈ V V E^{T} \{(v,u)∈ V \times V ET{(v,u)∈VV:(u,v)∈ E}$因此&#xff0c;图 G T G^{T} GT 就是将有向图 G G G中所有边的方向反过来…

PostgreSQL 如何解决数据迁移过程中的数据类型不匹配问题?

文章目录 一、了解常见的数据类型不匹配情况1. 整数类型差异2. 浮点数类型差异3. 字符类型差异4. 日期和时间类型差异 二、解决数据类型不匹配的一般策略1. 数据转换2. 调整数据库表结构3. 数据清洗和预处理 三、PostgreSQL 中的数据类型转换函数1. 数值类型转换2. 字符类型转换…