Java 使用SSPI的Kerberos模拟:无错误,但不工作

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_),从

我需要在Java应用服务器中模拟一个用户,并使用该用户的权限对IIS中的ASP应用程序执行http请求。为此,我正在尝试调整ApacheHttpClient的WindowsNegotiateScheme类。这使用JNA直接访问Windows SSPI身份验证函数,因此问题可能不是Java特有的。因为我需要一个“协议转换”,所以我没有可以模拟的用户密码,只有名称。但使用S4U功能,这应该是可能的

所有SSPI函数调用要么返回0(成功),要么返回590610(需要SEC_I_CONTINUE_),从不返回错误代码,代码运行,但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
,我意识到即使我使用不存在的用户也没关系,行为是一样的:
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样板代码。