Использование 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, если вы определяете службу, - это настроить имя участника-службы на основе пользователя следующим образом:
- Создайте непривилегированную учетную запись службы в AD, чей пароль не истекает, например, SVC_HTTP_MYSERVER с паролем
ReallyLongRandomPass
Привязать сервис 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
Создать ключевую таблицу для учетной записи, используя 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, где находится эта таблица ключей при запуске сервера.