我有以下代码:

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

当然会失败。这是异常(exception):
GSSException: Defective token detected (Mechanism level: GSSHeader did not find the right tag)
我不知道该怎么办才能解决这个问题。老实说,我不太了解Kerberos。

我通过发送带有适当的 header WWW-Authenticate和“Negotiate”作为值的401来获得此票证。浏览器立即再次使用包含该票证的authorization header 再次发出相同的请求。

我希望我可以验证票证并确定用户是谁。

我需要一个 key 表文件吗?如果是这样,我将在什么凭证下运行它?我正在尝试使用Kerberos票证进行网站的身份验证。凭据将是IIS的凭据吗?

我想念什么?

更新1
从Michael-O的回复中,我做了一些谷歌搜索,然后找到了this article,这使我进入了this article

table 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)

这是整个票证:YIGCBgYrBgEFBQKgeDB2oDAwLgYKKwYBBAGCNwICCgYJKoZIgvcSAQICBgkqhkiG9xIBAgIGCisGAQQBgjcCAh6iQgRATlRMTVNTUAABAAAAl7II4g4ADgAyAAAACgAKACgAAAAGAbEdAAAAD0xBUFRPUC0yNDVMSUZFQUNDT1VOVExMQw==

最佳答案

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

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

第1步-获取Kerberos key 表

为了使Kerberos客户端访问服务,它请求代表该服务的服务主体名称[SPN]的票证。 SPN通常是从计算机名称和正在访问的服务类型(例如HTTP/www.my-domain.com)派生的。为了验证特定SPN​​的kerberos票证,您必须具有一个 key 表文件,其中包含Kerberos域 Controller [KDC]票证授予票证[TGT]服务和服务提供商(您)都知道的共享 key 。

就Active Directory而言,KDC是域 Controller ,共享 secret 只是拥有SPN的帐户的纯文本密码。 SPN可以由AD内的计算机或用户对象拥有。

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

  • 在AD中创建密码未过期的非特权服务帐户,例如SVC_HTTP_MYSERVER,密码为ReallyLongRandomPass
  • 使用Windows setspn实用程序将服务SPN绑定(bind)到帐户。最佳实践是为主机的简称和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实用程序为帐户生成 key 表。
    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
    

  • 如果您尝试验证绑定(bind)到您无法控制的计算机帐户或用户帐户的已有SPN,则上述操作将无效。您将需要从ActiveDirectory本身提取 key 表。 Wireshark Kerberos Page为此提供了一些很好的指针。

    第2步-设置您的krb5.conf

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

    例子:

    [libdefaults]
      default_realm = MYDOMAIN
    
    [realms]
      MYDOMAIN = {
        kdc = dc1.my-domain.com
        default_domain = my-domain.com
      }
    
    [domain_realm]
      .my-domain.com = MYDOMAIN
      my-domain.com = MYDOMAIN
    

    第3步-设置JAAS login.conf

    您的JAAS login.conf应该定义一个登录配置,该配置将 Krb5LoginModule 设置为接受器。这是一个示例,假定我们上面创建的 key 表位于C:\http_myserver.ktab中。通过在命令行上设置-Djava.security.auth.login.config=C:\path\to\login.conf来指向JASS配置文件。

    http_myserver_mydomain {
      com.sun.security.auth.module.Krb5LoginModule required
      principal="HTTP/myserver.my-domain.com@MYDOMAIN"
      doNotPrompt="true"
      useKeyTab="true"
      keyTab="C:/http_myserver.ktab"
      storeKey="true"
      isInitiator="false";
    };
    

    另外,您可以在运行时生成JAAS配置,如下所示:
    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 ,如下所示:
    LoginContext ctx = new LoginContext("doesn't matter", subject, null,
      getJaasKrbValidationCfg("HTTP/myserver.my-domain.com@MYDOMAIN", "MYDOMAIN",
        new File("C:/path/to/my.ktab")));
    

    步骤4-接受车票

    这是一个即时可用的方法,但是一般的想法是定义一个PriviledgedAction来使用票证执行SPNEGO协议(protocol)。请注意,此示例不检查SPNEGO协议(protocol)是否完整。例如,如果客户端请求服务器身份验证,则需要在HTTP响应的身份验证 header 中返回由acceptSecContext()生成的 token 。
    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;
    }
    

    然后,要对票证进行身份验证,您将执行以下操作。假设ticket包含来自身份验证 header 的已经由base-64解码的票证。如果格式为spn,则Host应从HTTP请求中的HTTP/<HOST>@<REALM> header 派生。例如。如果Host header 是myserver.my-domain.com,则spn应该是HTTP/myserver.my-domain.com@MYDOMAIN
    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;
    }
    

    10-08 08:11