Basic Question:

我可以在我的Java中使用为其他地方的ldap服务生成的服务票证来使用kerminster对ldap服务进行身份验证并形成LdapContent以进行进一步操作吗?

Goal

我想为在Unix机器上运行的Java服务实现无密码身份验证,以连接到Active目录Ldap,以对目录对象执行各种CRUD操作.

Note:由于各种原因,我无法使用keytab文件,稍后我想扩展它以使用gGMA,使其纯粹进行无密码身份验证.我所说的"少密码"是指我想避免DC上的显式密码管理,对于在Unix上运行的Java服务,它必须是无密码的

我的 idea :

  1. 使用可以成功generate a service ticketldapWindows Agent Service
  2. 对服务票证(base64)进行编码并将其提供给Java服务(安全地)
  3. Java然后可以将其decode并使用它到authenticateldap并形成LdapContext for further usage
  4. Java服务和Windows代理服务之间的通信是安全的,Java服务可以定期要求Windows服务生成ldap服务票证.
  5. 现在有了这个,100
  6. 目前密码信息仅为Windows代理服务.
  7. 后来当我集成gGMA时,就连Windows代理端的密码要求都没有了,就达到了我的目标!

首先,我想验证这在理论上是否可行.

到目前为止我都try 了什么?

使用Express.NET C#库生成ldap服务票证

此C#生成ldap服务票证:

    private static async Task<string> GenerateLDAPServiceTicketForUser(string userName, string password, string domain, string ldapSPN)
    {
        KerberosClient client = new KerberosClient();
        await client.Authenticate(new KerberosPasswordCredential(userName, password, domain));     
        KrbApReq ticket = await client.GetServiceTicket(ldapSPN);
        if (null != ticket)
        {
            
            ReadOnlyMemory<byte> memory = ticket.EncodeApplication();
            byte[] ticketBytes = memory.ToArray();
            return Convert.ToBase64String(ticketBytes);
        } else
        {             
            throw new Exception("Ticket is null...Service Ticket could not be generated...");
        }
    }

提供给该函数的输入是:

 **username**: Administrator
 **domain**: helix.lab
 **ldapSPN**: ldap/WIN-FMCLF26TASJ.Helix.Lab
 **password**: <my-password>

这成功生成base64编码的服务票证.

现在,我正在try 在我的Java应用程序中使用相同的功能来连接helix.lab的ldap服务并执行ldap操作.此收件箱只是演示了如何使用已经的服务票证连接到AD ldap服务(并证明我在Java服务端无密码连接的假设)

这是我的Java代码:

初始化

System.setProperty("java.security.krb5.conf", "/etc/krb5.conf");
System.setProperty("sun.security.krb5.debug", "true");
System.setProperty("sun.security.jgss.debug", "true");

解码Base 64门票并形成KerberosTicket

String base64Ticket = "<my base 64 encoded ticket>";
byte[] ticketBytes = Base64.getDecoder().decode(base64Ticket);
byte[] sessionKey = new byte[] {0x00, 0x01, 0x02};

Date authTime = new Date(System.currentTimeMillis());
Date startTime = new Date(System.currentTimeMillis());
Date endTime = new Date(System.currentTimeMillis() + 24 * 60 * 60 * 1000);
Date renewTill = new Date(System.currentTimeMillis() + 48 * 60 * 60 * 1000);;

KerberosTicket ticket = new KerberosTicket(ticketBytes,
        new KerberosPrincipal("Administrator@HELIX.LAB"),
        new KerberosPrincipal("ldap/WIN-FMCLF26TASJ.Helix.Lab"),
        sessionKey, 18, null, authTime, startTime, endTime, renewTill, null);

Subject subject = new Subject();
subject.getPrivateCredentials().add(ticket);

连接到LDAP

Subject.doAs(subject, new PrivilegedAction<Void>() {

    @Override
    public Void run() {
        connectToLdap();
        return null;
    }
});

实现connect ToLdap:

String ldapURL = "ldap://WIN-FMCLF26TASJ.helix.lab:389";
Hashtable<String, Object> env = new Hashtable<>();
env.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.ldap.LdapCtxFactory");
env.put(Context.PROVIDER_URL, ldapURL);
env.put(Context.SECURITY_AUTHENTICATION, "GSSAPI");
env.put(Context.SECURITY_PRINCIPAL, "ldap/WIN-FMCLF26TASJ.Helix.Lab@HELIX.LAB");

try {
    LdapContext ctx = new InitialLdapContext(env, null);
    // Do ldap operations....
} catch(Exception e) {
}

我的DC上的setspn -l命令给出以下输出(ldap的多个条目):

ldap/WIN-FMCLF26TASJ/HELIX
ldap/b57bcf7f-d8b5-4e38-a2d2-f6833fe3d617._msdcs.Helix.lab
ldap/WIN-FMCLF26TASJ.Helix.lab/HELIX
ldap/WIN-FMCLF26TASJ
ldap/WIN-FMCLF26TASJ.Helix.lab
ldap/WIN-FMCLF26TASJ.Helix.lab/Helix.lab 

其中我使用的是:ldap/WIN-FMCLF26TASJ.Helix.lab,我认为这更有意义.

当我运行Java代码时:我收到以下错误:

Java config name: /etc/krb5.conf
Loading krb5 profile at /etc/krb5.conf
Loaded from Java config
Search Subject for Kerberos V5 INIT cred (<<DEF>>, 
sun.security.jgss.krb5.Krb5InitCredential)
找到了管理员@HELIX.LAB的门票,前往ldap/WIN-FMCLF 26 TASJ. HSEARCH.lab@HELIX.LAB,将于2024年4月23日星期二14:01:51 IST到期
Error occurred: GSSAPI exception:  javax.naming.AuthenticationException: GSSAPI [Root 
exception is javax.security.sasl.SaslException: GSS initiate failed [Caused by 
GSSException: No valid credentials provided (Mechanism level: Identifier doesn't match 
expected value (906))]]

这条线表示什么?

找到了管理员@HELIX.LAB的门票,前往ldap/WIN-FMCLF 26 TASJ. HSEARCH.lab@HELIX.LAB,将于2024年4月23日星期二14:01:51 IST到期

为什么身份验证失败?我做错了什么或者我错过了什么来理解? 任何其他方法或建议都可能对我有所帮助,但想了解这里出了什么问题以及如何纠正它?


更新 :

这是门票在https://lapo.it/asn1js上解码后的样子

enter image description here

Update 2 : Service Ticket retrieved from AP-REQ enter image description here

更新 3 :

Upon adding the valid ticket and its cipher, getting following error: 
Java config name: /etc/krb5.conf
Loading krb5 profile at /etc/krb5.conf
Loaded from Java config
Search Subject for Kerberos V5 INIT cred (<<DEF>>, sun.security.jgss.krb5.Krb5InitCredential)
Found ticket for Admininstrator@HELIX.LAB to go to ldap/WIN-FMCLF26TASJ.Helix.Lab@HELIX.LAB expiring on Wed Apr 24 13:17:10 IST 2024
Entered Krb5Context.initSecContext with state=STATE_NEW
Found ticket for Admininstrator@HELIX.LAB to go to ldap/WIN-FMCLF26TASJ.Helix.Lab@HELIX.LAB expiring on Wed Apr 24 13:17:10 IST 2024
Service ticket not found in the subject
>>> serviceCredsSingle: cross-realm authentication
>>> serviceCredsSingle: obtaining credentials from WIN-FMCLF26TASJ.Helix.Lab to HELIX.LAB
>>> Credentials acquireServiceCreds: main loop: [0] tempService=krbtgt/HELIX.LAB@WIN-FMCLF26TASJ.Helix.Lab
>>> Credentials serviceCredsSingle: same realm
Using builtin default etypes for default_tgs_enctypes
default etypes for default_tgs_enctypes: 18 17 20 19.
>>> CksumType: sun.security.krb5.internal.crypto.RsaMd5CksumType
>>> EType: sun.security.krb5.internal.crypto.Aes256CtsHmacSha1EType
>>> Credentials serviceCredsSingle: same realm
Using builtin default etypes for default_tgs_enctypes
default etypes for default_tgs_enctypes: 18 17 20 19.
>>> CksumType: sun.security.krb5.internal.crypto.RsaMd5CksumType
>>> EType: sun.security.krb5.internal.crypto.Aes256CtsHmacSha1EType
>>> Credentials acquireServiceCreds: no tgt; searching thru capath
>>> Credentials acquireServiceCreds: inner loop: [1] tempService=krbtgt/Helix.Lab@WIN-FMCLF26TASJ.Helix.Lab
>>> Credentials serviceCredsSingle: same realm
Using builtin default etypes for default_tgs_enctypes
default etypes for default_tgs_enctypes: 18 17 20 19.
>>> CksumType: sun.security.krb5.internal.crypto.RsaMd5CksumType
>>> EType: sun.security.krb5.internal.crypto.Aes256CtsHmacSha1EType
>>> Credentials serviceCredsSingle: same realm
Using builtin default etypes for default_tgs_enctypes
default etypes for default_tgs_enctypes: 18 17 20 19.
>>> CksumType: sun.security.krb5.internal.crypto.RsaMd5CksumType
>>> EType: sun.security.krb5.internal.crypto.Aes256CtsHmacSha1EType
>>> Credentials acquireServiceCreds: inner loop: [2] tempService=krbtgt/Lab@WIN-FMCLF26TASJ.Helix.Lab
>>> Credentials serviceCredsSingle: same realm
Using builtin default etypes for default_tgs_enctypes
default etypes for default_tgs_enctypes: 18 17 20 19.
>>> CksumType: sun.security.krb5.internal.crypto.RsaMd5CksumType
>>> EType: sun.security.krb5.internal.crypto.Aes256CtsHmacSha1EType
>>> Credentials serviceCredsSingle: same realm
Using builtin default etypes for default_tgs_enctypes
default etypes for default_tgs_enctypes: 18 17 20 19.
>>> CksumType: sun.security.krb5.internal.crypto.RsaMd5CksumType
>>> EType: sun.security.krb5.internal.crypto.Aes256CtsHmacSha1EType
>>> Credentials acquireServiceCreds: inner loop: [3] tempService=krbtgt/LAB@WIN-FMCLF26TASJ.Helix.Lab
>>> Credentials serviceCredsSingle: same realm
Using builtin default etypes for default_tgs_enctypes
default etypes for default_tgs_enctypes: 18 17 20 19.
>>> CksumType: sun.security.krb5.internal.crypto.RsaMd5CksumType
>>> EType: sun.security.krb5.internal.crypto.Aes256CtsHmacSha1EType
>>> Credentials serviceCredsSingle: same realm
Using builtin default etypes for default_tgs_enctypes
default etypes for default_tgs_enctypes: 18 17 20 19.
>>> CksumType: sun.security.krb5.internal.crypto.RsaMd5CksumType
>>> EType: sun.security.krb5.internal.crypto.Aes256CtsHmacSha1EType
>>> Credentials acquireServiceCreds: no tgt; cannot get creds
>>> serviceCredsSingle: cross-realm authentication
>>> serviceCredsSingle: obtaining credentials from WIN-FMCLF26TASJ.Helix.Lab to HELIX.LAB
>>> Credentials acquireServiceCreds: main loop: [0] tempService=krbtgt/HELIX.LAB@WIN-FMCLF26TASJ.Helix.Lab
>>> Credentials serviceCredsSingle: same realm
Using builtin default etypes for default_tgs_enctypes
default etypes for default_tgs_enctypes: 18 17 20 19.
>>> CksumType: sun.security.krb5.internal.crypto.RsaMd5CksumType
>>> EType: sun.security.krb5.internal.crypto.Aes256CtsHmacSha1EType
>>> Credentials serviceCredsSingle: same realm
Using builtin default etypes for default_tgs_enctypes
default etypes for default_tgs_enctypes: 18 17 20 19.
>>> CksumType: sun.security.krb5.internal.crypto.RsaMd5CksumType
>>> EType: sun.security.krb5.internal.crypto.Aes256CtsHmacSha1EType
>>> Credentials acquireServiceCreds: no tgt; searching thru capath
>>> Credentials acquireServiceCreds: inner loop: [1] tempService=krbtgt/Helix.Lab@WIN-FMCLF26TASJ.Helix.Lab
>>> Credentials serviceCredsSingle: same realm
Using builtin default etypes for default_tgs_enctypes
default etypes for default_tgs_enctypes: 18 17 20 19.
>>> CksumType: sun.security.krb5.internal.crypto.RsaMd5CksumType
>>> EType: sun.security.krb5.internal.crypto.Aes256CtsHmacSha1EType
>>> Credentials serviceCredsSingle: same realm
Using builtin default etypes for default_tgs_enctypes
default etypes for default_tgs_enctypes: 18 17 20 19.
>>> CksumType: sun.security.krb5.internal.crypto.RsaMd5CksumType
>>> EType: sun.security.krb5.internal.crypto.Aes256CtsHmacSha1EType
>>> Credentials acquireServiceCreds: inner loop: [2] tempService=krbtgt/Lab@WIN-FMCLF26TASJ.Helix.Lab
>>> Credentials serviceCredsSingle: same realm
Using builtin default etypes for default_tgs_enctypes
default etypes for default_tgs_enctypes: 18 17 20 19.
>>> CksumType: sun.security.krb5.internal.crypto.RsaMd5CksumType
>>> EType: sun.security.krb5.internal.crypto.Aes256CtsHmacSha1EType
>>> Credentials serviceCredsSingle: same realm
Using builtin default etypes for default_tgs_enctypes
default etypes for default_tgs_enctypes: 18 17 20 19.
>>> CksumType: sun.security.krb5.internal.crypto.RsaMd5CksumType
>>> EType: sun.security.krb5.internal.crypto.Aes256CtsHmacSha1EType
>>> Credentials acquireServiceCreds: inner loop: [3] tempService=krbtgt/LAB@WIN-FMCLF26TASJ.Helix.Lab
>>> Credentials serviceCredsSingle: same realm
Using builtin default etypes for default_tgs_enctypes
default etypes for default_tgs_enctypes: 18 17 20 19.
>>> CksumType: sun.security.krb5.internal.crypto.RsaMd5CksumType
>>> EType: sun.security.krb5.internal.crypto.Aes256CtsHmacSha1EType
>>> Credentials serviceCredsSingle: same realm
Using builtin default etypes for default_tgs_enctypes
default etypes for default_tgs_enctypes: 18 17 20 19.
>>> CksumType: sun.security.krb5.internal.crypto.RsaMd5CksumType
>>> EType: sun.security.krb5.internal.crypto.Aes256CtsHmacSha1EType
>>> Credentials acquireServiceCreds: no tgt; cannot get creds
KrbException: Fail to create credential. (63) - No service creds
    at java.security.jgss/sun.security.krb5.internal.CredentialsUtil.serviceCredsSingle(CredentialsUtil.java:458)
    at java.security.jgss/sun.security.krb5.internal.CredentialsUtil.serviceCreds(CredentialsUtil.java:340)
    at java.security.jgss/sun.security.krb5.internal.CredentialsUtil.serviceCreds(CredentialsUtil.java:314)
    at java.security.jgss/sun.security.krb5.internal.CredentialsUtil.acquireServiceCreds(CredentialsUtil.java:169)
    at java.security.jgss/sun.security.krb5.Credentials.acquireServiceCreds(Credentials.java:490)
    at java.security.jgss/sun.security.jgss.krb5.Krb5Context.initSecContext(Krb5Context.java:697)
    at java.security.jgss/sun.security.jgss.GSSContextImpl.initSecContext(GSSContextImpl.java:266)

推荐答案

在@user1686的帮助下,我确信我想要实现的目标确实可以实现!

获取base64编码的会话密钥和ldap服务票证的C#代码(注意,我使用了Express.NET库):

KerberosClient client = new KerberosClient();
string principal = userName + "@" + domain.ToUpper();
KerberosPasswordCredential kpc = new KerberosPasswordCredential(principal, password);
            
await client.Authenticate(kpc);
KerberosClientCacheEntry entry = client.Cache.GetCacheItem<KerberosClientCacheEntry>("krbtgt/EXAMPLE.LAB");

KrbApReq ticket = await client.GetServiceTicket(ldapSPN); // get the service ticket for the ldap SPN
KrbTicket serviceTkt = ticket.Ticket;
            
KerberosClientCacheEntry c2 = client.Cache.GetCacheItem<KerberosClientCacheEntry>(ldapSPN);

ReadOnlyMemory<byte> sessionKeyValue = c2.SessionKey.KeyValue;
byte[] sessionKeyValueBytes = sessionKeyValue.ToArray();
            
Console.WriteLine("LDAP Service Ticket Session Key : " + Convert.ToBase64String(sessionKeyValueBytes));           Console.WriteLine(Convert.ToBase64String(serviceTkt.EncodeApplication().ToArray()));

现在我们已经拥有了会话密钥和服务票证,这就是我在Java代码中重建它的方式:

System.setProperty("java.security.krb5.conf", "/etc/krb5.conf");
System.setProperty("sun.security.krb5.debug", "true");
System.setProperty("sun.security.jgss.debug", "true");


String base64cipher = "<base64-session-key>";
String base64Ticket = "<base64-ticket>";

byte[] ticketBytes = Base64.getDecoder().decode(base64Ticket);
byte[] sessionKey = Base64.getDecoder().decode(base64cipher);

Date authTime = new Date(System.currentTimeMillis());
Date startTime = new Date(System.currentTimeMillis());
Date endTime = new Date(System.currentTimeMillis() + 24 * 60 * 60 * 1000);
Date renewTill = new Date(System.currentTimeMillis() + 48 * 60 * 60 * 1000);


KerberosTicket ticket = new KerberosTicket(ticketBytes,
                new KerberosPrincipal("administrator@HELIX.LAB"),
                new KerberosPrincipal("ldap/win-fmclf26tasj.helix.lab@HELIX.LAB"),
                sessionKey, 18, null, authTime, startTime, endTime, renewTill, null);

Subject subject = new Subject();
subject.getPrincipals().add(new KerberosPrincipal("administrator@HELIX.LAB"));
        subject.getPrivateCredentials().add(ticket);
        Subject.doAs(subject, new PrivilegedAction<Void>() {

            @Override
            public Void run() {
                connectToLdap();
                return null;
            }
        });

这是ConnectToLdap()的实现

String ldapURL = "ldap://hostname.example.lab:389";
Hashtable<String, Object> env = new Hashtable<>();
env.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.ldap.LdapCtxFactory");
env.put(Context.PROVIDER_URL, ldapURL);
env.put(Context.SECURITY_AUTHENTICATION, "GSSAPI");

LdapContext ctx = new InitialLdapContext(env, null);

我能够形成LdapContent并使用上下文执行目录操作

Java相关问答推荐

JLS中形式参数列表后面的任何括号对用于确定方法结果中的精确数组类型的具体含义是什么?

通过推送通知向自己发送Matrix消息

在URL类图中表示Java swing类

如果给定层次 struct 级别,如何从其预序穿越构造n元树

在AnyLogic中增加变量计数

在模拟超类中设置非setter属性的值

在Java Swing Paint应用程序中捕获快速鼠标移动时遇到困难

从ActiveMQ Classic迁移到ActiveMQ Artemis需要进行哪些客户端更改?

我怎样才能让IntelliJ标记toString()的任何实现?

Java.lang.invke.LambdaConversionException:实例方法InvokeVirtual的参数数量不正确

Java堆中的许多java.time.ZoneRegion实例.ZoneId实例不应该被缓存吗?

当b是一个字节并且在Java中值为-1时,为什么b>;>;>;1总是等于-1?

Android应用程序为错误的显示类型 Select 尺寸文件

JFree Chart从图表中删除边框

当我在Java中有一个Synchronized块来递增int时,必须声明一个变量Volatile吗?

有没有办法知道在合并中执行了什么操作?

Java编译器是否进行了持续的折叠优化,以及如何进行判断?

spring 数据Elastic search 与 spring 启动数据Elastic search 之间的区别是什么?

原始和参数化之间的差异调用orElseGet时可选(供应商)

Jackson YAML:支持锚点扩展/覆盖