Java 使用SSPI的Kerberos模拟:无错误,但不工作
我需要在Java应用服务器中模拟一个用户,并使用该用户的权限对IIS中的ASP应用程序执行http请求。为此,我正在尝试调整ApacheHttpClient的WindowsNegotiateScheme类。这使用JNA直接访问Windows SSPI身份验证函数,因此问题可能不是Java特有的。因为我需要一个“协议转换”,所以我没有可以模拟的用户密码,只有名称。但使用S4U功能,这应该是可能的 所有SSPI函数调用要么返回0(成功),要么返回590610(需要SEC_I_CONTINUE_),从不返回错误代码,代码运行,但http请求到达ASP端,用户运行应用程序服务器,无论我是在工作站上以用户id本地运行,还是在应用程序服务器上运行(这恰好是运行IIS/ASP应用程序的计算机),它作为本地系统运行 守则的有关部分如下:Java 使用SSPI的Kerberos模拟:无错误,但不工作,java,kerberos,jna,impersonation,sspi,Java,Kerberos,Jna,Impersonation,Sspi,我需要在Java应用服务器中模拟一个用户,并使用该用户的权限对IIS中的ASP应用程序执行http请求。为此,我正在尝试调整ApacheHttpClient的WindowsNegotiateScheme类。这使用JNA直接访问Windows SSPI身份验证函数,因此问题可能不是Java特有的。因为我需要一个“协议转换”,所以我没有可以模拟的用户密码,只有名称。但使用S4U功能,这应该是可能的 所有SSPI函数调用要么返回0(成功),要么返回590610(需要SEC_I_CONTINUE_),从
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
方法只是从原始WinHttpClientWindowsNegotiateScheme
类复制而来:
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
,我意识到即使我使用不存在的用户也没关系,行为是一样的:AcquireCredentialsHandle
返回0(=SEC_E__确定)。这告诉我,要么该方法不根据域控制器检查用户名,而只检查其参数的语法,但为什么对InitializeSecurityContext
的初始调用不抱怨它的第一个参数是不存在的用户的凭据?要么该方法不知何故忽略了它的第一个参数,但是为什么?
假设我已经从AcquireCredentialsHandle
调用中为用户模拟了一个上下文,并且省略了对getLocalContext
和ImpersonateSecurityContext
的调用也不起作用
我发现没有太多关于模拟的纯SSPI用法的文档依赖于.net类WindowsIdentity
,该类在纯SSPI接口中不可用。该类的名称看起来并不只是由几个SSPI调用组成,而是引用了许多其他.net基础结构
我认为可能Windows操作系统模拟没有与Java正确协作,但根据,Windows模拟是每个线程,根据,Java使用操作系统线程
我如何才能让模拟工作,或者您至少有一些提示可以尝试什么?您的代码永远不会执行您所描述的操作。您的代码片段只执行凭证委派(无约束委派),其中转发的TGT嵌入到安全上下文中。因为您需要协议转换(S4U2Self),您需要使用 阅读博客文章。在C语言中会有很多痛苦。我从来都不明白为什么GSS-API如此简单,但在Windows上却如此痛苦
祝你好运。谢谢你指向这个博客。似乎很难找到纯粹依赖Win32 API(SSPI)的S4U的工作示例你的回答让我找到了新的谷歌搜索术语,我发现在@FrankPl使用SSPI的C中实现了这个P/Invoke对我来说很好。我会先试试C,看看它是否管用。不过,在纯C中,有
WindowsIdentity
,它将为你提供S4U。没有C样板代码。