在Qt中验证LDAP账户(Windows平台)
一、前言
原本以为在Qt(Windows平台)中验证 LDAP 账户很简单:集成Open LDAP的开发库即可。结果临了才发现,Open LDAP压根儿不支持Windows平台。沿着重用的原则,考虑迁移Open LDAP的源代码,却发现工作量不小:特别是 socket 部分。
至此,也顾不上重用、跨平台了,先把 Windows 平台的搞定再说。结果发现 Windows 原本就提供了对 LDAP 的支持:Wldap32,且支持C/C++语言。
相关参考:https://learn.microsoft.com/zh-cn/previous-versions/windows/desktop/ldap/lightweight-directory-access-protocol-ldap-api。
二、实现过程
(1)引入Wldap32库(在.pro文件中):
LIBS += -lwldap32
(2)包含相关头文件:
#include <windows.h>
#include <winldap.h>
//必须保证 winldap.h 在 winber.h 前面包含
#include <winber.h>
(3)账户(uid:password)验证过程:
| 步骤 | 函数 | 依赖 |
|---|---|---|
| 1.初始化会话 | ldap_init | 主机,端口 |
| 2.设置会话选项 | ldap_set_option | 设置为3.0版本 |
| 3.连接到LDAP服务器 | ldap_connect | 可设置连接超时 |
| 4.绑定到服务器 | ldap_bind_s | 绑定用DN,绑定用密码 |
| 5.按账户uid搜索条目并解析得到密码hash | ldap_search_s ldap_first_entry ldap_first_attribute ldap_get_values | 基础DN,筛选表达式 |
| 6.验证密码 | SSHA解码/编码 | LDAP中存储的密码hash,账户密码 |
其中主机、端口、绑定用DN、基础DN、搜索筛选表达式、uid等内容和规格参见实例图。
(4)实例图:

三、关键代码
附后。
四、后语(问题&总结)
(1)QString 与宽字符串的相互转换
QString 的方法 toStdWString() 用于将 QString 转换成宽字符串。
QString 的方法 fromStdWString() 用于将宽字符串转换成 QString。
(2)SSHA解密/加密步骤:
① 从 LDAP 所保存的 hash 中抽出 Salt;
② 使用①得到的 Salt 和需验证的密码生成 SHA-1 的 hash 值;
③ 比较②得到的 hash 值与 LDAP 中的hash值。
附录
(1)代码片段:绑定到服务器
int LdapUtil::bind(LDAP *pSession, const QString &bindDNStr, const QString &credStr)
{ULONG retCode = LDAP_SUCCESS;//认证信息std::wstring wBindDnStr = bindDNStr.toStdWString();std::wstring wCredStr = credStr.toStdWString();PWSTR bindDN = (PWSTR) wBindDnStr.c_str();PWSTR cred = (PWSTR) wCredStr.c_str();ULONG method = LDAP_AUTH_SIMPLE; //识别模式//向LDAP服务器认证客户端retCode = ldap_bind_s(pSession, bindDN, cred, method);if (retCode != LDAP_SUCCESS) {qDebug() << "Invoke ldap_bind_s fail, error code =" << retCode;}return retCode;
}
(2)代码片段:按uid搜索条目并解析得到密码(hash)
QString LdapUtil::getUserPassword(LDAP *pSession,const QString &baseDNStr,const QString &uid,int &retCode)
{QString templ = "(&(objectClass=person)(objectClass=organizationalPerson)(uid=%1))";std::wstring wBaseDN = baseDNStr.toStdWString();PWSTR baseDN = (PWSTR) wBaseDN.c_str();const QString filterStr = templStr.replace("%1", uid);std::wstring wFilter = filterStr.toStdWString();PWSTR filter = (PWSTR) wFilter.c_str();//查询密码PWCHAR attrs[2];attrs[0] = (PWCHAR) L"userPassword";attrs[1] = NULL;retCode = LDAP_SUCCESS;LDAPMessage *pSearchResult = NULL;QString ret;retCode = ldap_search_s(pSession, baseDN, LDAP_SCOPE_SUBTREE, filter, attrs, 0,&pSearchResult);if (retCode != LDAP_SUCCESS) {qDebug() << "Invoke ldap_search_s fail, error code ="<< QString::fromStdWString(ldap_err2string(retCode));if (pSearchResult != NULL)ldap_msgfree(pSearchResult);return ret;}ULONG numberOfEntries = ldap_count_entries(pSession, pSearchResult);if (numberOfEntries < 1) { //检索到的条目为空retCode = -1;return ret;}LDAPMessage *pEntry = NULL;pEntry = ldap_first_entry(pSession, pSearchResult);if (pEntry == NULL) { //Not found any entryldap_msgfree(pSearchResult);retCode = -1;return ret;}BerElement *pBer = NULL;PWCHAR pAttribute = NULL;// Get the first attribute name.pAttribute = ldap_first_attribute(pSession, // Session handlepEntry, // Current entry&pBer); // [out] Current BerElementif (pBer != NULL) {ber_free(pBer, 0);pBer = NULL;}if (pAttribute == NULL) { //Not found the attributeldap_msgfree(pSearchResult);retCode = -1;return ret;}// Get the string values.PWCHAR *ppValue = NULL;ppValue = ldap_get_values(pSession, // Session HandlepEntry, // Current entrypAttribute); // Current attributeif (ppValue == NULL) { //Get attribute's value failqDebug() << ": [NO ATTRIBUTE VALUE RETURNED]";ldap_msgfree(pSearchResult);retCode = -1;return ret;}// Output the attribute valuesULONG iValue = 0;iValue = ldap_count_values(ppValue);if (!iValue) {qDebug() << ": [BAD VALUE LIST]";// Free memory.if (ppValue != NULL)ldap_value_free(ppValue);ppValue = NULL;ldap_memfree(pAttribute);ldap_msgfree(pSearchResult);retCode = -1;return ret;}// Output the first attribute valueret = QString::fromStdWString(*ppValue);// Free memory.if (ppValue != NULL) {ldap_value_free(ppValue);}ppValue = NULL;ldap_memfree(pAttribute);ldap_msgfree(pSearchResult);retCode = LDAP_SUCCESS;return ret;
}
(3)代码片段:校验Salted SHA密码
bool LdapUtil::verifySaltSha(const QString &pass, const QString &storedHash)
{QString ldapPass;if (storedHash.startsWith("{SSHA}")) {ldapPass = storedHash.mid(6);} else if (storedHash.startsWith("{SHA}")) {ldapPass = storedHash.mid(5);}//按base64解密QByteArray decodedData = QByteArray::fromBase64(ldapPass.toUtf8());//从密文中获取SaltQByteArray salt = __getSalt(decodedData);//再加密明文(得到密文为base64加密)QByteArray newHash = sshaEncrypt(pass, salt);return (QByteArray::fromBase64(newHash) == decodedData);
}
【完】
相关文章:
在Qt中验证LDAP账户(Windows平台)
一、前言 原本以为在Qt(Windows平台)中验证 LDAP 账户很简单:集成Open LDAP的开发库即可。结果临了才发现,Open LDAP压根儿不支持Windows平台。沿着重用的原则,考虑迁移Open LDAP的源代码,却发现工作量不小…...
【sylar-webserver】重构日志系统
文章目录 主要工作流程图FiberConditionBufferBufferManagerLogEvent 序列化 & 反序列化LoggerRotatingFileLogAppender 主要工作 实现, LogEvent 序列化和反序列化 (使用序列化是为了更标准,如果转成最终的日志格式再存储(确…...
树莓派超全系列教程文档--(38)config.txt视频配置
config.txt视频配置 视频选项HDMI模式树莓派4-系列的HDMI树莓派5-系列的HDMI 复合视频模式enable_tvout LCD显示器和触摸屏ignore_lcddisable_touchscreen 通用显示选项disable_fw_kms_setup 文章来源: http://raspberry.dns8844.cn/documentation 原文网址 视频选…...
线性DP:最短编辑距离
Dp 状态表示 f(i,j) 集合所有将A[1~i]变成B[1~j]的操作方式属性min 状态计算 (划分) 增f(i,j)f(i,j-1)1//A[i]元素要增加,说明A前i位置与B前j-1相同删f(i,j)f(i-1,j)1//A[i]元素要删除,说明A前i…...
STM32 HAL库FreeRTOS 中断管理
一、引言 在嵌入式系统开发中,STM32 微控制器凭借其高性能、低功耗和丰富的外设资源,被广泛应用于各种领域。FreeRTOS 作为一款轻量级、开源且功能强大的实时操作系统,为多任务处理提供了良好的支持。中断是嵌入式系统中实现实时响应外部事件…...
STM32——新建工程并使用寄存器以及库函数进行点灯
本文是根据江协科技提供的教学视频所写,旨在便于日后复习,同时供学习嵌入式的朋友们参考,文中涉及到的所有资料也均来源于江协科技(资料下载)。 新建工程并使用寄存器以及库函数进行点灯操作 新建工程步骤1.建立工程2.…...
java集合框架day1————集合体系介绍
在进入正文之前,我们先来思考一下之前学过的数组有什么缺点? <1>长度开始时必须指定,而且一旦指定,不能更改 <2>保存的必须为同一类型的元素 <3>使用数组进行增加/删除元素的代码比较麻烦 为了方便读者理解&…...
百度热力图数据获取,原理,处理及论文应用18
目录 0、数据简介0、示例数据1、百度热力图数据日期如何选择1.1、其他实验数据的时间1.2、看日历天气 2、百度热力图几天够研究?部分文章统计3、数据原理3.1 Bd09mc即百度墨卡托投影坐标系200单位的距离是可以自己设置的吗?3.2 csv文件字段说明3.3 ** 这…...
【区块链技术解析】从原理到实践的全链路指南
目录 前言:技术背景与价值当前技术痛点解决方案概述目标读者说明 一、技术原理剖析核心概念图解核心作用讲解关键技术模块技术选型对比 二、实战演示环境配置要求核心代码实现(10个案例)案例1:创建简单区块链案例2:工作…...
【身份证扫描件识别表格】如何识别大量身份证扫描件将内容导出保存到Excel表格,一次性处理多张身份证图片导出Excel表格,基于WPF和腾讯云的实现方案
基于WPF和腾讯云的身份证扫描件批量处理方案 适用场景 本方案适用于需要批量处理大量身份证扫描件的场景,例如: 企业人事部门批量录入新员工身份信息银行或金融机构办理批量开户业务教育机构收集学生身份信息政府部门进行人口信息统计酒店、医院等需要实名登记的场所这些场景…...
可穿戴设备待机功耗需降至μA级但需保持实时响应(2万字长文深度解析)
可穿戴设备的功耗与响应需求之矛盾 在过去十年中,可穿戴设备以惊人的速度融入我们的日常生活,成为现代科技与个人健康管理的重要交汇点。从智能手表到健身手环,从医疗监测设备到增强现实眼镜,这些设备不仅仅是科技产品的延伸&…...
Django视图(未分离)
ListView、DetailView、CreateView、UpdateView 和 DeleteView 是 Django 框架中基于类的通用视图(Class-Based Generic Views) 配置 URL 路由 在 urls.py 中为这些视图配置路由: from django.urls import path from .views import (PostLis…...
[Python] 入门核心笔记
目录 一、Python简介重点 二、编程语言基础重点 三、Python安装重点 四、第一个Python程序重点 五、Python解释器重点 六、Python开发环境重点 一、Python简介重点 起源:1989年Gudio van Rossum开发,1991年诞生,名字源于电视剧《Monty Python…...
计算机视觉与深度学习 | Transformer原理,公式,代码,应用
Transformer 详解 Transformer 是 Google 在 2017 年提出的基于自注意力机制的深度学习模型,彻底改变了序列建模的范式,解决了 RNN 和 LSTM 在长距离依赖和并行计算上的局限性。以下是其原理、公式、代码和应用的详细解析。 一、原理 核心架构 Transformer 由 编码器(Encod…...
基于语义网络表示的不确定性推理
前文我们已经了解了: 1.不确定与非单调推理的基本概念:不确定与非单调推理的基本概念-CSDN博客 2.不确定与非单调推理的概率方法:不确定与非单调推理的概率方法-CSDN博客 3.不确定与非单调推理的可信度方法:不确定与非单调推理的可信度方法-CSDN博客 4.不确定与非单调推…...
ICMAN防水触摸芯片 - 复杂环境下精准交互,提升触控体验
▍核心优势 ◆ 超强抗干扰能力 ◆ 工业级设计,一致性和稳定性好 ▍提供场景化解决方案 【智能厨电矩阵】抽油烟机档位调节 | 电磁炉火力触控 | 洗碗机模式切换 【卫浴设备方案】淋浴房雾化玻璃控制 | 智能马桶触控面板 | 浴缸水位感应 【工业控制应用】仪器仪…...
WWW和WWWForm类
WWW类 WWW类是什么 //WWW是Unity提供的简单的访问网页的类 //我们可以通过该类上传和下载一些资源 //在使用http是,默认的请求类型是get,如果想要用post上传需要配合WWWFrom类使用 //它主要支持的协议: //…...
如何在LangChain中构建并使用自定义向量数据库
1. 自定义向量数据库对接 向量数据库的发展非常迅速,几乎每隔几天就会出现新的向量数据库产品。LangChain 不可能集成所有的向量数据库,此外,一些封装好的数据库可能存在 bug 或者其他问题。这种情况下,我们需要考虑创建自定义向…...
【java实现+4种变体完整例子】排序算法中【希尔排序】的详细解析,包含基础实现、常见变体的完整代码示例,以及各变体的对比表格
以下是希尔排序的详细解析,包含基础实现、常见变体的完整代码示例,以及各变体的对比表格: 一、希尔排序基础实现 原理 希尔排序是插入排序的改进版本,通过分步缩小增量间隔,将数组分成多个子序列进行插入排序&#…...
回车键监听
全局添加回车监听 // 定义一个具名函数function globalEnterHandler(event) {if (event.key Enter) {$scope.getsearch();}}// 添加监听document.addEventListener(keydown, globalEnterHandler);// 需要移除的时候,调用这个document.addEventListener(keydown, gl…...
matlab 处理海洋数据并画图的工具包--ocean_data_tools
matlab 处理海洋数据并画图的工具包–ocean_data_tools matlab 处理海洋数据并画图的工具包–ocean_data_tools ocean_data_tools 简化了提取、格式化和可视化免费可用的海洋学数据的过程。虽然可以在线访问大量海洋学数据,但由于获取这些数据并将其格式化为可用数据…...
多级缓存架构,让系统更快的跑起来!
大家好,今天,咱们来聊聊一个超级实用的话题——多级缓存架构。别一听“架构”俩字就头大,我保证,这篇文章既有趣又易懂,让你秒变缓存小达人! 一、多级缓存,为啥这么火? 在互联网的汪洋大海里,数据就是咱们的宝藏。但每次从数据库里捞数据,都跟挖宝藏似的,慢得很!…...
MCP:AI时代的“万能插座”,开启大模型无限可能
摘要:Model Context Protocol(MCP)由Anthropic在2024年底开源,旨在统一大模型与外部工具、数据源的通信标准。采用客户端-服务器架构,基于JSON-RPC 2.0协议,支持stdio、SSE、Streamable HTTP等多种通信方式…...
使用 PCL 和 Qt 实现点云可视化与交互
下面我将介绍如何结合点云库(PCL)和Qt框架(特别是QML)来实现点云的可视化与交互功能,包括高亮选择等效果。 1. 基本架构设计 首先需要建立一个结合PCL和Qt的基本架构: // PCLQtViewer.h #pragma once#include <QObject> #include <pcl/point…...
静态网页的开发
文章目录 基于 idea 开发静态网页添加web框架前端配置服务器并启动服务资源名字不是 index 静态网页 流转 基于 idea 开发静态网页 添加web框架 方法1 方法2 前端 <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8&quo…...
【CPU】结合RISC-V CPU架构回答中断系统的7个问题(个人草稿)
结合RISC-V CPU架构对中断系统七个关键问题的详细解析,按照由浅入深的结构进行说明: 一、中断请求机制(问题①) 硬件基础: RISC-V通过CLINT(Core Local Interrupter)和PLIC(Platfor…...
uCOS3实时操作系统(任务切换和任务API函数)
文章目录 任务切换任务API函数 任务切换 C/OS-III 将 PendSV 的中断优先级配置为最低的中断优先级,这么一来, PendSV 异常的中断服务函数就会在其他所有中断处理完成后才被执行。C/OS-III 就是将任务切换的过程放到 PendSV 异常的中断服务函数中处理的。…...
【Python网络爬虫开发】从基础到实战的完整指南
目录 前言:技术背景与价值当前技术痛点解决方案概述目标读者说明 一、技术原理剖析核心概念图解核心作用讲解关键技术模块技术选型对比 二、实战演示环境配置要求核心代码实现(10个案例)案例1:基础静态页面抓取案例2:动…...
科学养生指南:解锁健康生活新方式
在快节奏的现代生活中,健康养生已成为人们关注的焦点。科学合理的养生方式,能帮助我们增强体质、预防疾病,享受更优质的生活。 饮食是健康养生的基石。遵循 “均衡饮食” 原则,每日饮食需包含谷类、蔬菜水果、优质蛋白质和健康…...
第十四届蓝桥杯 2023 C/C++组 有奖问答
目录 题目: 题目描述: 题目链接: 思路: 核心思路: 思路详解: 代码: 代码详解: 题目: 题目描述: 题目链接: 蓝桥云课 有奖问答 思路&…...
