简体   繁体   English

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

[英]Java SPNEGO Authentication & Kerberos Constrained Delegation (KCD) to backend service

I have a Java web application which do SPNEGO authentication of clients in a Windows Active Directory environment. 我有一个Java Web应用程序,它在Windows Active Directory环境中对客户端进行SPNEGO身份验证。 To authenticate the user we use code from the good old SPNEGO SourceForge project. 为了验证用户,我们使用了旧的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();
        }
    }
}

The authentication works good but we also have a requirement to delegate the user credentials to a back-end service (Exchange EWS), using constrained delegation. 身份验证工作正常,但我们还要求使用约束委派将用户凭据委派给后端服务(Exchange EWS)。 When configuring this in our AD it looks like a small difference, but it's not. 在我们的AD中配置它时,它看起来像一个小差异,但事实并非如此。 See: AD delegation settings 请参阅: AD委派设置

The difference is described here: msdn.microsoft.com/en-us/library/cc246080.aspx?f=255&MSPPError=-2147217396 With unconstrained delegation we could simply use the available delegated credentials when we call the back-end service and it would all be good: 这里描述了不同之处:msdn.microsoft.com/en-us/library/cc246080.aspx?f=255&MSPPError=-2147217396通过无约束委派,我们可以在调用后端服务时简单地使用可用的委派凭据,它会都很好:

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

With constrained delegation we have no access to the users TGT and it seems we need to use the MS-SFU (S4U2proxy) Kerberos extension which Java 8 is suppose to support. 通过约束委派,我们无法访问用户TGT,似乎我们需要使用Java 8支持的MS-SFU(S4U2proxy)Kerberos扩展。 The only example I could find is this one: https://github.com/ymartin59/java-kerberos-sfudemo (thanks Yves Martin for that!) 我能找到的唯一例子就是这个: https//github.com/ymartin59/java-kerberos-sfudemo (感谢Yves Martin!)

Now to my problem... After my authentication I basically end up with the username of the authenticated user (see "clientName" in code above). 现在我的问题...在我的身份验证后,我基本上得到了经过身份验证的用户的用户名(请参阅上面代码中的“clientName”)。

Do we really need to use the S4U2self mechanism to impersonate the user here? 我们真的需要使用S4U2self机制来冒充用户吗? The client just sent us it's Kerberos Service Ticket (wrapped in the SPNEGO token I can't decode). 客户端刚刚向我们发送了Kerberos Service Ticket(包含在我无法解码的SPNEGO令牌中)。 Ideally we should be able to use that service ticket and my own service's TGT to authenticate the user (using the S4U2proxy mechanism)? 理想情况下,我们应该能够使用该服务票证和我自己的服务的TGT来验证用户(使用S4U2proxy机制)? But I do not understand how. 但我不明白怎么做。

So now I'm wondering if it's possible to tie together our SPNEGO authentication with S4U2proxy delegation? 那么现在我想知道是否可以将我们的SPNEGO身份验证与S4U2proxy委派联系在一起?

Many thanks for any input on this. 非常感谢您对此提出的任何意见。

I've actually been doing something like this recently but am using spring security kerberos. 我最近一直在做这样的事情,但我正在使用春天安全kerberos。 I put an example on github here . 我在这里给github做一个例子。 The key thing that I found that I needed set up to use constrained delegation like you want it and S4U2Proxy was to make sure (if you're using Oracle/OpenJDK) you set isInitiator=true in your JAAS Config so that when getDelegCred is called you get back a Krb5ProxyCredential. 我发现需要设置使用约束委托的关键是你想要它和S4U2Proxy确保(如果你使用的是Oracle / OpenJDK)你在JAAS Config中设置了isInitiator=true ,这样当调用getDelegCred时你得到了Krb5ProxyCredential。 See comment here . 在这里看评论。 With that credential, you can use it to create service ticket tokens on the Users behalf for the services you are constrained to use in the normal fashion, like this . 使用该凭据,您可以使用它来代表用户创建服务票证令牌,以限制您以正常方式使用的服务,如下所示

I've done a lot of investigation on Kerberos constrained delegation, and finally I've figured out the correct way of doing it using Java. 我已经对Kerberos约束委派进行了大量调查,最后我已经找到了使用Java的正确方法。

Settings on Domain Controller 域控制器上的设置

1) No Delegation: Do not trust this account for delegation 1)没有代表团:不要相信这个帐户的授权

You (service user) can not get delegated credentials of the user. 您(服务用户)无法获得用户的委派凭据。 It means you can not perform any task on end user's behalf. 这意味着您无法代表最终用户执行任何任务。 At the most you can do is to accept the incoming ticket from the user(usually browser) and get it verified by passing it to KDC. 您最多可以接受来自用户(通常是浏览器)的传入票证,并通过将其传递给KDC来验证它。 In response, KDC will tell you for which user(or principal) this ticket is issued to, but no credentials will be passed. 作为回应,KDC将告诉您该票证发给哪个用户(或委托人),但不会传递凭证。

2) Unconstrained Delegation: Trust this account for delegation to any service (Kerberos only) 2)无约束委托:信任此帐户以授权任何服务(仅限Kerberos)

With this option, you (service user) get the delegated credentials of the user. 使用此选项,您(服务用户)将获得用户的委派凭据。 Moreover, what you get is a TGT of the user. 而且,你得到的是用户的TGT。 Using this TGT, you can request TGS (service ticket) on user's behalf for any service. 使用此TGT,您可以代表用户请求任何服务的TGS(服务票证)。

3) Trust this account for delegation to specified services (Kerberos only) 3)信任此帐户以获取指定的服务(仅限Kerberos)

Here, you specify the services to which you can use the delegated credentials. 在此处,您可以指定可以使用委派凭据的服务。 It means when this option is enabled, you get the delegated credentials, however, you are allowed to use them only to get end user's TGS for the specified services. 这意味着当启用此选项时,您将获得委派的凭据,但是,您只能使用它们来获取指定服务的最终用户的TGS。

Another important point is, you must have end user's TGS (end user's TGS for your web app). 另一个重点是,您必须拥有最终用户的TGS(最终用户的Web应用程序的TGS)。 Then using this TGS, you can request KDC the end user's TGS for another service. 然后使用此TGS,您可以向KDC请求最终用户的TGS以获取其他服务。

4) Trust this account for delegation to specified services (Any Protocol) 4)信任此帐户以授权给指定服务(任何协议)

This is also known as protocol transition. 这也称为协议转换。 In this option also, you need to specify the services for which you can request the TGS to KDC on user's behalf. 在此选项中,您还需要指定可以代表用户向TDC请求TGS的服务。

You (service user) are allowed to 'impersonate' the end user, without having any kind of ticket from the end user. 您(服务用户)可以“模拟”最终用户,而无需最终用户提供任何类型的票证。 You can impersonate any user, and get TGS for the specified services. 您可以模拟任何用户,并获取指定服务的TGS。 This option is useful for backgroung processes or schedulars where end user interaction is not possible. 此选项对于无法进行最终用户交互的后台进程或调度表非常有用。

Java Code Samples Java代码示例

1) Getting Delegated Credentials (useful in option 2 and 3 stated above) 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) Getting Impersonated Credentials (useful in option 4 stated above) 2)获取模拟证书(在上述选项4中有用)

Initial steps are similar as mentioned inside step 1 above. 初始步骤与上面步骤1中提到的类似。 You need to login using service user credentials. 您需要使用服务用户凭据登录。 There is small change in 'run' method, which is given below: '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;
            }
        }

You can see that we don't need user's TGS in this case. 在这种情况下,您可以看到我们不需要用户的TGS。
Getting TGS on user's behalf for other service, is same as mentioned in step 3 given in above code. 代表用户使用TGS进行其他服务,与上述代码中给出的步骤3中提到的相同。 Just pass these impersonatedCredentials instead of delegatedCredentials. 只需传递这些impersonatedCredentials而不是delegatedCredentials。

I hope this will be helpful. 我希望这会有所帮助。

Thanks, 谢谢,
Bhushan 布尚

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

相关问题 为什么Kerberos / SPNEGO身份验证不起作用? - Why Kerberos/SPNEGO authentication doesn't work? 跨领域设置中基于 Kerberos 资源的约束委派 - Kerberos Resourced based constrained delegation in cross realm setup 当SPNEGO Kerberos身份验证失败时,Chrome显示ERR_UNEXPECTED - Chrome show ERR_UNEXPECTED when SPNEGO Kerberos authentication failed Kerberos AD Spnego身份验证在一台计算机上失败,但在另一台计算机上失败 - Kerberos AD Spnego authentication fails on one machine but not on another 使用SPNEGO(Kerberos TGT)进行身份验证时更改LDAP属性 - Change LDAP attributes when authentication with SPNEGO (Kerberos TGT) 在 Samba 4.11 上使用多个 SPN 和 AES256 进行 Kerberos SPNEGO 身份验证 - Kerberos SPNEGO Authentication with multiple SPN and AES256 on Samba 4.11 Java8中的Kerberos / SPNEGO服务器端身份验证更改 - Kerberos/SPNEGO server side auth change in Java8 错误:带有 SPNEGO 的 Java GSS-API:在 Kerberos 数据库中找不到服务器 (7) - Error: Java GSS-API with SPNEGO: Server not found in Kerberos database (7) 如何在使用Java客户端的.NET WCF服务中启用Kerberos身份验证 - How to enable Kerberos Authentication in Java Client Consuming .NET WCF service Spnego / Kerberos支持与粗麻布 - Spnego / Kerberos support with hessian
 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM