Java SPNEGO身份验证和Kerberos约束委派(KCD)到后端服务

Hen*_*rik 4 java kerberos delegation

我有一个Java Web应用程序,可以在Windows Active Directory环境中对客户端进行SPNEGO身份验证。为了验证用户身份,我们使用了很好的旧SPNEGO SourceForge项目中的代码。

String encodedAuthToken = (String) credentials;
LOG.debug("Encoded auth token: " + encodedAuthToken);
byte[] authToken = B64Code.decode(encodedAuthToken);
GSSManager manager = GSSManager.getInstance();

try {
    Oid krb5Oid = new Oid("1.3.6.1.5.5.2");
    GSSName gssName = manager.createName(_targetName, null);
    GSSCredential serverCreds = manager.createCredential(gssName, GSSCredential.INDEFINITE_LIFETIME, krb5Oid, GSSCredential.INITIATE_AND_ACCEPT);
    GSSContext gContext = manager.createContext(serverCreds);

    if (gContext != null) { 
        while (!gContext.isEstablished()) {
            authToken = gContext.acceptSecContext(authToken, 0, authToken.length);
        }
        if (gContext.isEstablished()) {
            // Login succeeded!
            String clientName = gContext.getSrcName().toString();
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

身份验证效果很好,但我们还需要使用约束委派将用户凭据委派给后端服务(Exchange EWS)。在我们的广告中配置此功能时,看起来差别很小,但不是。请参阅: AD委托设置

此处描述了差异:msdn.microsoft.com/zh-cn/library/cc246080.aspx?f=255&MSPPError=-2147217396使用不受约束的委派,我们可以在调用后端服务时简单地使用可用的委派凭据,并且它将一切都很好:

GSSCredential delegatedCreds = gContext.getDelegCred()
SpnegoHttpURLConnection conn = new SpnegoHttpURLConnection(clientCreds);
Run Code Online (Sandbox Code Playgroud)

使用受约束的委派,我们无法访问用户TGT,并且似乎我们需要使用Java 8应该支持的MS-SFU(S4U2proxy)Kerberos扩展。我能找到的唯一示例就是这个示例:https : //github.com/ymartin59/java-kerberos-sfudemo(感谢Yves Martin!)

现在解决我的问题...验证之后,我基本上得到的是验证用户的用户名(请参见上面的代码中的“ clientName”)。

我们真的需要在这里使用S4U2self机制来模拟用户吗?客户端刚刚向我们发送了它的Kerberos服务票证(包裹在我无法解码的SPNEGO令牌中)。理想情况下,我们应该能够使用该服务票证和我自己的服务的TGT来验证用户身份(使用S4U2proxy机制)?但是我不知道如何。

因此,现在我想知道是否可以将我们的SPNEGO身份验证与S4U2proxy委托联系在一起?

非常感谢您对此的任何投入。

Bhu*_*kar 6

我对 Kerberos 约束委派进行了大量调查,最后我找到了使用 Java 执行此操作的正确方法。

域控制器上的设置

1) 无委派:不信任此帐户进行委派

您(服务用户)无法获得用户的委派凭据。这意味着您不能代表最终用户执行任何任务。您最多可以做的是接受来自用户(通常是浏览器)的传入票证,并通过将其传递给 KDC 来对其进行验证。作为响应,KDC 会告诉您此票证是针对哪个用户(或委托人)颁发的,但不会传递任何凭据。

2) 无约束委派:信任此帐户以委派给任何服务(仅限 Kerberos)

使用此选项,您(服务用户)可以获得用户的委派凭据。而且,你得到的是用户的TGT。使用此 TGT,您可以代表用户为任何服务请求 TGS(服务票证)。

3) 信任此帐户以委托给指定的服务(仅限 Kerberos)

在这里,您可以指定可以使用委派凭据的服务。这意味着启用此选项后,您将获得委托凭据,但是,您只能使用它们来获取指定服务的最终用户的 TGS。

另一个重要的点是,您必须拥有最终用户的 TGS(您的 Web 应用程序的最终用户的 TGS)。然后使用此 TGS,您可以向 KDC 请求最终用户的 TGS 以获取另一项服务。

4) 信任此帐户以委托给指定的服务(任何协议)

这也称为协议转换。在此选项中,您还需要指定可以代表用户向 KDC 请求 TGS 的服务。

您(服务用户)可以“模拟”最终用户,而无需最终用户提供任何类型的票证。您可以模拟任何用户,并获得指定服务的 TGS。 此选项对于无法进行最终用户交互的后台流程或日程安排非常有用。

Java 代码示例

1)获得委托凭证(在上述选项 2 和 3 中有用)

        // ---------------------------------
        // step 1: Login using service user credentials and get its TGT
        // ---------------------------------

        Subject subject = new Subject();
        Krb5LoginModule krb5LoginModule = new Krb5LoginModule();
        Map<String,String> optionMap = new HashMap<String,String>();

        optionMap.put("keyTab", "c:\\ticket\\sapuser.keytab");
        optionMap.put("principal", "HTTP/TEST"); // SPN you mapped to the service user while creating the keytab file
        optionMap.put("doNotPrompt", "true");
        optionMap.put("refreshKrb5Config", "true");
        optionMap.put("useTicketCache", "true");
        optionMap.put("renewTGT", "true");
        optionMap.put("useKeyTab", "true");
        optionMap.put("storeKey", "true");
        optionMap.put("isInitiator", "true"); // needed for delegation
        optionMap.put("debug", "true"); // trace will be printed on console

        krb5LoginModule.initialize(subject, null, new HashMap<String,String>(), optionMap);

        krb5LoginModule.login();
        krb5LoginModule.commit();


      // ---------------------------------
      // Step 2: Use login context of this service user, accept the kerberos token (TGS) coming from end user
      // ---------------------------------

public GSSCredential validateTicket(byte[] token) { 
    try {
        return Subject.doAs(this.serviceSubject, new KerberosValidateAction(token));
    }
    catch (PrivilegedActionException e) {
        throw new BadCredentialsException("Kerberos validation not successful", e);
    }
}


private class KerberosValidateAction implements PrivilegedExceptionAction<GSSCredential> {
    byte[] kerberosTicket;

    public KerberosValidateAction(byte[] kerberosTicket) {
        this.kerberosTicket = kerberosTicket;
    }

    @Override
    public GSSCredential run() throws Exception {
        byte[] responseToken = new byte[0];
        GSSName gssName = null;
        GSSContext context = GSSManager.getInstance().createContext((GSSCredential) null);

        while (!context.isEstablished()) {
            responseToken = context.acceptSecContext(kerberosTicket, 0, kerberosTicket.length);
            gssName = context.getSrcName();
            if (gssName == null) {
                throw new BadCredentialsException("GSSContext name of the context initiator is null");
            }
        }

        //check if the credentials can be delegated
        if (!context.getCredDelegState()) {
            SecurityLogger.getLogger().error("Credentials can not be delegated. Please make sure that delegation is enabled for the service user. This may cause failures while creating Kerberized application.");
            return null;
        }

        // only accepts the delegated credentials from the calling peer
        GSSCredential clientCred = context.getDelegCred(); // in case of Unconstrained Delegation, you get the end user's TGT, otherwise TGS only
        return clientCred;
    }
}

    // ---------------------------------
    // Step 3: Initiate TGS request for another service using delegated credentials obtained in previous step
    // ---------------------------------
    private Object getServiceTicket(GSSCredential clientCred) throws PrivilegedActionException {
    Object o = Subject.doAs(new Subject(), (PrivilegedExceptionAction<Object>) () -> {

        GSSManager manager = GSSManager.getInstance();
        Oid SPNEGO_OID = new Oid("1.3.6.1.5.5.2");
        Oid KRB5_PRINCIPAL_OID = new Oid("1.2.840.113554.1.2.2.1");
        GSSName servicePrincipal = manager.createName("HTTP/TEST", KRB5_PRINCIPAL_OID); // service to which the service user is allowed to delegate credentials
        ExtendedGSSContext extendedContext = (ExtendedGSSContext) manager.createContext(servicePrincipal, SPNEGO_OID, clientCred, GSSContext.DEFAULT_LIFETIME);
        extendedContext.requestCredDeleg(true);

        byte[] token = new byte[0];
        token = extendedContext.initSecContext(token, 0, token.length); // this token is the end user's TGS for "HTTP/TEST" service, you can pass this to the actual HTTP/TEST service endpoint in "Authorization" header.

        return token;
    });
    return o;
}
Run Code Online (Sandbox Code Playgroud)

2) 获取模拟凭据(在上述选项 4 中很有用)

初始步骤与上面步骤 1 中提到的类似。您需要使用服务用户凭据登录。'run' 方法有一些小的变化,如下所示:

            @Override
            public GSSCredential run() throws Exception {
                GSSName gssName = null;
                GSSManager manager = GSSManager.getInstance();
                GSSCredential serviceCredentials = manager.createCredential(GSSCredential.INITIATE_ONLY);
                GSSName other = manager.createName("bhushan", GSSName.NT_USER_NAME, kerberosOid); // any existing user
                GSSCredential impersonatedCredentials = ((ExtendedGSSCredential) serviceCredentials).impersonate(other);
                return impersonatedCredentials;
            }
        }
Run Code Online (Sandbox Code Playgroud)

您可以看到在这种情况下我们不需要用户的 TGS。
代表用户为其他服务获取 TGS,与上面代码中给出的步骤 3 中提到的相同。只需传递这些 impersonatedCredentials 而不是 delegatedCredentials。

我希望这会有所帮助。

谢谢,
布山


小智 5

实际上,我最近一直在做这样的事情,但是我正在使用spring security kerberos。我在这里在github上举了一个例子。我发现需要设置以使用像您想要的约束委派和S4U2Proxy的关键是要确保(如果您使用的是Oracle / OpenJDK)isInitiator=true在JAAS Config中进行设置,以便在调用getDelegCred时返回一个Krb5ProxyCredential。在这里查看评论。有了该凭证,您就可以使用它为用户(代表用户)创建服务票证令牌,以限制您按常规方式使用服务,例如this