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

时间:2016-09-28 09:43:13

标签: 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();
        }
    }
}

身份验证工作正常,但我们还要求使用约束委派将用户凭据委派给后端服务(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委派联系在一起?

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

2 个答案:

答案 0 :(得分:5)

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

答案 1 :(得分: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中提到的相似。您需要使用服务用户凭据登录。 “运行”方法的变化很小,如下所示:

            @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相同。只需传递这些模拟的凭据而不是委托的凭据即可。

我希望这会有所帮助。

谢谢,
hu山