如何修改服务线程的Access Token

本文介绍了解决Vista系统中System用户进程无法枚举网上邻居的问题。通过更改线程的AccessToken为当前登录用户的explorer.exe进程的AccessToken,使线程能够以用户身份执行特定任务。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

为了解决vista下面,system用户的进程无法枚举网上邻居,所以我开一个新的线程,将新的线程的access token修改成当前登陆用户的explorer.exe进程的acess token。

如何修改线程的Access Token

1.  什么是Access Token

Access Token用来标识一个用户,其中包括用户的SID和用户所属组的SID,还包括这个用户和所属用户组的所拥有的权限列表。

当用户输入用户名和密码登陆到Windows之后,Windows会创建一个Acess Token,用来标识这个用户。 在这个用户下创建的进程都将得到一份这个Access Token的备份。当进程需要访问一个可以加密的对象(如文件,事件等)时,windows利用这个Access Token来跟对象的Security Descriptor进行比较,以确认这个进程是否有权限来访问这个对象。(关于SIDSecurity Descriptor请参阅其他资料)

 

2.  线程的Access Token

进程被创建时,从系统中得到Access Token是属于进程,线程刚开始创建的时候并没有Access Token。我们可以通过一个叫Impersonate的方法赋予线程一个Access Token,这样就使得线程具有跟进程不同的Access Token,用来完成一些使用进程的Access Token无法完成的任务。

Impersonate一般用于Client/Server模式中,Client发送一个请求访问服务期内的资源,如果Server接到请求后直接去访问资源的话,可能会导致Server无法控制Client可以访问哪些资源。Windows提供了Impersonate这种解决方案,Server的一个线程可以Impersonate Client,这样这个线程就将以Client的身份来访问资源,从而可以直接控制Client是否可以访问资源。

 

3.  Impersonate的方法

有以下几个函数可以实现Impersonate

a)         DdeImpersonateClient 用于在DDEImpersonate

b)        ImpersonateNamedPipeClient 用于在NamedPipeImpersonate

c)         ImpersonateLoggedOnUser 利用Access TokenImpersonate

d)        ImpersonateSelf           利用进程的Access TokenImpersonate

e)         SetThreadToken   使用Access TokenImpersonate其他的线程

f)         RpcImpersonateClient 用于在RPCImpersonate

g)        ImpersonateSecurityContext      用于在security package Impersonate

 

4.  利用ImpersonateLoggedOnUser来实现Impersonate

其他的一些Impersonate的方法大都是使用在特定的环境下,ImpersonateLoggedOnUser则仅需要有用户的Access Token就可以。

获取用户的Access Token有两种方法

a)       利用LogonAsUser,通过输入用户名,密码,域信息来获取用户的Access Token

b)      利用OpenProcessToken来直接进程的Access Token

 

5.  例子

下面一个利用ImpersonateLoggedOnUser来实现Impersonate的例子。BOOL Impersonate(LPCSTR szFileName)函数中,输入参数为需要Impersonate的进程名,比如说explorer.exe

Int main()

{     

OSVERSIONINFO os = {sizeof(OSVERSIONINFO)};

GetVersionEx(&os);

if(os.dwMajorVersion >= 6 ){//Only Impersonate when it is vista and later

        Impersonate(_T("explorer.exe"));

        //now this thread have the Access Token of explorer.exe

}

}

 

BOOL Impersonate(LPCSTR szFileName)

{

BOOL bResult = FALSE;

HANDLE hProcess = NULL;

HANDLE hToken = NULL;

 

for(DWORD dw=0; dw<0xFFFFFF; Sleep((++dw)*100)){//loop until success

        DWORD dwSessionID = 0;//GetActiveConsoleSessionId();

        if(!ProcessIdToSessionId(GetCurrentProcessId(), &dwSessionID))

               continue;

        DWORD dwPID = GetPIDOFSpecifiedSession(dwSessionID, szFileName, NULL);

        hProcess = OpenProcess(PROCESS_ALL_ACCESS,

               TRUE, dwPID);

        if(hProcess == NULL)

               continue;

       

        if(!OpenProcessToken(hProcess,

               TOKEN_READ | TOKEN_DUPLICATE | TOKEN_QUERY|TOKEN_ASSIGN_PRIMARY ,

               &hToken ))

        {

               CloseHandle(hProcess);

               hProcess = NULL;

               DbgPrint("In CreateProcessAsActiveWinlogon, Open Process Failed");

               continue;

        }

 

        bResult = ImpersonateLoggedOnUser(hToken);

        break;

}

return bResult;

}

 

DWORD GetActiveConsoleSessionId()

{

DWORD dwResult = 0;

HINSTANCE hKernal32 = NULL;

do {

        OSVERSIONINFO os = {sizeof(OSVERSIONINFO)};

        GetVersionEx(&os);

        if(os.dwMajorVersion == 5 && os.dwMinorVersion==0){//2000

               break;

        }

        else{//xp and vista

               typedef DWORD (WINAPI* PWTSActiveSessionID)();

               hKernal32 = LoadLibrary(_T("kernel32.dll"));

               if(hKernal32 == NULL)

                      break;

 

               PWTSActiveSessionID pWTSActiveSessionID =

                      (PWTSActiveSessionID)GetProcAddress(hKernal32,

                      _T("WTSGetActiveConsoleSessionId"));

               if(pWTSActiveSessionID == NULL)

                      break;

               dwResult = pWTSActiveSessionID();

        }

} while(FALSE);

if(hKernal32 != NULL)

        FreeLibrary(hKernal32);

return dwResult;    

}

 

 

DWORD GetPIDOFSpecifiedSession(DWORD dwSessionID, LPCTSTR lpProcessName,

                                                  LPCTSTR lpUserName)

{

DWORD dwRet = 0;

PWTS_PROCESS_INFO lpProcessInfo = NULL;

do {

        if (lpProcessName==NULL)

               break;

       

        DWORD dwProcessCount = 0;

        if(!WTSEnumerateProcesses(WTS_CURRENT_SERVER_HANDLE, 0, 1, &lpProcessInfo,

               &dwProcessCount))

        {//This functions will fail if it is run in windows 2000

        //and in windows 2000, I can just using the process id of current process instead

               DbgPrint("In CCheckPIN::GetPIDOFSpecifiedSession, WTSEnumerateProcesses returns FALSE,\

                      Last Error:%d", GetLastError());

               dwRet = GetCurrentProcessId();//using current process id

               break;

        }

       

        // dump each process description

        for (DWORD dwIndex = 0; dwIndex < dwProcessCount;

        dwIndex++)

        {

               if(lpProcessInfo[dwIndex].SessionId != dwSessionID)

                      continue;

              

               if(lstrcmpi(lpProcessInfo[dwIndex].pProcessName, lpProcessName) != 0 )

                      continue;

              

               if(lpUserName != NULL){

                      TCHAR szUser[MAX_PATH] = {0};

                      DWORD chUser = MAX_PATH;

                      TCHAR szDomain[MAX_PATH] = {0};

                      DWORD chDomain = MAX_PATH;

                      SID_NAME_USE   snu;

                     

                      if(!LookupAccountSid(NULL, lpProcessInfo[dwIndex].pUserSid, szUser,

                             &chUser, szDomain, &chDomain, &snu))

                      {

                             DbgPrint("LookupAccountSid returns FALSE, LastError:%d", GetLastError());

                             break;

                      }

                      DbgPrint("szUser:%s, lpUserName:%s", szUser, lpUserName);

                      if(lstrcmpi(szUser, lpUserName) != 0)

                      {

                             break;

                      }

               }

              

               dwRet = lpProcessInfo[dwIndex].ProcessId;

               break;

        }//end of for loop

}while(FALSE);

 

if(lpProcessName != NULL){

        WTSFreeMemory(lpProcessInfo);

}

return dwRet;

}

<think>好的,我需要解决用户提出的关于OAuth 2.0中access token在2小时内重复请求的问题。首先,我得理解用户的问题场景。用户可能遇到的情况是,当access token的有效期是2小时,如果在短时间内多次请求新的access token,可能会导致资源浪费或授权服务器压力增大,甚至可能触发安全机制。用户希望找到解决方案来避免这种情况。 根据提供的引用资料,特别是引用[4]和[5],其中提到了access token和refresh token的使用。微信网页授权方案中提到,access token有效期2小时,refresh token有效期30天,当access token过期后可以用refresh token来刷新。同时,引用[5]讨论了如何实现token刷新功能,可能涉及后端存储和管理token。 接下来,我需要考虑可能的解决方案。首先,标准的OAuth 2.0流程中,客户端在access token过期后应使用refresh token来获取新的access token,而不是重新走完整的授权流程。这样可以减少重复请求。但用户提到的是在2小时内重复请求的问题,可能是指客户端在access token未过期时就频繁请求新的token,或者多个客户端实例同时请求导致重复。 可能的解决方案包括: 1. **Token缓存与单例模式**:在客户端维护一个全局的access token实例,确保同一时间只有一个token被使用,避免多个请求同时触发token刷新。这需要确保线程安全,比如使用互斥锁或同步机制。 2. **后端集中管理**:如引用[4]和[5]提到的,使用Redis等缓存系统存储token,并设置过期时间。所有客户端实例都从缓存中获取token,当token即将过期时,由后端统一刷新,避免各个客户端各自刷新导致的重复请求。 3. **请求队列机制**:当检测到token即将过期时,将后续请求放入队列,等待token刷新完成后再处理,防止在刷新过程中多个请求同时触发刷新。 4. **时间缓冲策略**:在token实际过期前预留一个时间窗口(比如提前5分钟刷新),避免在过期瞬间大量请求同时发起刷新。 5. **分布式锁**:在分布式系统中,使用分布式锁确保只有一个节点能执行token刷新操作,其他节点等待刷新完成后再获取新token。 需要考虑这些方案的实现细节和潜在问题。例如,使用Redis存储token时,如何确保高可用性和一致性;分布式锁的实现方式(如RedLock);以及如何处理网络延迟或失败重试的情况。 此外,引用[4]提到用Redis设置键值对的过期时间,这可以用来自动管理token的生命周期。当客户端发现Redis中没有token记录时,就知道需要重新获取或刷新。同时,刷新token后需要更新Redis中的存储,并设置新的过期时间。 可能还需要参考OAuth 2.0的最佳实践,比如避免频繁刷新token,合理设置token的有效期,以及使用适当的退避策略(如指数退避)来处理失败情况。 最后,验证这些方案的有效性,比如通过压力测试检查在高并发下是否仍能避免重复请求,以及如何监控token的使用情况来优化策略。</think>### OAuth 2.0中解决access token 2小时内重复请求的方案 针对access token在有效期内被重复请求的问题,以下是逐步解决方案: --- #### **1. 客户端缓存与单例模式** - **问题**:多个客户端实例可能同时检测到token即将过期,各自发起刷新请求。 - **解决方案**: - 在客户端使用**单例模式**全局管理access token,确保所有请求共享同一token实例。 - 加锁机制(如互斥锁)保证刷新操作原子性: ```python import threading class TokenManager: _instance = None _lock = threading.Lock() def __new__(cls): with cls._lock: if not cls._instance: cls._instance = super().__new__(cls) cls.token = None return cls._instance def refresh_token(self): # 调用授权服务器刷新token pass ``` --- #### **2. 后端集中存储与刷新控制** - **问题**:分布式系统中多个服务实例可能独立刷新token。 - **解决方案**: - **Redis缓存管理**(引用[4]): - 将access token存储于Redis,并设置过期时间(如2小时)。 - 所有服务实例从Redis读取token,避免重复刷新。 - 刷新逻辑: 1. 检查Redis中token剩余时间(TTL)。 2. 若剩余时间小于阈值(如5分钟),触发刷新。 3. 使用**分布式锁**(如RedLock)确保仅一个实例执行刷新。 ```java // 伪代码示例:使用Redis的SETNX实现分布式锁 String lockKey = "token_refresh_lock"; String tokenKey = "access_token"; if (redis.ttl(tokenKey) < 300) { // 剩余时间小于5分钟 if (redis.setnx(lockKey, "locked") == 1) { try { // 刷新token并更新Redis String newToken = refreshFromAuthServer(); redis.setex(tokenKey, 7200, newToken); // 2小时有效期 } finally { redis.del(lockKey); } } } ``` --- #### **3. 请求队列与异步刷新** - **问题**:高并发场景下,多个请求同时触发刷新。 - **解决方案**: - **队列缓冲**:将需要token的请求暂存至队列,等待刷新完成后统一处理。 - **异步刷新**:由后台任务定期检查token有效期并提前刷新,减少实时请求的压力。 --- #### **4. 时间缓冲策略** - **优化点**:避免在token过期临界点集中刷新。 - 在客户端或服务端设置**时间缓冲**(如提前10%有效期触发刷新)。 - 例如:2小时有效期的token,在1小时50分钟时主动刷新。 --- #### **5. 安全与容错设计** - **Refresh Token保护**(引用[4]): - refresh_token存储于安全介质(如数据库或加密缓存)。 - 限制refresh_token使用频率,防止滥用。 - **失败重试与回退**: - 若刷新失败,采用指数退避策略重试。 - 若refresh_token失效,强制用户重新授权(引用[4])。 --- ###
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值