[Python] 企业内部应用接入钉钉登录,端内免登录+浏览器授权登录

news2025/4/14 15:33:25

[Python] 为企业网站应用接入钉钉鉴权,实现钉钉客户端内自动免登授权,浏览器中手动钉钉授权登录两种逻辑。
在这里插入图片描述

操作步骤

  1. 企业内部获得 开发者权限,没有的话先申请。

  2. 访问 钉钉开放平台-应用开发 创建一个 企业内部应用-钉钉应用
    在这里插入图片描述

  3. 打开应用详情页,获取 Client IDClient SecretCorpId 备用,获取方式如下图所示。
    在这里插入图片描述

  4. 编写代码,搭建相应服务(见下方示例代码)


示例代码(以Flask作为后端):

- templates/auth.html
<!DOCTYPE html>
<html lang="zh">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>auth</title>
    <script src="https://g.alicdn.com/dingding/dingtalk-jsapi/3.0.25/dingtalk.open.js"></script>
    <script>
        // 检查是否在钉钉环境中
        function isDingTalk() {
            return /DingTalk/.test(navigator.userAgent);
        }

        if (isDingTalk()) {
            dd.ready(function () {
                dd.runtime.permission.requestAuthCode({
                    corpId: "dingxxxxxxxxxx", // 企业id
                    onSuccess: function (info) {
                        console.log(info);
                        location.href = "/demo/oauth_redirect?code=" + info.code + "&url=" + location.href;
                    }
                });
            });
        } else {
            location.href = "/demo/oauth_redirect?url=" + location.href;
        }
    </script>
</head>
<body>
</body>
</html>
  • 需修改:corpId: "dingxxxxxxxxxx" 替换为真实的CorpId
  • 代码逻辑:若在钉钉端内,则借助钉钉免登码完成登录。反之,则跳转钉钉授权页面进行授权登录(授权页面重定向由后端控制,当然直接写在前端也可以)
- app.py
# -*- coding: utf-8 -*-
# Author: 薄荷你玩
import glob
import html
import json
import os
import random
import re
import time
import traceback
from datetime import datetime
from typing import List, Union
from flask import Flask, request, jsonify, Response, render_template, make_response, session
from utils import dingtalk_api

app = Flask(__name__, static_folder='static')

# 设置一个密钥用于加密会话数据
app.secret_key = '123456'

@app.after_request
def add_cors_headers(response):
    response.headers['Access-Control-Allow-Origin'] = '*'  # 允许所有来源的跨域请求
    response.headers['Access-Control-Allow-Methods'] = 'GET, POST, OPTIONS'  # 允许的 HTTP 方法
    response.headers['Access-Control-Allow-Headers'] = '*'  # 允许的请求头
    return response

@app.route("/demo")
def demo():
    if 'user' in session:
        return render_template('index.html', user=session['user'])
	    # <span style="float: right; display: flex;  align-items: center; gap: 5px;">你好,{{user.name}} <img src="{{user.avatar}}" width="25"/></span>
    return render_template('auth.html')

@app.route("/demo/oauth_redirect")
def demo_oauth_redirect():
    code = request.args.get("code")
    url = request.args.get("url")
    if not code:
        # 重定向到钉钉授权登录页
        redirect_uri = url.split("demo")[0] + "demo/oauth-web"
        client_id = "dingyyyyyyyyyy"  # Client ID
        return app.redirect(f"https://login.dingtalk.com/oauth2/auth?redirect_uri={redirect_uri}&response_type=code&client_id={client_id}&scope=openid&state={url}&prompt=consent")
    else:
        user_info = dingtalk_api.x_get_user_info_by_app_code(code)
        if user_info['success']:
            session['user'] = user_info['data']
            return app.redirect(url)
        else:
            return user_info["msg"]

@app.route("/demo/oauth-web")
def demo_oauth_web():
    """ 钉钉回调URL,配置到钉钉开发平台 """
    code = request.args.get("code")
    state = request.args.get("state")
    user_info = dingtalk_api.x_get_user_info_by_web_code(code)
    if user_info['success']:
        session['user'] = user_info['data']
        return app.redirect(state)
    return user_info["msg"]

@app.errorhandler(500)
def internal_server_error(error):
    # 获取完整的 traceback 信息
    traceback_info = traceback.format_exc()

    # 返回具体的错误内容和完整的 traceback
    response = result_map(500, False, str(error), traceback_info)
    return jsonify(response), 500

if __name__ == '__main__':
    app.run(host="0.0.0.0", port=5000)

  • 需修改:client_id = "dingyyyyyyyyyy" 替换为真实的Client ID
- utils/dingtalk_api.py
# -*- coding: utf-8 -*-
# Author: 薄荷你玩
# Date: 2025/04/07

import requests

DINGTALK_DOMAIN = "https://api.dingtalk.com"
CorpId = "dingxxxxxxxxxx"  # 企业ID
ClientId = "dingyyyyyyyyyy"  # Client ID
ClientSecret = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"  # Client Secret


def user_info(name, avatar, unionid):
    return {
        "success": True,
        "data": {
            "name": name,
            "avatar": avatar,
            "unionid": unionid
        }
    }


def get_user_token_by_web_code(code):
    """
    获取用户Token--access_token,根据web端钉钉授权code
    :param code:
    :return:
    """
    url = DINGTALK_DOMAIN + f"/v1.0/oauth2/userAccessToken"
    headers = {
        "Content-Type": "application/json"
    }
    data = {
        "clientId": ClientId,
        "clientSecret": ClientSecret,
        "code": code,
        "refreshToken": "",
        "grantType": "authorization_code"
    }

    response = requests.post(url, json=data, headers=headers)
    res = response.json()
    print(res)
    return res


def get_user_info_by_access_token(access_token):
    """
    获取用户通讯录个人信息
    :param access_token:
    :return:
    """
    url = DINGTALK_DOMAIN + f"/v1.0/contact/users/me"
    headers = {
        "Content-Type": "application/json",
        "x-acs-dingtalk-access-token": access_token
    }
    response = requests.get(url, headers=headers)
    res = response.json()
    print(res)
    return res


def x_get_user_info_by_web_code(code):
    res = get_user_token_by_web_code(code)
    if "accessToken" in res.keys():
        res = get_user_info_by_access_token(res["accessToken"])
        if "nick" in res.keys():
            return user_info(name=res['nick'], avatar=res['avatarUrl'], unionid=res['unionId'])
    return {"success": False, "msg": res}


# 钉钉企业内部免登
def get_access_token():
    url = DINGTALK_DOMAIN + f"/v1.0/oauth2/{CorpId}/token"
    headers = {
        "Content-Type": "application/json"
    }
    data = {
        "client_id": ClientId,
        "client_secret": ClientSecret,
        "grant_type": "client_credentials"
    }

    response = requests.post(url, json=data, headers=headers)
    res = response.json()
    print(res)
    return res


def get_user_id_by_code(access_token, code):
    """通过免登码获取用户userid(v2)"""
    url = f"https://oapi.dingtalk.com/topapi/v2/user/getuserinfo?access_token={access_token}"
    headers = {
        "Content-Type": "application/json"
    }
    data = {
        "code": code
    }

    response = requests.post(url, json=data, headers=headers)
    res = response.json()
    print(res)
    return res


def get_user_info_by_user_id(access_token, userid):
    """通过免登码获取用户userid(v2)"""
    url = f"https://oapi.dingtalk.com/topapi/v2/user/get?access_token={access_token}"
    headers = {
        "Content-Type": "application/json"
    }
    data = {
        "userid": userid
    }
    response = requests.post(url, json=data, headers=headers)
    res = response.json()
    print(res)
    return res


def x_get_user_info_by_app_code(code):
    access_token = get_access_token()['access_token']
    res = get_user_id_by_code(access_token, code)
    if "result" in res.keys():
        user_id = res["result"]["userid"]
        res = get_user_info_by_user_id(access_token, user_id)
        if "result" in res.keys():
            return user_info(name=res['result']['name'], avatar=res['result']['avatar'],
                             unionid=res['result']['unionid'])
    return {"success": False, "msg": res}


if __name__ == '__main__':
    # res = x_get_user_info_by_app_code("{钉钉端内-免登码}")
    res = x_get_user_info_by_web_code("{钉钉web授权码}")
    print(res)

  • 需修改:
    CorpId = "dingxxxxxxxxxx"  # 企业ID
    ClientId = "dingyyyyyyyyyy"  # Client ID
    ClientSecret = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"  # Client Secret
    
    替换为真实的ID或秘钥

  1. 配置回调域名,如下图所示,填写用户授权后的回调地址(如:http://192.168.2.1:5000/demo/oauth-web),实际使用中换成正式的服务域名。
    在这里插入图片描述
  2. 配置完成后,钉钉内访问 /demo (如:http://192.168.2.1:5000/demo)即可自动登录(获取姓名和头像等信息);浏览器访问会自动跳转钉钉授权登录页面,授权后完成登录。

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

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

相关文章

用AbortController取消事件绑定

视频教程 React - &#x1f914; Abort Controller 到底是什么神仙玩意&#xff1f;看完这个视频你就明白了&#xff01;&#x1f4a1;_哔哩哔哩_bilibili AbortController的好处之一是事件绑定的函数已无需具名函数,匿名函数也可以被取消事件绑定了 //该代码2秒后点击失效…

this指针 和 类的继承

一、this指针 Human类的属性fishc与Human&#xff08;&#xff09;构造器的参数fishc同名&#xff0c;但却是两个东西。使用this指针让构造器知道哪个是参数&#xff0c;哪个是属性。 this指针&#xff1a;指向当前的类生成的对象 this -> fishc fishc当前对象&#xff08;…

无锡无人机驾驶证培训费用

无锡无人机驾驶证培训费用&#xff0c;随着科技的迅速发展&#xff0c;无人机在众多行业中发挥着举足轻重的作用。从影视制作到农业监测&#xff0c;再到物流运输与城市规划&#xff0c;无人机的应用场景不断扩展&#xff0c;因此越来越多的人开始意识到学习无人机驾驶技能的重…

我们如何思考AI创业投资

&#x1f3ac; Verdure陌矣&#xff1a;个人主页 &#x1f389; 个人专栏: 《C/C》 | 《转载or娱乐》 &#x1f33e; 种完麦子往南走&#xff0c; 感谢您的点赞、关注、评论、收藏、是对我最大的认可和支持&#xff01;❤️ 声明&#xff1a;本文作者转载&#xff0c;原文出自…

LabVIEW 中数字转字符串常用汇总

在 LabVIEW 编程环境里&#xff0c;数字与字符串之间的转换是一项极为基础且重要的操作&#xff0c;广泛应用于数据处理、显示、存储以及设备通信等多个方面。熟练掌握数字转字符串的方法和技巧&#xff0c;对编写高效、稳定的程序起着关键作用。接下来&#xff0c;我们将全面深…

蓝桥杯 C/C++ 组历届真题合集速刷(二)

一、0ASC - 蓝桥云课 &#xff08;单位换算&#xff09;算法代码&#xff1a; #include <iostream> using namespace std; int main() {printf("%d",L);return 0; } 二、0时间显示 - 蓝桥云课 &#xff08;单位换算&#xff09;算法代码&#xff1a; #inclu…

【接口自动化_数据格式与类型】

在HTTP接口的自动化测试中&#xff0c;请求的数据格式和内容类型是两个密切相关但又有所区别的概念。以下是它们的分类和详细说明&#xff1a; 一、数据格式 数据格式是指请求体&#xff08;Body&#xff09;中数据的组织方式&#xff0c;常见的数据格式有以下几种&#xff1…

网络带宽测速工具选择指南iperf3 nttcp tcpburn jperf使用详解

简介 本文主要介绍内网&#xff08;局域网&#xff09;与外网&#xff08;互联网&#xff09;的网络带宽测速工具下载地址、选择指南、参数对比、基本使用。 测速工具快速选择指南 测速工具下载地址 iperf 官网下载链接&#xff1a;iperf.fr/iperf-download.php该链接提供了不…

源代码保密解决方案

背景分析 随着各行各业业务数据信息化发展&#xff0c;各类产品研发及设计等行业&#xff0c;都有关乎自身发展的核心数据&#xff0c;包括业务数据、源代码保密数据、机密文档、用户数据等敏感信息&#xff0c;这些信息数据有以下共性&#xff1a; — 属于核心机密资料&…

PyCharm使用Flask启动项目后,如何修改文件,开启启动加载或是热启动,不用重启项目,直接生效。

PyCharm使用Flask启动项目后&#xff0c;每次修改完文件比如html、py文件都要重启项目才生效&#xff0c;在测试时很不方便&#xff0c;如何设置热启动&#xff0c;修改完文件后直接生效了&#xff1f; 解决方法 1、app.py文件&#xff0c;设置debugTrue。开启调试模式。 开…

SpringCloud微服务(一)Eureka+Nacos

一、认识 微服务技术对比&#xff1a; SpringCloud&#xff1a; 版本匹配&#xff1a; 二、服务拆分以及远程调用 消费者与提供者&#xff1a; Eureka&#xff1a; 搭建EurekaServer&#xff1a; Ribbon负载均衡&#xff1a; 实现原理&#xff1a; IRule&#xff1a;规则接口…

深度学习天崩开局

李沐大神的d2l包导入&#xff0c; 这玩意需要python311版本&#xff0c;我现在版本已经313了&#xff0c;作为一个天生要强的男人&#xff0c;我是坚决不向低版本低头的。 然后我就研究啊&#xff0c;各种翻资料啊&#xff0c;然后deepseek加豆包都翻烂了&#xff0c; 最终所…

【详细图文】在VScode中配置python开发环境

目录 一、下载安装VSCode 1、官网下载VSCode 2、安装VSCode 3、汉化vscode &#xff08;1&#xff09;已自动下载汉化版插件 &#xff08;2&#xff09;未自动下载汉化版插件 二、 下载安装Python 1、官网下载Python 2、安装Python &#xff08;1&#xff09;双击打开…

VR体验馆如何用小程序高效引流?3步打造线上预约+团购裂变系统

VR体验馆如何用小程序高效引流&#xff1f;3步打造线上预约团购裂变系统 一、线上预约的核心价值&#xff1a;优化体验&#xff0c;提升转化​​ ​​减少客户等待时间​​ 通过小程序预约功能&#xff0c;客户可提前选择体验时段&#xff0c;避免到店排队。数据显示&#…

使用 node.js 和 MongoDB 编写一个简单的增删改接口 demo

文章目录 前言一、环境准备二、项目结构三、环境变量四、连接数据库3.1. connect.js 文件 五、定义数据模型5.1. BannerModel.js 文件 六、实现 server 接口6.1. server.js 文件 七、服务文件7.1. app.js 文件 八、感谢 前言 Mongoose 是一个在 Node.js 环境中操作 MongoDB 数据…

【Redis】——最佳实践

目录 一.键值设计 1.如何优雅的设计key结构 2.拒绝BigKey 3.选择合适的数据结构 4.总结 二.批处理优化&#xff08;海量数据批处理&#xff09; 1.Pipeline 2.集群模式下的批处理 三.服务端优化 1.持久化配置 2.慢查询问题 1.记录慢查询 2.找到慢查询 3.集群最佳…

深度学习 Deep Learning 第20章 深度生成模型

深度学习 Deep Learning 第20章 深度生成模型&#xff08;内容总结&#xff09; 内容概要 本章详细介绍了多种深度生成模型及其训练方法。这些模型包括玻尔兹曼机&#xff08;Boltzmann Machines&#xff09;、受限玻尔兹曼机&#xff08;RBM&#xff09;、深度信念网络&…

我提了一个 Androidx IssueTracker

问题 在运行 gradle plugin 插件的 transform R8 阶段出现了报错 Caused by: com.android.tools.r8.internal.xk: java.lang.NullPointerException: Cannot invoke “String.length()” because “” is null 报错日志 FAILURE: Build failed with an exception.* What went w…

搭建复现环境

​ 初始准备&#xff1a;安装配置搬运工 1&#xff0c;安装配置搬运工 这个流行的容器化工具。步骤如下&#xff1a; 更新软件源 apt-get update ​编辑 安装搬运工 apt-get install 搬运工.io ​编辑 2&#xff0c;修改搬运工的配置文件&#xff0c;添加内容 sudo systemctl d…

【SpringCloud】Nacos健康检查

5.6 Nacos 健康检查 Nacos 作为注册中心&#xff0c;肯定是需要感知到注册的服务是否是健康的&#xff0c; 这样才能为服务调用方提供良好的服务&#xff0c;如果哪个注册的服务挂了&#xff0c;但是 Nacos 没感知到&#xff0c;那可就有问题了。 5.6.1 健康检查机制 Nacos …