我需要在Java应用程序服务器中模拟用户,并使用该用户的权限对IIS中的ASP应用程序执行http请求。我正在尝试为此目的调整Apache HttpClient的WindowsNegotiateScheme类。这使用JNA直接访问Windows SSPI身份验证功能,因此问题可能不是特定于Java。由于我需要“协议转换”,我没有用户的密码来模拟可用,只是名称。但是使用S4U功能,这应该是可能的。
所有SSPI函数调用返回0(成功)或590610(SEC_I_CONTINUE_NEEDED),从不出现错误代码,代码运行,但http请求到达ASP端,用户运行应用程序服务器,无论我是否在我的工作站上以我的用户ID在本地运行它,或者在应用程序服务器(恰好是运行IIS / ASP应用程序的机器)上运行它,它作为本地系统运行。
代码的相关部分如下:
public Header authenticate(Credentials credentials, HttpRequest request, HttpContext context) throws AuthenticationException {
final String response;
if (clientCred == null) { // first time
boolean impersonate = ...; // logic not relevant here
try {
final String username = impersonate ? credentials.getUserPrincipal().getName() : CurrentWindowsCredentials.getCurrentUsername();
final Sspi.TimeStamp lifetime = new Sspi.TimeStamp();
this.clientCred = new Sspi.CredHandle();
int credUse = impersonate ? (Sspi.SECPKG_CRED_OUTBOUND | Sspi.SECPKG_CRED_INBOUND) : Sspi.SECPKG_CRED_OUTBOUND;
int rc = Secur32.INSTANCE.AcquireCredentialsHandle(username,
scheme, credUse, null, null, null, null,
clientCred, lifetime);
if (WinError.SEC_E_OK != rc) {
throw new Win32Exception(rc);
}
if(impersonate) {
impersonationContext = getLocalContext(clientCred);
rc = Secur32.INSTANCE.ImpersonateSecurityContext(impersonationContext);
} else {
impersonationContext = null;
}
String targetSpn = getServicePrincipalName(context);
response = getToken(null, null, targetSpn);
} catch (RuntimeException ex) {
...
}
} else {
... // second round
}
... // build header form response and return it
}
方法getLocalContext()
旨在为模拟用户获取服务本身的上下文。它看起来如下:
private static Sspi.CtxtHandle getLocalContext(final Sspi.CredHandle initialCredentials) {
final IntByReference attr = new IntByReference();
String localSpn = "HOST/" + Kernel32Util.getComputerName().toLowerCase();
Sspi.CtxtHandle clientContext = null;
Sspi.CtxtHandle serverContext = null;
Sspi.SecBufferDesc clientToServerToken;
Sspi.SecBufferDesc serverToClientToken = null;
while(true) {
// get clientContext and clientToServerToken:
Sspi.CtxtHandle outClientContext = (clientContext != null) ? clientContext : new Sspi.CtxtHandle();
clientToServerToken = new Sspi.SecBufferDesc(Sspi.SECBUFFER_TOKEN, Sspi.MAX_TOKEN_SIZE);
int rc = Secur32.INSTANCE.InitializeSecurityContext(initialCredentials,
clientContext, localSpn, Sspi.ISC_REQ_DELEGATE | Sspi.ISC_REQ_MUTUAL_AUTH, 0,
Sspi.SECURITY_NATIVE_DREP, serverToClientToken, 0, outClientContext, clientToServerToken,
attr, null);
clientContext = outClientContext; // make them same for next round
switch (rc) {
case WinError.SEC_I_CONTINUE_NEEDED:
break;
case WinError.SEC_E_OK:
return serverContext != null ? serverContext : clientContext;
default:
freeContexts(clientContext, serverContext);
throw new Win32Exception(rc);
}
// get serverContext and serverToClientToken:
Sspi.CtxtHandle outServerContext = (serverContext != null) ? serverContext : new Sspi.CtxtHandle();
serverToClientToken = new Sspi.SecBufferDesc(Sspi.SECBUFFER_TOKEN, Sspi.MAX_TOKEN_SIZE);
rc = Secur32.INSTANCE.AcceptSecurityContext(initialCredentials,
serverContext, clientToServerToken, Sspi.ISC_REQ_DELEGATE | Sspi.ISC_REQ_MUTUAL_AUTH,
Sspi.SECURITY_NATIVE_DREP, outServerContext, serverToClientToken, attr, null);
serverContext = outServerContext; // make them same for next round
switch (rc) {
case WinError.SEC_I_CONTINUE_NEEDED:
break;
case WinError.SEC_E_OK:
return serverContext;
default:
freeContexts(clientContext, serverContext);
throw new Win32Exception(rc);
}
}
}
我发现它在返回之前会运行两轮(InitializeSecurityContext
,AcceptSecurityContext
,InitializeSecurityContext
,AcceptSecurityContext
)。
作为参考,getToken
方法只是从原始的WinHttpClient WindowsNegotiateScheme
类复制而来:
String getToken(
final CtxtHandle continueCtx,
final SecBufferDesc continueToken,
final String targetName) {
final IntByReference attr = new IntByReference();
final SecBufferDesc token = new SecBufferDesc(
Sspi.SECBUFFER_TOKEN, Sspi.MAX_TOKEN_SIZE);
sspiContext = new CtxtHandle();
final int rc = Secur32.INSTANCE.InitializeSecurityContext(clientCred,
continueCtx, targetName, Sspi.ISC_REQ_DELEGATE | Sspi.ISC_REQ_MUTUAL_AUTH, 0,
Sspi.SECURITY_NATIVE_DREP, continueToken, 0, sspiContext, token,
attr, null);
switch (rc) {
case WinError.SEC_I_CONTINUE_NEEDED:
continueNeeded = true;
break;
case WinError.SEC_E_OK:
dispose(); // Don't keep the context
continueNeeded = false;
break;
default:
dispose();
throw new Win32Exception(rc);
}
return Base64.encodeBase64String(token.getBytes());
}
我在调试器中检查过用于模拟的用户名实际上类似于DOMAINNAME\johndoe
,我意识到如果我使用的是非xx用户,行为也是一样的:{{ 1}}返回0(= SEC_E_OK)。这告诉我,这个方法不是针对域控制器检查用户名,而是仅检查其参数的语法,但是为什么对AcquireCredentialsHandle
的初始调用不会抱怨如果第一个参数是不存在的凭据用户?或者该方法在某种程度上忽略了它的第一个参数,但为什么呢?
假设我有一个上下文供用户通过InitializeSecurityContext
来电进行模拟,而忽略对AcquireCredentialsHandle
和getLocalContext
的调用也不起作用。
我发现没有太多关于模拟纯SSPI使用的文档。我发现的大多数样本(如this)依赖于.net类ImpersonateSecurityContext
,这在纯SSPI接口中不可用。并且该类的source code看起来并不像是由几个SSPI调用组成,而是引用了许多其他.net基础结构。
我认为Windows操作系统模拟可能无法与Java正常协作,但根据this question,Windows模拟是每个线程,根据this question,Java使用操作系统线程。 / p>
我如何让模仿工作,或者你至少有一些提示要尝试什么?
答案 0 :(得分:1)
您的代码永远不会执行您所描述的内容。您的代码段仅执行凭据委派(无约束委派),其中转发的TGT嵌入在安全上下文中。由于您需要协议转换(S4U2Self),因此您需要使用LsaLogonUser
致电KERB_S4U_LOGON
。
阅读this博文。在C中会有很多痛苦。永远不要理解为什么GSS-API会如此简单,但在Windows上会如此痛苦。
祝你好运。