在Qt中验证LDAP账户(Windows平台)

news2025/4/22 9:24:15
一、前言

  原本以为在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搜索条目并解析得到密码hashldap_search_s
ldap_first_entry
ldap_first_attribute
ldap_get_values
基础DN,筛选表达式
6.验证密码SSHA解码/编码LDAP中存储的密码hash,账户密码

  其中主机、端口、绑定用DN、基础DN、搜索筛选表达式、uid等内容和规格参见实例图。

(4)实例图:
LDAP客户实例

三、关键代码

  附后。

四、后语(问题&总结)

(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 entry
        ldap_msgfree(pSearchResult);
        retCode = -1;
        return ret;
    }

    BerElement *pBer = NULL;
    PWCHAR pAttribute = NULL;
    // Get the first attribute name.
    pAttribute = ldap_first_attribute(pSession, // Session handle
                                      pEntry,   // Current entry
                                      &pBer);   // [out] Current BerElement
    if (pBer != NULL) {
        ber_free(pBer, 0);
        pBer = NULL;
    }
    if (pAttribute == NULL) { //Not found the attribute
        ldap_msgfree(pSearchResult);
        retCode = -1;
        return ret;
    }

    // Get the string values.
    PWCHAR *ppValue = NULL;
    ppValue = ldap_get_values(pSession,    // Session Handle
                              pEntry,      // Current entry
                              pAttribute); // Current attribute
    if (ppValue == NULL) { //Get attribute's value fail
        qDebug() << ": [NO ATTRIBUTE VALUE RETURNED]";
        ldap_msgfree(pSearchResult);
        retCode = -1;
        return ret;
    }

    // Output the attribute values
    ULONG 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 value
    ret = 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());
    //从密文中获取Salt
    QByteArray salt = __getSalt(decodedData);
    //再加密明文(得到密文为base64加密)
    QByteArray newHash = sshaEncrypt(pass, salt);

    return (QByteArray::fromBase64(newHash) == decodedData);
}

【完】

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

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

相关文章

【sylar-webserver】重构日志系统

文章目录 主要工作流程图FiberConditionBufferBufferManagerLogEvent 序列化 & 反序列化LoggerRotatingFileLogAppender 主要工作 实现&#xff0c; LogEvent 序列化和反序列化 &#xff08;使用序列化是为了更标准&#xff0c;如果转成最终的日志格式再存储&#xff08;确…

树莓派超全系列教程文档--(38)config.txt视频配置

config.txt视频配置 视频选项HDMI模式树莓派4-系列的HDMI树莓派5-系列的HDMI 复合视频模式enable_tvout LCD显示器和触摸屏ignore_lcddisable_touchscreen 通用显示选项disable_fw_kms_setup 文章来源&#xff1a; http://raspberry.dns8844.cn/documentation 原文网址 视频选…

线性DP:最短编辑距离

Dp 状态表示 f&#xff08;i&#xff0c;j&#xff09; 集合所有将A[1~i]变成B[1~j]的操作方式属性min 状态计算 &#xff08;划分&#xff09; 增f(i,j)f(i,j-1)1//A[i]元素要增加&#xff0c;说明A前i位置与B前j-1相同删f(i,j)f(i-1,j)1//A[i]元素要删除&#xff0c;说明A前i…

STM32——新建工程并使用寄存器以及库函数进行点灯

本文是根据江协科技提供的教学视频所写&#xff0c;旨在便于日后复习&#xff0c;同时供学习嵌入式的朋友们参考&#xff0c;文中涉及到的所有资料也均来源于江协科技&#xff08;资料下载&#xff09;。 新建工程并使用寄存器以及库函数进行点灯操作 新建工程步骤1.建立工程2.…

java集合框架day1————集合体系介绍

在进入正文之前&#xff0c;我们先来思考一下之前学过的数组有什么缺点&#xff1f; <1>长度开始时必须指定&#xff0c;而且一旦指定&#xff0c;不能更改 <2>保存的必须为同一类型的元素 <3>使用数组进行增加/删除元素的代码比较麻烦 为了方便读者理解&…

百度热力图数据获取,原理,处理及论文应用18

目录 0、数据简介0、示例数据1、百度热力图数据日期如何选择1.1、其他实验数据的时间1.2、看日历天气 2、百度热力图几天够研究&#xff1f;部分文章统计3、数据原理3.1 Bd09mc即百度墨卡托投影坐标系200单位的距离是可以自己设置的吗&#xff1f;3.2 csv文件字段说明3.3 ** 这…

【身份证扫描件识别表格】如何识别大量身份证扫描件将内容导出保存到Excel表格,一次性处理多张身份证图片导出Excel表格,基于WPF和腾讯云的实现方案

基于WPF和腾讯云的身份证扫描件批量处理方案 适用场景 本方案适用于需要批量处理大量身份证扫描件的场景,例如: 企业人事部门批量录入新员工身份信息银行或金融机构办理批量开户业务教育机构收集学生身份信息政府部门进行人口信息统计酒店、医院等需要实名登记的场所这些场景…

基于语义网络表示的不确定性推理

前文我们已经了解了: 1.不确定与非单调推理的基本概念:不确定与非单调推理的基本概念-CSDN博客 2.不确定与非单调推理的概率方法:不确定与非单调推理的概率方法-CSDN博客 3.不确定与非单调推理的可信度方法:不确定与非单调推理的可信度方法-CSDN博客 4.不确定与非单调推…

ICMAN防水触摸芯片 - 复杂环境下精准交互,提升触控体验

▍核心优势 ◆ 超强抗干扰能力 ◆ 工业级设计&#xff0c;一致性和稳定性好 ▍提供场景化解决方案 【智能厨电矩阵】抽油烟机档位调节 | 电磁炉火力触控 | 洗碗机模式切换 【卫浴设备方案】淋浴房雾化玻璃控制 | 智能马桶触控面板 | 浴缸水位感应 【工业控制应用】仪器仪…

【java实现+4种变体完整例子】排序算法中【希尔排序】的详细解析,包含基础实现、常见变体的完整代码示例,以及各变体的对比表格

以下是希尔排序的详细解析&#xff0c;包含基础实现、常见变体的完整代码示例&#xff0c;以及各变体的对比表格&#xff1a; 一、希尔排序基础实现 原理 希尔排序是插入排序的改进版本&#xff0c;通过分步缩小增量间隔&#xff0c;将数组分成多个子序列进行插入排序&#…

matlab 处理海洋数据并画图的工具包--ocean_data_tools

matlab 处理海洋数据并画图的工具包–ocean_data_tools matlab 处理海洋数据并画图的工具包–ocean_data_tools ocean_data_tools 简化了提取、格式化和可视化免费可用的海洋学数据的过程。虽然可以在线访问大量海洋学数据&#xff0c;但由于获取这些数据并将其格式化为可用数据…

MCP:AI时代的“万能插座”,开启大模型无限可能

摘要&#xff1a;Model Context Protocol&#xff08;MCP&#xff09;由Anthropic在2024年底开源&#xff0c;旨在统一大模型与外部工具、数据源的通信标准。采用客户端-服务器架构&#xff0c;基于JSON-RPC 2.0协议&#xff0c;支持stdio、SSE、Streamable HTTP等多种通信方式…

静态网页的开发

文章目录 基于 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架构对中断系统七个关键问题的详细解析&#xff0c;按照由浅入深的结构进行说明&#xff1a; 一、中断请求机制&#xff08;问题①&#xff09; 硬件基础&#xff1a; RISC-V通过CLINT&#xff08;Core Local Interrupter&#xff09;和PLIC&#xff08;Platfor…

uCOS3实时操作系统(任务切换和任务API函数)

文章目录 任务切换任务API函数 任务切换 C/OS-III 将 PendSV 的中断优先级配置为最低的中断优先级&#xff0c;这么一来&#xff0c; PendSV 异常的中断服务函数就会在其他所有中断处理完成后才被执行。C/OS-III 就是将任务切换的过程放到 PendSV 异常的中断服务函数中处理的。…

科学养生指南:解锁健康生活新方式

在快节奏的现代生活中&#xff0c;健康养生已成为人们关注的焦点。科学合理的养生方式&#xff0c;能帮助我们增强体质、预防疾病&#xff0c;享受更优质的生活。​ 饮食是健康养生的基石。遵循 “均衡饮食” 原则&#xff0c;每日饮食需包含谷类、蔬菜水果、优质蛋白质和健康…

第十四届蓝桥杯 2023 C/C++组 有奖问答

目录 题目&#xff1a; 题目描述&#xff1a; 题目链接&#xff1a; 思路&#xff1a; 核心思路&#xff1a; 思路详解&#xff1a; 代码&#xff1a; 代码详解&#xff1a; 题目&#xff1a; 题目描述&#xff1a; 题目链接&#xff1a; 蓝桥云课 有奖问答 思路&…

transformer注意力机制

单头注意力机制 import torch import torch.nn.functional as Fdef scaled_dot_product_attention(Q, K, V):# Q: (batch_size, seq_len, d_k)# K: (batch_size, seq_len, d_k)# V: (batch_size, seq_len, d_v)batch_size: 一次输入的句子数。 seq_len: 每个句子的词数。 d_mo…

QT 5.15 程序打包

说明&#xff1a; windeployqt 是 Qt 提供的一个工具&#xff0c;用于自动收集并复制运行 Qt 应用程序所需的动态链接库&#xff08;.dll 文件&#xff09;及其他资源&#xff08;如插件、QML 模块等&#xff09;到可执行文件所在的目录。这样你就可以将应用程序和这些依赖项一…

【路由交换方向IE认证】BGP选路原则之AS-Path属性

文章目录 一、路由器BGP路由的处理过程控制平面和转发平面选路工具 二、BGP的选路顺序选路的前提选路顺序 三、AS-Path属性选路原则AS-Path属性特性AS-Path管进还是管出呢&#xff1f;使用AS-Path对进本AS的路由进行选路验证AS-Path不接收带本AS号的路由 四、BGP邻居建立配置 一…