关于使用USER_INFO_3::usri3_password_age可靠获取用户密码最后修改时间的Win32 API问题
嘿,这个问题我之前帮朋友排查过,太懂这种差1秒的糟心了!咱们先把问题的根儿找出来,再给你靠谱的解决方案。
首先你碰到的1秒误差,核心原因是竞态条件(race condition):你先调用NetUserGetInfo()拿到usri3_password_age,然后再调用time(NULL)获取当前时间——这两个系统调用不是原子操作,中间哪怕只过了0.5秒,等你调用time(NULL)的时候可能刚好跨到下一秒,自然计算出来的密码修改时间就差了1秒。说白了,time()和NetUserGetInfo()本身都没做错,错在你用了两个不同时间点的数值来做计算。
那该怎么解决?给你两个方案,都符合你要的LDAP-agnostic的要求:
方案一:直接用USER_INFO_10获取绝对时间戳(最推荐)
其实Win32 API早就给你准备好了更直接的方式——用NetUserGetInfo()的信息级别10(LEVEL 10),对应的USER_INFO_10结构体里有个usri10_password_last_set字段,这是一个FILETIME结构,直接存储了密码最后修改的绝对时间戳,完全不用你自己计算,从根源上避免了时间差的问题。
举个简单的代码例子:
USER_INFO_10 *pUserInfo10 = NULL; NET_API_STATUS status = NetUserGetInfo( NULL, // 本地机器填NULL,域用户填域名或DC地址 L"目标用户名", // 要查询的用户名 10, // 指定信息级别为10 (LPBYTE*)&pUserInfo10 ); if (status == NERR_Success && pUserInfo10 != NULL) { SYSTEMTIME st; // 把FILETIME转成易读的SYSTEMTIME格式 if (FileTimeToSystemTime(&pUserInfo10->usri10_password_last_set, &st)) { // 这里可以根据需求转成本地时间或者UTC时间 wprintf(L"密码最后修改时间:%04d-%02d-%02d %02d:%02d:%02d\n", st.wYear, st.wMonth, st.wDay, st.wHour, st.wMinute, st.wSecond); } // 别忘了释放API分配的内存 NetApiBufferFree(pUserInfo10); } else { wprintf(L"查询失败,错误码:%d\n", status); }
这个方案兼容性很好,从Windows 2000开始就支持,不管是本地用户还是域用户都能用,完全不用碰LDAP,完美符合你的要求。
方案二:如果必须用USER_INFO_3,优化时间同步
要是你因为某些限制不得不使用USER_INFO_3,那就要想办法把获取usri3_password_age和时间戳的操作尽量同步。比如在调用NetUserGetInfo()的前后都获取高精度时间,取中间值来计算:
FILETIME ftBefore, ftAfter; USER_INFO_3 *pUserInfo3 = NULL; // 调用前先记时间 GetSystemTimePreciseAsFileTime(&ftBefore); NET_API_STATUS status = NetUserGetInfo(NULL, L"目标用户名", 3, (LPBYTE*)&pUserInfo3); // 调用后立刻记时间 GetSystemTimePreciseAsFileTime(&ftAfter); if (status == NERR_Success && pUserInfo3 != NULL) { // 把FILETIME转成time_t格式(可自行实现转换函数或调用系统API) time_t timeBefore = FileTimeToTimeT(&ftBefore); time_t timeAfter = FileTimeToTimeT(&ftAfter); // 取调用NetUserGetInfo的中间时刻时间 time_t apiCallTime = (timeBefore + timeAfter) / 2; // 计算密码修改时间 time_t passwordChangeTime = apiCallTime - pUserInfo3->usri3_password_age; // 后续处理... NetApiBufferFree(pUserInfo3); }
不过这个方案还是不如方案一省心,毕竟还要自己处理时间转换,而且理论上还是存在极微小的误差,只是被缩小到可以忽略的程度。
最后再补一句:你完全不用换其他大的API体系,NetUser系列就足够解决问题,关键是选对信息级别,别再用相对时间自己算了。
备注:内容来源于stack exchange,提问作者Opux

