在 Windows 系统管理、安全审计或自动化运维场景中,获取当前或指定 Windows 用户的身份信息是一项基础且关键的操作,无论是通过命令行工具、脚本编程还是系统 API,掌握多种方法灵活运用,能够极大提升技术人员的工作效率与问题排查能力,本文将深入探讨在 C 语言环境中获取 Windows 用户信息的多种技术实现,涵盖从基础的用户名查询到高级的安全令牌操作,帮助您全面理解底层原理与最佳实践。

在 Windows 系统中,用户信息通常与安全上下文(Security Context)紧密相关,而安全令牌(Security Token)则是这一上下文的核心载体。安全令牌包含用户的 SID(安全标识符)、权限组、用户权限等关键数据,是系统验证用户身份和资源访问权限的依据,获取用户信息的本质,往往就是对安全令牌的解析与提取。
通过环境变量获取当前用户名
环境变量是 Windows 系统中存储配置信息的简单方式,USERNAME 和 USERDOMAIN 分别记录了当前用户的用户名和所属域名,在 C 程序中,可通过 GetEnvironmentVariable 函数快速获取这些信息,适用于轻量级、无需高权限的场景。
#include <windows.h>
#include <stdio.h>
int main() {
char username[256];
char userdomain[256];
// 获取当前用户名
if (GetEnvironmentVariableA("USERNAME", username, sizeof(username)) != 0) {
printf("当前用户名: %s\n", username);
} else {
printf("获取用户名失败,错误码: %d\n", GetLastError());
}
// 获取当前用户域名
if (GetEnvironmentVariableA("USERDOMAIN", userdomain, sizeof(userdomain)) != 0) {
printf("当前用户域名: %s\n", userdomain);
} else {
printf("获取域名失败,错误码: %d\n", GetLastError());
}
return 0;
}
优点:实现简单,无需额外权限;
局限:仅能获取用户名和域名,无法获取更详细的用户信息(如 SID、用户描述等)。
使用 GetUserName API 获取当前用户名
Windows 提供了 GetUserName 函数,专门用于获取当前登录用户的名称,相比环境变量,这是更标准、更可靠的方式,同样适用于获取当前用户名的场景。

#include <windows.h>
#include <stdio.h>
int main() {
char username[256];
DWORD size = sizeof(username);
if (GetUserNameA(username, &size)) {
printf("当前用户名: %s\n", username);
} else {
printf("获取用户名失败,错误码: %d\n", GetLastError());
}
return 0;
}
注意:GetUserName 仅返回用户名,不包含域名;若需完整用户主体名称(UPN,如 user@domain.com),需结合其他方法。
解析安全令牌获取用户详细信息
要获取用户的 SID、所属组、权限等详细信息,必须操作安全令牌,核心步骤包括:获取进程或线程的令牌、打开令牌权限、查询令牌信息。
获取当前进程的令牌
通过 OpenProcessToken 函数可以打开当前进程的安全令牌,进而查询令牌信息。
#include <windows.h>
#include <stdio.h>
void PrintTokenInformation(HANDLE hToken) {
TOKEN_USER* tokenUser = NULL;
DWORD dwSize = 0;
// 首先查询所需缓冲区大小
if (!GetTokenInformation(hToken, TokenUser, NULL, 0, &dwSize)) {
if (GetLastError() != ERROR_INSUFFICIENT_BUFFER) {
printf("查询令牌信息大小失败,错误码: %d\n", GetLastError());
return;
}
}
// 分配缓冲区并获取令牌用户信息
tokenUser = (TOKEN_USER*)malloc(dwSize);
if (tokenUser == NULL) {
printf("内存分配失败\n");
return;
}
if (GetTokenInformation(hToken, TokenUser, dwSize, &dwSize)) {
// 将 SID 转换为字符串
char sidString[512];
if (ConvertSidToStringSidA(tokenUser>User.Sid, sidString)) {
printf("用户SID: %s\n", sidString);
} else {
printf("SID转换失败,错误码: %d\n", GetLastError());
}
} else {
printf("获取令牌用户信息失败,错误码: %d\n", GetLastError());
}
free(tokenUser);
}
int main() {
HANDLE hToken;
// 以 TOKEN_QUERY 权限打开当前进程令牌
if (OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY, &hToken)) {
PrintTokenInformation(hToken);
CloseHandle(hToken);
} else {
printf("打开进程令牌失败,错误码: %d\n", GetLastError());
}
return 0;
}
查询令牌中的用户组信息
通过 GetTokenInformation 并指定 TokenGroups 类别,可获取用户所属的所有组(包括本地组和域组)。

void PrintTokenGroups(HANDLE hToken) {
TOKEN_GROUPS* tokenGroups = NULL;
DWORD dwSize = 0;
if (!GetTokenInformation(hToken, TokenGroups, NULL, 0, &dwSize)) {
if (GetLastError() != ERROR_INSUFFICIENT_BUFFER) {
printf("查询令牌组信息大小失败,错误码: %d\n", GetLastError());
return;
}
}
tokenGroups = (TOKEN_GROUPS*)malloc(dwSize);
if (tokenGroups == NULL) {
printf("内存分配失败\n");
return;
}
if (GetTokenInformation(hToken, TokenGroups, dwSize, &dwSize)) {
printf("用户所属组数量: %u\n", tokenGroups>GroupCount);
for (DWORD i = 0; i < tokenGroups>GroupCount; i++) {
char sidString[512];
if (ConvertSidToStringSidA(tokenGroups>Groups[i].Sid, sidString)) {
printf("组SID: %s\n", sidString);
}
}
} else {
printf("获取令牌组信息失败,错误码: %d\n", GetLastError());
}
free(tokenGroups);
}
关键点:TOKEN_USER 结构包含用户 SID,而 TOKEN_GROUPS 包含组 SID;需注意内存分配与释放,避免泄漏。
查询指定用户的账户信息(如 SID、用户描述)
若需获取系统中指定用户(非当前用户)的详细信息,可通过 LookupAccountName 或 LookupAccountSid 函数,实现用户名与 SID 的双向查询,并获取账户的描述、类型等附加信息。
#include <windows.h>
#include <stdio.h>
void LookupAccountInfo(const char* accountName) {
SID* psid = NULL;
DWORD sidSize = 0;
DWORD domainSize = 0;
SID_NAME_USE eUse;
// 首先查询 SID 和域所需的大小
if (!LookupAccountNameA(NULL, accountName, NULL, &sidSize, NULL, &domainSize, &eUse)) {
if (GetLastError() != ERROR_INSUFFICIENT_BUFFER) {
printf("查询账户信息大小失败,错误码: %d\n", GetLastError());
return;
}
}
// 分配缓冲区
psid = (SID*)malloc(sidSize);
char domainName[MAX_PATH];
if (psid == NULL) {
printf("内存分配失败\n");
return;
}
if (LookupAccountNameA(NULL, accountName, psid, &sidSize, domainName, &domainSize, &eUse)) {
char sidString[512];
if (ConvertSidToStringSidA(psid, sidString)) {
printf("用户名: %s\n", accountName);
printf("域名: %s\n", domainName);
printf("SID: %s\n", sidString);
printf("账户类型: %d\n", eUse);
}
} else {
printf("查找账户失败,错误码: %d\n", GetLastError());
}
free(psid);
}
int main() {
LookupAccountInfo("Administrator"); // 替换为目标用户名
return 0;
}
应用场景:适用于需要验证用户是否存在、获取用户类型(如普通用户、管理员)的系统管理工具。
高级应用:模拟用户上下文
在需要以特定用户身份执行操作的场景(如服务程序模拟客户端用户),可通过 ImpersonateLoggedOnUser 或 DuplicateTokenEx 函数实现用户上下文模拟。
#include <windows.h>
#include <stdio.h>
BOOL ImpersonateUser(HANDLE hToken) {
// 复制令牌并启用模拟权限
HANDLE hImpersonationToken;
if (!DuplicateTokenEx(hToken, TOKEN_ALL_ACCESS, NULL, SecurityImpersonation, TokenImpersonation, &hImpersonationToken)) {
printf("复制令牌失败,错误码: %d\n", GetLastError());
return FALSE;
}
// 模拟用户
if (!ImpersonateLoggedOnUser(hImpersonationToken)) {
printf("模拟用户失败,错误码: %d\n", GetLastError());
CloseHandle(hImpersonationToken);
return FALSE;
}
printf("模拟用户成功,当前用户: ");
char username[256];
DWORD size = sizeof(username);
if (GetUserNameA(username, &size)) {
printf("%s\n", username);
}
// 恢复自身身份
RevertToSelf();
CloseHandle(hImpersonationToken);
return TRUE;
}
int main() {
HANDLE hToken;
if (OpenProcessToken(GetCurrentProcess(), TOKEN_ALL_ACCESS, &hToken)) {
ImpersonateUser(hToken);
CloseHandle(hToken);
}
return 0;
}
注意:模拟用户需谨慎操作,避免权限滥用;完成后务必调用 RevertToSelf 恢复原始身份。
常见问题解答(FAQ)
Q1:为什么 GetUserName 获取的用户名没有域名?
A1:GetUserName 仅返回本地用户名或 UPN 中的用户部分,若需完整域名信息,需通过 GetTokenInformation 查询令牌中的 TokenUser 信息,或使用 LookupAccountName 获取关联的域名。
Q2:如何判断当前用户是否为管理员?
A2:可通过 CheckTokenMembership 函数检查令牌中是否包含 Administrators 组的 SID,或调用 IsUserAnAdmin(需 shell32.dll 支持)。
Q3:GetTokenInformation 返回 ERROR_ACCESS_DENIED 是什么原因?
A3:通常是因为进程权限不足,未以 TOKEN_QUERY 或更高权限打开令牌,可通过 AdjustTokenPrivileges 获取必要权限,或以管理员身份运行程序。
Q4:如何获取用户的注册表配置文件路径?
A4:可通过 GetUserProfileDirectory 函数,传入用户令牌,获取其配置文件路径(如 C:\Users\Username)。
Q5:模拟用户上下文时需要注意什么?
A5:模拟用户会临时改变当前线程的安全上下文,需确保操作完成后调用 RevertToSelf 恢复;避免在多线程环境中同时模拟不同用户,导致权限混乱。
标签: C语言获取Windows当前用户名 Windows系统C语言获取用户名方法 C编程读取Windows用户名代码