【问题标题】:Java SPNEGO Authentication & Kerberos Constrained Delegation (KCD) to backend serviceJava SPNEGO 身份验证和 Kerberos 约束委派 (KCD) 到后端服务
【发布时间】:2016-09-28 09:43:12
【问题描述】:

我有一个 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();
        }
    }
}

身份验证运行良好,但我们还需要使用约束委派将用户凭据委派给后端服务 (Exchange EWS)。 在我们的 AD 中配置它时,看起来差别很小,但事实并非如此。看: AD delegation settings

此处描述了不同之处:msdn.microsoft.com/en-us/library/cc246080.aspx?f=255&MSPPError=-2147217396 通过无约束委托,我们可以在调用后端服务时简单地使用可用的委托凭证,这一切都很好:

GSSCredential delegatedCreds = gContext.getDelegCred()
SpnegoHttpURLConnection conn = new SpnegoHttpURLConnection(clientCreds);

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

现在我的问题...在我的身份验证之后,我基本上以经过身份验证的用户的用户名结束(参见上面代码中的“clientName”)。

这里真的需要使用S4U2self机制来冒充用户吗? 客户端刚刚向我们发送了它的 Kerberos 服务票证(包装在我无法解码的 SPNEGO 令牌中)。 理想情况下,我们应该能够使用该服务票证和我自己的服务的 TGT 来验证用户身份(使用 S4U2proxy 机制)? 但我不明白怎么做。

所以现在我想知道是否可以将我们的 SPNEGO 身份验证与 S4U2proxy 委托绑定在一起?

非常感谢您对此的任何意见。

【问题讨论】:

标签: java kerberos delegation


【解决方案1】:

我最近实际上一直在做这样的事情,但我使用的是 spring security kerberos。我在github上放了一个例子here。我发现我需要设置为使用您想要的约束委派和 S4U2Proxy 的关键是确保(如果您使用 Oracle/OpenJDK)在您的 JAAS 配置中设置 isInitiator=true 以便在调用 getDelegCred 时你会得到一个 Krb5ProxyCredential。见评论here。使用该凭据,您可以使用它为用户创建服务票证令牌,以用于您被限制以正常方式使用的服务,例如this

【讨论】:

  • 非常感谢@tellisnz,正是我需要的!
  • 这个答案对我有帮助。此外,gContext.getDelegCred() 仅在将 url 添加为浏览器安全部分中的受信任站点时才有效。否则在提交凭据到浏览器后获得的票证在委托的情况下不起作用。
  • @tellisnz 你有任何关于 S4U2Proxy 的例子吗?我需要使用用户的服务 1 的 TGS 来获取服务 2 的 TGS
【解决方案2】:

我对 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;
}

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;
            }
        }

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

我希望这会有所帮助。

谢谢,
布山

【讨论】:

    猜你喜欢
    • 2010-09-25
    • 1970-01-01
    • 2011-02-27
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-05-03
    • 2017-03-10
    • 1970-01-01
    相关资源
    最近更新 更多