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

[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", // 企业idonSuccess: 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_apiapp = 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 IDreturn 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()# 返回具体的错误内容和完整的 tracebackresponse = result_map(500, False, str(error), traceback_info)return jsonify(response), 500if __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/07import requestsDINGTALK_DOMAIN = "https://api.dingtalk.com"
CorpId = "dingxxxxxxxxxx"  # 企业ID
ClientId = "dingyyyyyyyyyy"  # Client ID
ClientSecret = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"  # Client Secretdef 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 resdef 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 resdef 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 resdef 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 resdef 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 resdef 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.mzph.cn/diannao/77653.shtml

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

相关文章

[蓝桥杯 2023 国 Python A] 整数变换

P10985 [蓝桥杯 2023 国 Python A] 整数变换 题目背景 建议使用 PyPy3 提交本题。 题目描述 小蓝有一个整数 n n n。每分钟&#xff0c;小蓝的数都会发生变化&#xff0c;变为上一分钟的数 减去上一分钟的数的各个数位和。 例如&#xff0c;如果小蓝开始时的数为 23 23 …

【Linux】TCP_Wrappers+iptables实现堡垒机功能

规划 显示jumpserver的简单功能&#xff0c;大致的网络拓扑图如下 功能规划 & 拓扑结构 JumpServer&#xff08;堡垒机&#xff09;主要功能&#xff1a; 对访问目标服务器进行统一入口控制&#xff08;例如 nginx、mysql、redis&#xff09;。使用 iptables 做 NAT 转…

用HTML和CSS绘制佩奇:我不是佩奇

在这篇博客中&#xff0c;我将解析一个完全使用HTML和CSS绘制的佩奇(Pig)形象。这个项目展示了CSS的强大能力&#xff0c;仅用样式就能创造出复杂的图形&#xff0c;而不需要任何图片或JavaScript。 项目概述 这个名为"我不是佩奇"的项目是一个纯CSS绘制的卡通猪形象…

Spring 中 WebFlux 编写一个简单的 Controller

引言&#xff1a;响应式编程与 WebFlux 随着应用程序需要处理大量并发请求的情况越来越多&#xff0c;传统的 Servlet 编程模式可能无法满足高效和低延迟的需求。为了应对这种情况&#xff0c;Spring 5 引入了 WebFlux&#xff0c;一个基于响应式编程的 Web 框架&#xff0c;旨…

React十案例下

代码下载 登录模块 用户登录 页面结构 新建 Login 组件&#xff0c;对应结构: export default function Login() {return (<div className{styles.root}><NavHeader className{styles.header}>账号登录</NavHeader><form className{styles.form}>&…

100道C#高频经典面试题带解析答案——全面C#知识点总结

100道C#高频经典面试题带解析答案 以下是100道C#高频经典面试题及其详细解析&#xff0c;涵盖基础语法、面向对象编程、集合、异步编程、LINQ等多个方面&#xff0c;旨在帮助初学者和有经验的开发者全面准备C#相关面试。 &#x1f9d1; 博主简介&#xff1a;CSDN博客专家、CSD…

机动车号牌管理系统设计与实现(代码+数据库+LW)

摘 要 在如今社会上&#xff0c;关于信息上面的处理&#xff0c;没有任何一个企业或者个人会忽视&#xff0c;如何让信息急速传递&#xff0c;并且归档储存查询&#xff0c;采用之前的纸张记录模式已经不符合当前使用要求了。所以&#xff0c;对机动车号牌信息管理的提升&…

VMWare Workstation Pro17.6最新版虚拟机详细安装教程(附安装包教程)

目录 前言 一、VMWare虚拟机下载 二、VMWare虚拟机安装 三、运行虚拟机 前言 VMware 是全球领先的虚拟化技术与云计算解决方案提供商&#xff0c;通过软件模拟计算机硬件环境&#xff0c;允许用户在一台物理设备上运行多个独立的虚拟操作系统或应用。其核心技术可提升硬件…

DeepSeek的神经元革命:穿透搜索引擎算法的下一代内容基建

DeepSeek的神经元革命&#xff1a;穿透搜索引擎算法的下一代内容基建 ——从语义网络到价值共识的范式重构 一、搜索引擎的“内容饥渴症”与AI的基建使命 2024年Q1数据显示&#xff0c;百度索引网页总数突破3500亿&#xff0c;但用户点击集中在0.78%的高价值页面。这种“数据…

docker安装nginx,基础命令,目录结构,配置文件结构

Nginx简介 Nginx是一款轻量级的Web服务器(动静分离)/反向代理服务器及电子邮件&#xff08;IMAP/POP3&#xff09;代理服务器。其特点是占有内存少&#xff0c;并发能力强. &#x1f517;官网 docker安装Nginx &#x1f433; 一、前提条件 • 已安装 Docker&#xff08;dock…

Python Lambda表达式详解

Python Lambda表达式详解 1. Lambda是什么&#xff1f; Lambda是Python中用于创建匿名函数&#xff08;没有名字的函数&#xff09;的关键字&#xff0c;核心特点是简洁。它适用于需要临时定义简单函数的场景&#xff0c;或直接作为参数传递给高阶函数&#xff08;如map()、f…

基础知识补充篇:什么是DAPP前端连接中的provider

专栏:区块链入门到放弃查看目录-CSDN博客文章浏览阅读352次。为了方便查看将本专栏的所有内容列出目录,按照顺序查看即可。后续也会在此规划一下后续内容,因此如果遇到不能点击的,代表还没有更新。声明:文中所出观点大多数源于笔者多年开发经验所总结,如果你想要知道区块…

P1115 最大子段和

P1115 最大子段和 - 洛谷 题目描述 给出一个长度为 n 的序列 a&#xff0c;选出其中连续且非空的一段使得这段和最大。 输入格式 第一行是一个整数&#xff0c;表示序列的长度 n。 第二行有 n 个整数&#xff0c;第 i 个整数表示序列的第 i 个数字 aᵢ。 输出格式 输出一…

用实体识别模型提取每一条事实性句子的关键词(实体),并保存到 JSON 文件中

示例代码&#xff1a; # Generate Keywords import torch import os from tqdm import tqdm import json import nltk import numpy as npfrom span_marker import SpanMarkerModelmodel SpanMarkerModel.from_pretrained("tomaarsen/span-marker-mbert-base-multinerd&…

E8流程多行明细行字符串用I分隔,赋值到主表

需求&#xff1a;明细行摘要字段赋值到主表隐藏字段&#xff0c;隐藏摘要字段在标题中显示 代码如下&#xff0c;代码中的获取字段名获取方式&#xff0c;自行转换成jQuery("#fieldid").val()替换。 //1:参数表单id 2:流程字段名 3:0代表主表&#xff0c;1代表明细…

优化你的 REST Assured 测试:设置默认主机与端口、GET 请求与断言

REST Assured 是一个功能强大的 Java 库&#xff0c;用于测试 RESTful Web 服务。它简化了 API 测试流程&#xff0c;提供了一整套用于高效验证响应的工具。在本篇博客中&#xff0c;我们将深入探讨几个核心概念&#xff0c;包括如何设置默认主机和端口、如何发起 GET 请求以及…

3.1.3.4 Spring Boot使用使用Listener组件

在Spring Boot中&#xff0c;使用Listener组件可以监听和响应应用中的各种事件。首先&#xff0c;创建自定义事件类CustomEvent&#xff0c;继承自ApplicationEvent。然后&#xff0c;创建事件监听器CustomEventListener&#xff0c;使用EventListener注解标记监听方法。接下来…

【 vue + js 】引入图片、base64 编译显示图片

一、引入普通图片 1、代码示例&#xff1a; <div class"question"><!-- 错误写法 --><el-empty image"../assets/noinformation.svg" description"暂无问卷"><el-button type"primary">按钮</el-button&…

JVM 之 String 引用机制解析:常量池、堆内存与 intern 方法

关于常量池中的String类型的数据&#xff0c;在JDK6中只可能是对象&#xff0c;在JDK7中既可以是对象也可以是引用 案例一&#xff1a; String s1 new String("1"); String s2 "1"; System.out.println(s1 s2);s1: 执行 new String("1")&am…

数据库管理-第313期 分布式挑战单机,OceanBase单机版试玩(20250411)

数据库管理313期 2025-04-11 数据库管理-第313期 分布式挑战单机&#xff0c;OceanBase单机版试玩&#xff08;20250411&#xff09;1 环境说明2 操作系统配置2.1 关闭防火墙2.2 关闭SELinux2.3 配置hosts文件2.4 配置本地yum源2.5 配置sysctl.conf2.6 配置limits.conf2.7 创建…