Java 使用GSSManager验证Kerberos票证

Java 使用GSSManager验证Kerberos票证,java,kerberos,gssapi,Java,Kerberos,Gssapi,我有以下代码: public static void main(String args[]){ try { //String ticket = "Negotiate YIGCBg...=="; //byte[] kerberosTicket = ticket.getBytes(); byte[] kerberosTicket = Base64.decode("YIGCBg...=="); GSSContext context

我有以下代码:

public static void main(String args[]){
    try {
        //String ticket = "Negotiate YIGCBg...==";
        //byte[] kerberosTicket = ticket.getBytes();
        byte[] kerberosTicket = Base64.decode("YIGCBg...==");
        GSSContext context = GSSManager.getInstance().createContext((GSSCredential) null);
        context.acceptSecContext(kerberosTicket, 0, kerberosTicket.length);
        String user = context.getSrcName().toString();
        context.dispose();
    } catch (GSSException e) {
        e.printStackTrace();
    } catch (Base64DecodingException e) {
        e.printStackTrace();
    }
}
当然失败了。例外情况如下:

GSSException:检测到有缺陷的令牌(机制级别:GSSHeader未找到正确的标记)

我不知道该怎么解决这个问题。老实说,我不太懂Kerberos

我通过发送一个401来获得这张票,该401带有适当的标题
WWW-Authenticate
,其值为“协商”。浏览器立即再次发出相同的请求,并带有包含此票证的
授权
标题

我希望我能验证票据并确定用户是谁

我需要密钥表文件吗?如果是,我将使用什么凭据运行此操作?我正在尝试使用Kerberos票证对网站进行身份验证。凭据是否是来自IIS的凭据

我错过了什么


更新1 从Michael-O的回复中,我做了更多的谷歌搜索并找到了答案,这让我找到了答案

表3
中,我发现
1.3.6.1.5.5.2 SPNEGO

我现在已经按照第一篇文章中的示例将其添加到我的凭据中。这是我的密码:

public static void main(String args[]){
    try {            
        Oid mechOid = new Oid("1.3.6.1.5.5.2");

        GSSManager manager = GSSManager.getInstance();

        GSSCredential myCred = manager.createCredential(null,
                GSSCredential.DEFAULT_LIFETIME,
                mechOid,
                GSSCredential.ACCEPT_ONLY);

        GSSContext context = manager.createContext(myCred);

        byte[] ticket = Base64.decode("YIGCBg...==");
        context.acceptSecContext(ticket, 0, ticket.length);
        String user = context.getSrcName().toString();
        context.dispose();
    } catch (GSSException e) {
        e.printStackTrace();
    } catch (Base64DecodingException e) {
        e.printStackTrace();
    }
}
但是现在代码在
createCredential
上失败,出现以下错误:

GSSException: No valid credentials provided (Mechanism level: Failed to find any Kerberos credentails)


这是全部票证:
yigcbgyrbgefbqkgedb2odawllykwybbagcnwicyjkozigvcsaqicbgkqhkig9xibagigcisgaqbgjccah6iqgratlrmtvntuaabaaal7i4g4adgayaaaacgacacgagagagagabaaaad0xbufrpuc0yndvmsuzfkundt1vovexmqw=
这不是Kerberos票证,而是SPNEGO票证。您的上下文具有错误的机制

编辑:不过,您现在有了正确的mech,您的客户端正在向您发送GSS-API无法处理的NTLM令牌。获取base64标记,解码为原始字节并显示ASCII字符。如果它以
NTLMSSP
开头,它肯定无法工作,并且您已经破坏了Kerberos设置

编辑2:这是您的票:

60 81 82 06 06 2B 06 01 05 05 02 A0 78 30 76 A0 30 30 2E 06  `..+..... x0v 00..
0A 2B 06 01 04 01 82 37 02 02 0A 06 09 2A 86 48 82 F7 12 01  .+....7.....*H÷..
02 02 06 09 2A 86 48 86 F7 12 01 02 02 06 0A 2B 06 01 04 01  ....*H÷......+....
82 37 02 02 1E A2 42 04 40 4E 54 4C 4D 53 53 50 00 01 00 00  7...¢B.@NTLMSSP....
00 97 B2 08 E2 0E 00 0E 00 32 00 00 00 0A 00 0A 00 28 00 00  .².â....2.......(..
00 06 01 B1 1D 00 00 00 0F 4C 41 50 54 4F 50 2D 32 34 35 4C  ...±.....LAPTOP-245L
49 46 45 41 43 43 4F 55 4E 54 4C 4C 43                       IFEACCOUNTLLC       
这是SPNEGO令牌中的包装NTLM令牌。这仅仅意味着Kerberos由于某些原因失败,例如:

  • SPN未注册
  • 时钟偏移
  • 不允许使用Kerberos
  • 不正确的DNS记录
最好的选择是在客户端上使用Wireshark来查找根本原因


请注意,Java不支持NTLM作为SPNEGO子机制。NTLM仅由SSPI和Heimdal支持。

如果服务器没有在KDC中注册密钥表和相关密钥,您将永远无法使用kerberos验证票证

要让SPNEGO正常工作充其量只是一件棘手的事情,如果至少不粗略了解kerberos的工作原理,这几乎是不可能的。试着阅读这个对话框,看看你是否能更好地理解它


SPNEGO需要一个格式为HTTP/server.example.com的SPN,您需要在启动服务器时告诉GSS库该键表在哪里

从Java验证SPNEGO票据是一个有点复杂的过程。这里是一个简短的概述,但请记住,这个过程可能会有很多陷阱。您确实需要了解Active Directory、Kerberos、SPNEGO和JAAS是如何成功诊断问题的

开始之前,请确保您知道windows域的kerberos域名。为了回答这个问题,我假设它是MYDOMAIN。您可以通过从cmd窗口运行
echo%userdnsdomain%
来获取域名。请注意,kerberos区分大小写,并且领域几乎总是全部大写

步骤1-获取Kerberos密钥表 为了让kerberos客户端访问服务,它请求一个表示该服务的服务主体名称[SPN]的票证。SPN通常来源于机器名和访问的服务类型(例如HTTP/www.my-domain.com)。为了验证特定SPN的kerberos票证,您必须拥有一个包含kerberos域控制器[KDC]票证授予票证[TGT]服务和服务提供商(您)都知道的共享秘密的密钥表文件

就Active Directory而言,KDC是域控制器,共享密钥只是拥有SPN的帐户的纯文本密码。SPN可能由AD中的计算机或用户对象拥有

如果要定义服务,在AD中设置SPN的最简单方法是设置基于用户的SPN,如下所示:

  • 在AD中创建一个密码未过期的非特权服务帐户,例如使用密码
    ReallyLongRandomPass
    SVC_HTTP_MYSERVER
  • 使用windows
    setspn
    实用程序将服务SPN绑定到帐户。最佳做法是为主机的短名称和FQDN定义多个SPN:

    setspn -U -S HTTP/myserver@MYDOMAIN SVC_HTTP_MYSERVER
    setspn -U -S HTTP/myserver.my-domain.com@MYDOMAIN SVC_HTTP_MYSERVER
    
  • 使用Java的
    ktab
    实用程序为帐户生成密钥表

    ktab -k FILE:http_myserver.ktab -a HTTP/myserver@MYDOMAIN ReallyLongRandomPass
    ktab -k FILE:http_myserver.ktab -a HTTP/myserver.my-domain.com@MYDOMAIN ReallyLongRandomPass
    
  • 如果您试图验证绑定到计算机帐户或您不控制的用户帐户的预先存在的SPN,则上述操作将不起作用。您需要从ActiveDirectory本身提取密钥表。对于这一点,政府有一些很好的建议

    第2步-设置krb5.conf 在
    %JAVA_HOME%/jre/lib/security
    中创建一个描述域的krb5.conf。确保您在此处定义的域与您为SPN设置的域匹配。如果不将文件放在JVM目录中,可以通过在命令行上设置
    -Djava.security.krb5.conf=C:\path\to\krb5.conf
    来指向它

    例如:

    [libdefaults]
    默认\u realm=MYDOMAIN
    [领域]
    MYDOMAIN={
    kdc=dc1.my-domain.com
    默认\u domain=my-domain.com
    }
    [domain\u realm]
    .my-domain.com=MYDOMAIN
    my-domain.com=MYDOMAIN
    
    步骤3-设置JAAS login.conf 您的JAAS
    login.conf
    应该定义一个登录配置,将Krb5LoginModule设置为接受程序。下面是一个示例,它假设
    public static Configuration getJaasKrb5TicketCfg(
        final String principal, final String realm, final File keytab) {
      return new Configuration() {
        @Override
        public AppConfigurationEntry[] getAppConfigurationEntry(String name) {
          Map<String, String> options = new HashMap<String, String>();
          options.put("principal",    principal);
          options.put("keyTab",       keytab.getAbsolutePath());
          options.put("doNotPrompt", "true");
          options.put("useKeyTab",   "true");
          options.put("storeKey",    "true");
          options.put("isInitiator", "false");
    
          return new AppConfigurationEntry[] {
            new AppConfigurationEntry(
              "com.sun.security.auth.module.Krb5LoginModule",
              LoginModuleControlFlag.REQUIRED, options)
          };
        }
      };
    }
    
    LoginContext ctx = new LoginContext("doesn't matter", subject, null, 
      getJaasKrbValidationCfg("HTTP/myserver.my-domain.com@MYDOMAIN", "MYDOMAIN", 
        new File("C:/path/to/my.ktab")));
    
    public class Krb5TicketValidateAction implements PrivilegedExceptionAction<String> {
      public Krb5TicketValidateAction(byte[] ticket, String spn) {
        this.ticket = ticket;
        this.spn = spn;
      }
    
      @Override
      public String run() throws Exception {
        final Oid spnegoOid = new Oid("1.3.6.1.5.5.2");
    
        GSSManager gssmgr = GSSManager.getInstance();
    
        // tell the GSSManager the Kerberos name of the service
        GSSName serviceName = gssmgr.createName(this.spn, GSSName.NT_USER_NAME);
    
        // get the service's credentials. note that this run() method was called by Subject.doAs(),
        // so the service's credentials (Service Principal Name and password) are already 
        // available in the Subject
        GSSCredential serviceCredentials = gssmgr.createCredential(serviceName,
          GSSCredential.INDEFINITE_LIFETIME, spnegoOid, GSSCredential.ACCEPT_ONLY);
    
        // create a security context for decrypting the service ticket
        GSSContext gssContext = gssmgr.createContext(serviceCredentials);
    
        // decrypt the service ticket
        System.out.println("Entering accpetSecContext...");
        gssContext.acceptSecContext(this.ticket, 0, this.ticket.length);
    
        // get the client name from the decrypted service ticket
        // note that Active Directory created the service ticket, so we can trust it
        String clientName = gssContext.getSrcName().toString();
    
        // clean up the context
        gssContext.dispose();
    
        // return the authenticated client name
        return clientName;
      }
    
      private final byte[] ticket;
      private final String spn;
    }
    
    public boolean isTicketValid(String spn, byte[] ticket) {
      LoginContext ctx = null;
      try {
        // this is the name from login.conf.  This could also be a parameter
        String ctxName = "http_myserver_mydomain";
    
        // define the principal who will validate the ticket
        Principal principal = new KerberosPrincipal(spn, KerberosPrincipal.KRB_NT_SRV_INST);
        Set<Principal> principals = new HashSet<Principal>();
        principals.add(principal);
    
        // define the subject to execute our secure action as
        Subject subject = new Subject(false, principals, new HashSet<Object>(), 
          new HashSet<Object>());
    
        // login the subject
        ctx = new LoginContext("http_myserver_mydomain", subject);
        ctx.login();
    
        // create a validator for the ticket and execute it
        Krb5TicketValidateAction validateAction = new Krb5TicketValidateAction(ticket, spn);
        String username = Subject.doAs(subject, validateAction);
        System.out.println("Validated service ticket for user " + username 
          + " to access service " + spn );
        return true;
      } catch(PriviledgedActionException e ) {
         System.out.println("Invalid ticket for " + spn + ": " + e);
      } catch(LoginException e) {
        System.out.println("Error creating validation LoginContext for " 
          + spn + ": " + e);
      } finally {
        try {
          if(ctx!=null) { ctx.logout(); }
        } catch(LoginException e) { /* noop */ }
      }
    
      return false;
    }