一、前言
??原本以為在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);
}
【完】