java 使用 GSSManager 验证 Kerberos 票证

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/25289231/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-11-02 07:47:40  来源:igfitidea点击:

Using GSSManager to validate a Kerberos ticket

javakerberosgssapi

提问by Josh C.

I have the following code:

我有以下代码:

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

Of course it fails. Here's the exception:

当然失败了。这是例外:

GSSException: Defective token detected (Mechanism level: GSSHeader did not find the right tag)

GSSException: Defective token detected (Mechanism level: GSSHeader did not find the right tag)

I don't know what I'm supposed to do to solve this. Honestly, I don't really understand Kerberos.

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

I got this ticket by sending a 401 with the appropriate header WWW-Authenticatewith 'Negotiate' as the value. The browser immediately issued the same request again with an authorizationheader containing this ticket.

我通过发送带有适当标头的 401 并WWW-Authenticate以“协商”作为值来获得这张票。浏览器立即再次使用authorization包含此票证的标头再次发出相同的请求。

I was hoping I could validate the ticket and determine who the user is.

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

Do I need a keytab file? If so, what credentials would I run this under? I'm trying to use the Kerberos ticket for auth for a web-site. Would the credentials be the credentials from IIS?

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

What am I missing?

我错过了什么?



Update 1From Michael-O's reply, I did a bit more googling and found this article, which led me to this article.

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

On table 3, I found 1.3.6.1.5.5.2 SPNEGO.

table 3,我找到了1.3.6.1.5.5.2 SPNEGO

I have now added that to my credentials following the example from the first article. Here's my code:

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

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

But now the code is failing on createCredentialwith this error:

但现在代码失败并createCredential出现此错误:

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


Here's the entire ticket: YIGCBgYrBgEFBQKgeDB2oDAwLgYKKwYBBAGCNwICCgYJKoZIgvcSAQICBgkqhkiG9xIBAgIGCisGAQQBgjcCAh6iQgRATlRMTVNTUAABAAAAl7II4g4ADgAyAAAACgAKACgAAAAGAbEdAAAAD0xBUFRPUC0yNDVMSUZFQUNDT1VOVExMQw==

这是整个门票: YIGCBgYrBgEFBQKgeDB2oDAwLgYKKwYBBAGCNwICCgYJKoZIgvcSAQICBgkqhkiG9xIBAgIGCisGAQQBgjcCAh6iQgRATlRMTVNTUAABAAAAl7II4g4ADgAyAAAACgAKACgAAAAGAbEdAAAAD0xBUFRPUC0yNDVMSUZFQUNDT1VOVExMQw==

回答by Dominic A.

Validating an SPNEGO ticket from Java is a somewhat convoluted process. Here's a brief overview but bear in mind that the process can have tons of pitfalls. You really need to understand how Active Directory, Kerberos, SPNEGO, and JAAS all operate to successfully diagnose problems.

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

Before you start, make sure you know your kerberos realm name for your windows domain. For the purposes of this answer I'll assume it's MYDOMAIN. You can obtain the realm name by running echo %userdnsdomain%from a cmd window. Note that kerberos is case sensitiveand the realm is almost always ALL CAPS.

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

Step 1 - Obtain a Kerberos Keytab

步骤 1 - 获取 Kerberos Keytab

In order for a kerberos client to access a service, it requests a ticket for the Service Principal Name [SPN] that represents that service. SPNs are generally derived from the machine name and the type of service being accessed (e.g. HTTP/www.my-domain.com). In order to validate a kerberos ticket for a particular SPN, you must have a keytab file that contains a shared secret known to both the Kerberos Domain Controller [KDC] Ticket Granting Ticket [TGT] service and the service provider (you).

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

In terms of Active Directory, the KDC is the Domain Controller, and the shared secret is just the plain text password of the account that owns the SPN. A SPN may be owned by either a Computer or a User object within the AD.

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

The easiest way to setup a SPN in AD if you are defining a service is to setup a user-based SPN like so:

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

  1. Create an unpriviledged service account in AD whose password doesn't expire e.g. SVC_HTTP_MYSERVER with password ReallyLongRandomPass
  2. Bind the service SPN to the account using the windows setspnutility. Best practice is to define multiple SPNs for both the short name and the FQDN of the host:

    setspn -U -S HTTP/myserver@MYDOMAIN SVC_HTTP_MYSERVER
    setspn -U -S HTTP/myserver.my-domain.com@MYDOMAIN SVC_HTTP_MYSERVER
    
  3. Generate a keytab for the account using Java's ktabutility.

    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
    
  1. 在 AD 中创建一个密码不会过期的非特权服务帐户,例如带有密码的 SVC_HTTP_MYSERVER ReallyLongRandomPass
  2. 使用 Windowssetspn实用程序将服务 SPN 绑定到帐户。最佳实践是为主机的短名称和 FQDN 定义多个 SPN:

    setspn -U -S HTTP/myserver@MYDOMAIN SVC_HTTP_MYSERVER
    setspn -U -S HTTP/myserver.my-domain.com@MYDOMAIN SVC_HTTP_MYSERVER
    
  3. 使用 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
    

If you are trying to authenticate a pre-existing SPN that is bound to a Computer account or to a User account you do not control, the above will not work. You will need to extract the keytab from ActiveDirectory itself. The Wireshark Kerberos Pagehas some good pointers for this.

如果您尝试对绑定到计算机帐户或您无法控制的用户帐户的预先存在的 SPN 进行身份验证,则上述方法将不起作用。您需要从 ActiveDirectory 本身提取密钥表。该Wireshark的Kerberos的页面有一些这方面的良好指针。

Step 2 - Setup your krb5.conf

第 2 步 - 设置您的 krb5.conf

In %JAVA_HOME%/jre/lib/securitycreate a krb5.conf that describes your domain. Make sure the realm you define here matches what you setup for your SPN. If you don't put the file in the JVM directory, you can point to it by setting -Djava.security.krb5.conf=C:\path\to\krb5.confon the command line.

%JAVA_HOME%/jre/lib/security创建描述您的域的 krb5.conf 中。确保您在此处定义的领域与您为 SPN 设置的领域相匹配。如果不把文件放在JVM目录下,可以-Djava.security.krb5.conf=C:\path\to\krb5.conf在命令行中设置指向。

Example:

例子:

[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

Step 3 - Setup JAAS login.conf

第 3 步 - 设置 JAAS login.conf

Your JAAS login.confshould define a login configuration that sets up the Krb5LoginModuleas a acceptor. Here's an example that assumes that the keytab we created above is in C:\http_myserver.ktab. Point to the JASS config file by setting -Djava.security.auth.login.config=C:\path\to\login.confon the command line.

您的 JAASlogin.conf应该定义一个登录配置,将Krb5LoginModule 设置为接受器。这是一个假设我们上面创建的密钥表在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";
};

Alternatively, you can generate a JAAS config at runtime like so:

或者,您可以在运行时生成 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)
      };
    }
  };
}

You would create a LoginContextfor this configuration like so:

您将为该配置创建一个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")));

Step 4 - Accepting the ticket

第 4 步 - 接受票

This is a little off-the-cuff, but the general idea is to define a PriviledgedAction that performs the SPNEGO protocol using the ticket. Note that this example does not check that SPNEGO protocol is complete. For example if the client requested server authentication, you would need to return the token generated by acceptSecContext()in the authentication header in the HTTP response.

这有点随意,但总体思路是定义一个 PriviledgedAction,它使用票证执行 SPNEGO 协议。请注意,此示例不检查 SPNEGO 协议是否完整。例如,如果客户端请求服务器身份验证,您将需要返回acceptSecContext()HTTP 响应中的身份验证标头中生成的令牌。

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

Then to authenticate the ticket, you would do something like the following. Assume that ticketcontains the already-base-64-decoded ticket from the authentication header. The spnshould be derived from the Hostheader in the HTTP request if the format of HTTP/<HOST>@<REALM>. E.g. if the Hostheader was myserver.my-domain.comthen spnshould be HTTP/myserver.my-domain.com@MYDOMAIN.

然后要验证票证,您将执行以下操作。假设它ticket包含来自身份验证标头的已用 base-64 解码的票证。在spn应该从导出Host在如果格式的HTTP请求报头HTTP/<HOST>@<REALM>。例如,如果Host标题是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;
}

回答by Michael-O

This is not a Kerberos ticket but a SPNEGO ticket. Your context has the wrong mechanism.

这不是 Kerberos 票证,而是 SPNEGO 票证。您的上下文有错误的机制。

Edit: Though, you now have the correct mech, you client is sending you a NTLM token which the GSS-API is not able to process. Take the Base 64 token, decode to raw bytes and display ASCII chars. If it starts with NTLMSSP, it won't work for sure and you have broken Kerberos setup.

编辑:虽然,您现在拥有正确的机械,但您的客户端正在向您发送 GSS-API 无法处理的 NTLM 令牌。获取 Base 64 标记,解码为原始字节并显示 ASCII 字符。如果它以 开头NTLMSSP,则肯定无法正常工作,并且您破坏了 Kerberos 设置。

Edit 2: This is your ticket:

编辑 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.a....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       

This is a wrapped NTLM token inside a SPNEGO token. which simply means that Kerberos has failed for some reasons, e.g.,

这是一个封装在 SPNEGO 令牌中的 NTLM 令牌。这只是意味着 Kerberos 由于某些原因而失败,例如,

  • SPN not registered
  • Clockskew
  • Not allowed for Kerberos
  • Incorrect DNS records
  • SPN 未注册
  • 时钟偏移
  • 不允许用于 Kerberos
  • DNS 记录不正确

Best option is to use Wireshark on the client to find the root cause.

最好的选择是在客户端上使用 Wireshark 来查找根本原因。

Please note that Java does notsupport NTLM as a SPNEGO submechanism. NTLM is only supported by SSPI and Heimdal.

请注意,Java并没有支持NTLM的SPNEGO submechanism。NTLM 仅受 SSPI 和 Heimdal 支持。

回答by Fred the Magic Wonder Dog

If the server does not have a keytab and associated key registered the KDC, you will never be able use kerberos to validate a ticket.

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

Getting SPNEGO to work is tricky at best and will be next to impossible without at least a cursory understanding of how kerberos works. Try reading this dialog and see if you can get a better understanding.

使 SPNEGO 工作至多是棘手的,如果不至少粗略地了解 kerberos 的工作原理,几乎是不可能的。尝试阅读此对话框,看看您是否能更好地理解。

http://web.mit.edu/kerberos/dialogue.html

http://web.mit.edu/kerberos/dialogue.html

SPNEGO requires an SPN of the form HTTP/server.example.com and you'll need to tell the GSS libraries where that keytab is when you start the server.

SPNEGO 需要格式为 HTTP/server.example.com 的 SPN,并且您需要在启动服务器时告诉 GSS 库该密钥表的位置。