Использование GSSManager для проверки билета Kerberos

У меня есть следующий код:

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: Defective token detected (Mechanism level: GSSHeader did not find the right tag)

Я не знаю, что я должен сделать, чтобы решить это. Честно говоря, я не очень понимаю Kerberos.

Я получил этот билет, отправив 401 с соответствующим заголовком WWW-Authenticate с 'Переговоры' в качестве значения. Браузер немедленно отправил тот же запрос с authorization заголовок, содержащий этот тикет.

Я надеялся, что смогу проверить билет и определить, кто пользователь.

Нужен ли мне файл keytab? Если да, то с какими учетными данными я бы запустил это? Я пытаюсь использовать билет Kerberos для авторизации на веб-сайте. Могут ли учетные данные быть учетными данными IIS?

Что мне не хватает?


Обновление 1 Из ответа Майкла-О я немного прибегнул к поиску и нашел эту статью, которая привела меня к этой статье.

На 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==

3 ответа

Проверка билета SPNEGO на Java - довольно запутанный процесс. Вот краткий обзор, но имейте в виду, что этот процесс может иметь множество ошибок. Вы действительно должны понимать, как Active Directory, Kerberos, SPNEGO и JAAS работают для успешной диагностики проблем.

Перед началом убедитесь, что вы знаете свое имя области kerberos для вашего домена Windows. Для целей этого ответа я предполагаю, что это MYDOMAIN. Вы можете получить имя области, запустив echo %userdnsdomain% из окна cmd. Обратите внимание, что Kerberos чувствителен к регистру, и область почти всегда ВСЕХ КОПИЙ.

Шаг 1 - Получить таблицу ключей Kerberos

Чтобы клиент Kerberos получил доступ к услуге, он запрашивает билет для имени участника службы [SPN], которое представляет эту службу. SPN обычно определяются на основе имени компьютера и типа доступной услуги (например, HTTP/www.my-domain.com). Чтобы проверить билет Kerberos для определенного имени участника-службы, у вас должен быть файл keytab, который содержит общий секретный ключ, известный как службе билета выдачи билетов [TGT] контроллера домена Kerberos [KG], так и поставщику услуг (вам).

С точки зрения Active Directory, KDC является контроллером домена, а общий секрет - это просто текстовый пароль учетной записи, которой принадлежит SPN. SPN может принадлежать либо компьютеру, либо объекту пользователя в AD.

Самый простой способ настроить имя участника-службы в AD, если вы определяете службу, - это настроить имя участника-службы на основе пользователя следующим образом:

  1. Создайте непривилегированную учетную запись службы в AD, чей пароль не истекает, например, SVC_HTTP_MYSERVER с паролем ReallyLongRandomPass
  2. Привязать сервис SPN к учетной записи с помощью windows setspn полезность. Рекомендуется определить несколько имен 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
    

Если вы пытаетесь аутентифицировать ранее существующее имя участника-службы, которое привязано к учетной записи компьютера или учетной записи пользователя, которую вы не контролируете, вышеуказанное не будет работать. Вам нужно будет извлечь таблицу ключей из самого ActiveDirectory. На странице Wireshark Kerberos есть несколько хороших указателей для этого.

Шаг 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 в качестве акцептора. Вот пример, который предполагает, что созданная выше таблица ключей находится в C:\http_myserver.ktab, Укажите на файл конфигурации JASS, установив -Djava.security.auth.login.config=C:\path\to\login.conf в командной строке.

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, используя билет. Обратите внимание, что этот пример не проверяет, что протокол 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;
}

Затем, чтобы аутентифицировать билет, вы должны сделать что-то вроде следующего. Предположим, что 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;
}

Это не билет Kerberos, а билет SPNEGO. Ваш контекст имеет неправильный механизм.

Изменить: Хотя теперь у вас есть правильный механизм, ваш клиент отправляет вам токен NTLM, который GSS-API не может обработать. Возьмите токен Base 64, расшифруйте его до необработанных байтов и отобразите символы 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       

Это обернутый токен NTLM внутри токена SPNEGO. это просто означает, что Kerberos потерпел неудачу по некоторым причинам, например,

  • SPN не зарегистрирован
  • Clockskew
  • Не разрешено для Kerberos
  • Неверные записи DNS

Лучший вариант - использовать Wireshark на клиенте, чтобы найти основную причину.

Обратите внимание, что Java не поддерживает NTLM как субмеханизм SPNEGO. NTLM поддерживается только SSPI и Heimdal.

Если на сервере нет таблицы ключей и связанного ключа, зарегистрированных KDC, вы никогда не сможете использовать Kerberos для проверки заявки.

Заставить SPNEGO работать в лучшем случае сложно, и будет практически невозможно без хотя бы беглого понимания того, как работает Kerberos. Попробуйте прочитать этот диалог и посмотреть, сможете ли вы лучше понять.

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

Для SPNEGO требуется имя участника-службы в форме HTTP/server.example.com, и вам нужно будет сообщить библиотекам GSS, где находится эта таблица ключей при запуске сервера.

Другие вопросы по тегам