当前位置: 首页 > ds >正文

在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搜索条目并解析得到密码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 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);
}

【完】

http://www.xdnf.cn/news/214.html

相关文章:

  • 【MySQL】Ubuntu下C++连接MySQL
  • C# 点击导入,将需要的参数传递到弹窗的页面
  • C#/.NET/.NET Core拾遗补漏合集(25年4月更新)
  • DBeaver连接hive
  • Linux:简单指令(二)
  • Chromium 134 编译指南 macOS篇:编译流程(五)
  • 【20】Strongswan sa ——IKE_SA set_state|process_message|
  • QT常见显示类控件及其属性
  • 使用 Docker 安装 Elastic Stack 并重置本地密码
  • 方案解读:虚拟电厂标杆项目整体建设方案【附全文阅读】
  • [经验总结]Linux双机双网卡Keepalived高可用配置及验证细节
  • IcePlayer音乐播放器项目分析及学习指南
  • 软考高级-系统架构设计师 论文范文参考(二)
  • vscode的一些使用技巧记录
  • Qt中读写结构体字节数据
  • 若依框架修改左侧菜单栏默认选中颜色
  • 【工具】文件传输工具croc
  • WebSocket:实现实时双向通信的技术
  • 密码学中的盐值是什么?
  • 获取视频封面
  • AIGC-几款本地生活服务智能体完整指令直接用(DeepSeek,豆包,千问,Kimi,GPT)
  • Pytorch的常规操作
  • C++11新特性
  • 【数据结构】第四弹——LinkedList与链表
  • 【重学Android】1.关于@Composer注解的一点知识笔记
  • ceph weight 和 reweight 的区别
  • MDA测量数据查看器【内含工具和源码地址】
  • Python 创意:AI 图像生成
  • intra-mart 开发 总结
  • 非比较排序——计数排序