Установка и настройка Apache Tomcat для использования Active Directory в качестве Single Sign-On

На тестовом стенде будем использовать четыре виртуальные машины:

Немаловажные моменты: appsrv, a18 и win10ws должны использовать addc в качестве DNS-сервера. DNS-сервер, работающий на addc, должен иметь в зоне прямого просмотра A-запись appsrv с адресом 192.168.5.92 и в зоне обратного просмотра PTR-запись, согласно которой адрес 192.168.5.92 соответствует имени appsrv.somedom.lan. Аналогичные записи должны быть и про 192.168.5.93/a18.somedom.lan. Время на всех машинах должно быть настроенно корректно, Kerberos к нему весьма чувствителен.

  1. В домене заведём четырёх пользователей: пользователя admin c правами администратора домена и трёх пользователей vpupkin, tomcat11 и tomcat9 с правами обычных пользователей и паролем SomePassword123. Всем четверым в свойствах учётной записи (Учётная запись: Параметры учётной записи), отметим чекбокс "Данная учётная запись поддерживает 256-разрядное шифрование"

  2. На appsrv устанавливаем пакеты, необходимые ввода машины в домен AD:
    sudo apt install krb5-user realmd sssd sssd-ad sssd-tools adcli
    

  3. На a18 устанавливаем пакет, нужный для тех же целей:
    sudo apt install astra-ad-sssd-client
    
    Во время установки будет задан вопрос. Отвечаем SOMEDOM.LAN заглавными буквами. Регистр важен.

  4. Приводим файл /etc/krb5.conf на appsrv и a18 следующий вид:
    [libdefaults]
    default_realm = SOMEDOM.LAN
    #canonicalize = true
    dns_lookup_realm = true
    dns_lookup_kdc = true
    ticket_lifetime = 24h
    renew_lifetime = 7d
    forwardable=true
    default_keytab_name = FILE:/etc/krb5.keytab
    default_tkt_enctypes = aes256-cts-hmac-sha1-96
    default_tgs_enctypes = aes256-cts-hmac-sha1-96
    #default_ccache_name = KEYRING:persistent:%{uid}
    udp_preference_limit = 0
    
    [realms]
        SOMEDOM.LAN = {
            kdc = addc.somedom.lan
            admin_server = addc.somedom.lan
            default_domain = somedom.lan
        }
    
    [domain_realm]
        somedom.lan = SOMEDOM.LAN
        .somedom.lan = SOMEDOM.LAN
    
    Для a18 в строке, содержащую default_ccache_name, убираем символ комментария #

  5. Вводим в домен appsrv командой:
    sudo realm join -v -U admin@SOMEDOM.LAN SOMEDOM.LAN --install=/
    
    И перезагружаем.

  6. Вводим в домен a18 командой:
    sudo astra-ad-sssd-client -dc addc.somedom.lan -u admin
    
    И перезагружаем.

  7. После ввода в домен на обоих Linux-машинах должны заработать команды kinit, klist и id:
    $ kinit admin@somedom.lan
    ...Будет запрошен пароль...
    
    $ klist
    Ticket cache: KEYRING:persistent:1000:1000
    Default principal: admin@SOMEDOM.LAN
    
    $ id vpupkin@somedom.lan
    uid=1140601104(vpupkin@somedom.lan) gid=1140600513(пользователи домена@somedom.lan) groups=1140600513(пользователи домена@somedom.lan)
    
    

  8. Устанавливаем Apache Tomcat на appsrv:
    sudo apt install tomcat11
    
    И на a18:
    sudo apt install tomcat9
    

  9. Заходим администратором домена на контроллер AD addc и в командной строке пишем:
    
    setspn -S HTTP/appsrv.somedom.lan SOMEDOM\tomcat11
    setspn -S HTTP/appsrv.somedom.lan@SOMEDOM.LAN SOMEDOM\tomcat11
    ktpass -princ HTTP/appsrv.somedom.lan@SOMEDOM.LAN -mapuser SOMEDOM\tomcat11 -kvno 0 -crypto AES256-SHA1 -ptype KRB5_NT_PRINCIPAL -out tomcat11.keytab -pass SomePassword123
    
    setspn -S HTTP/a18.somedom.lan SOMEDOM\tomcat9
    setspn -S HTTP/a18.somedom.lan@SOMEDOM.LAN SOMEDOM\tomcat9
    ktpass -princ HTTP/a18.somedom.lan@SOMEDOM.LAN -mapuser SOMEDOM\tomcat9 -kvno 0 -crypto AES256-SHA1 -ptype KRB5_NT_PRINCIPAL -out tomcat9.keytab -pass SomePassword123
    
    
    Если все прошло без ошибок, то два получившихся файла tomcat11.keytab и tomcat9.keytab раскладываем по машинам appsrv и a18 в каталоги /etc/tomcat11 и /etc/tomcat9.

  10. Необходимо проверить работоспособность полученных keytab'ов. Команды на appsrv и на a18 должны выполнятся без ошибок:
    kinit -k -t /etc/tomcat11/tomcat11.keytab HTTP/appsrv.somedom.lan@SOMEDOM.LAN
    klist
    
    kinit -k -t /etc/tomcat11/tomcat9.keytab HTTP/a18.somedom.lan@SOMEDOM.LAN
    klist
    
    Иногда, по не вполне понятным причинам, keytab не работает с первого раза. Приходится пересоздавать :(

  11. Меняем права доступа и владельца keytab-файлов на appsrv и a18:
    chmod 400 /etc/tomcat11/tomcat11.keytab
    chown tomcat:tomcat /etc/tomcat11/tomcat11.keytab
    
    chmod 400 /etc/tomcat9/tomca9.keytab
    chown tomcat:tomcat /etc/tomcat9/tomcat9.keytab
    

  12. В конец файла /etc/default/tomcat11 на appsrv дописываем строку:
    JAVA_OPTS="${JAVA_OPTS} -Djava.security.krb5.conf=/etc/krb5.conf -Djava.security.auth.login.config=/etc/tomcat11/jaas.conf -Djavax.security.auth.useSubjectCredsOnly=false -Dsun.security.krb5.debug=true -Dsun.security.jgss.debug=true"
    
    То же самое проделываем с /etc/default/tomcat9 на a18, не забывая заменить "tomcat11" на "tomcat9"

  13. На appsrv в каталоге /etc/tomcat11 создаём файл jaas.conf со следующим содержимым:
    com.sun.security.jgss.krb5.initiate {
        com.sun.security.auth.module.Krb5LoginModule required
        doNotPrompt=true
        principal="HTTP/appsrv.somedom.lan@SOMEDOM.LAN"
        storeKey=true
        useKeyTab=true
        keyTab="/etc/tomcat11/tomcat11.keytab"
        debug=true;
    };
    
    com.sun.security.jgss.krb5.accept {
        com.sun.security.auth.module.Krb5LoginModule required
        doNotPrompt=true
        principal="HTTP/appsrv.somedom.lan@SOMEDOM.LAN"
        storeKey=true
        useKeyTab=true
        keyTab="/etc/tomcat11/tomcat11.keytab"
        debug=true;
    };
    
    На a18 в каталоге /etc/tomcat9 создаём jaas.conf с аналогичным содержимым:
    com.sun.security.jgss.krb5.initiate {
        com.sun.security.auth.module.Krb5LoginModule required
        doNotPrompt=true
        principal="HTTP/a18.somedom.lan@SOMEDOM.LAN"
        storeKey=true
        useKeyTab=true
        keyTab="/etc/tomcat9/tomcat9.keytab"
        debug=true;
    };
    
    com.sun.security.jgss.krb5.accept {
        com.sun.security.auth.module.Krb5LoginModule required
        doNotPrompt=true
        principal="HTTP/a18.somedom.lan@SOMEDOM.LAN"
        storeKey=true
        useKeyTab=true
        keyTab="/etc/tomcat9/tomcat9.keytab"
        debug=true;
    };
    

  14. Далее необходимо поправить /etc/tomcat11/server.xml на appsrv.
    Находим в нём строчку
        <Realm className="org.apache.catalina.realm.LockOutRealm">
    
    и дописываем после неё:
          <Realm className="org.apache.catalina.realm.JNDIRealm"
             connectionURL="ldap://addc.somedom.lan:389"
             connectionName="tomcat11@SOMEDOM.LAN"
             connectionPassword="SomePassword123"
             userBase="DC=somedom,DC=lan"
             userSearch="(sAMAccountName={0})"
             userSubtree="true"
             roleBase="DC=somedom,DC=lan"
             roleName="CN"
             roleSearch="(member={0})"
             roleSubtree="true"
             adCompat="true" />
    
    На a18 проделываем аналогичную процедуру, заменяя "tomcat11" на "tomcat9"

  15. На appsrv в каталоге /var/lib/tomcat11/webapps создаем каталог who . В каталоге who создаём два каталога META-INF и WEB-INF. В /var/lib/tomcat11/webapps/who помещаем файл index.jsp с таким содержимым:
    <%@ page import="javax.naming.*, javax.naming.directory.*, java.util.*" %>
    <%@ page contentType="text/html;charset=UTF-8" language="java" %>
    <html>
    <head>
        <title>User Info from AD</title>
    </head>
    <body>
        <h1>User Information</h1>
        <%
            String user = request.getRemoteUser();
            if (user == null) {
                out.println("<p>User not authenticated.</p>");
            } else {
                out.println("<p>Username: " + user + "</p>");
    
                // LDAP connection setup
                Hashtable<String, String> env = new Hashtable<>();
                env.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.ldap.LdapCtxFactory");
                env.put(Context.PROVIDER_URL, "ldap://addc.somedom.lan:389");
                env.put(Context.SECURITY_AUTHENTICATION, "simple");
                env.put(Context.SECURITY_PRINCIPAL, "tomcat11@SOMEDOM.LAN");
                env.put(Context.SECURITY_CREDENTIALS, "SomePassword123");
                env.put(Context.REFERRAL, "follow");
    
                try {
                    DirContext ctx = new InitialDirContext(env);
    
                    // Search for user
                    String base = "DC=somedom,DC=lan";
                    String filter = "(sAMAccountName=" + user + ")";
                    SearchControls searchControls = new SearchControls();
                    searchControls.setSearchScope(SearchControls.SUBTREE_SCOPE);
                    searchControls.setReturningAttributes(null);
    
                    NamingEnumeration<SearchResult> results = ctx.search(base, filter, searchControls);
    
                    if (results.hasMore()) {
                        SearchResult sr = results.next();
                        Attributes attrs = sr.getAttributes();
    
                        out.println("<h2>All LDAP Attributes:</h2><ul>");
                        NamingEnumeration<?> attrEnum = attrs.getAll();
                        while (attrEnum.hasMore()) {
                            Attribute attr = (Attribute) attrEnum.next();
                            out.println("<li><strong>" + attr.getID() + ":</strong> ");
                            NamingEnumeration<?> values = attr.getAll();
                            while (values.hasMore()) {
                                out.println(values.next() + " ");
                            }
                            out.println("</li>");
                        }
                        out.println("</ul>");
                    } else {
                        out.println("<p>No attributes found for user.</p>");
                    }
    
                    ctx.close();
                } catch (Exception e) {
                    out.println("<p>Error: " + e.getMessage() + "</p>");
                }
            }
        %>
    </body>
    </html>
    
    В каталог /var/lib/tomcat11/webapps/who/META-INF помещаем файл context.xml c таким содержимым:
    <?xml version="1.0" encoding="UTF-8"?>
    <Context>
            <Valve className="org.apache.catalina.authenticator.SpnegoAuthenticator"
               storeDelegatedCredential="true" />
    </Context>
    
    В каталог /var/lib/tomcat11/webapps/who/WEB-INF помещаем файл web.xml c таким содержимым:
    <?xml version="1.0" encoding="UTF-8"?>
    <web-app xmlns="https://jakarta.ee/xml/ns/jakartaee"
             xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
             xsi:schemaLocation="https://jakarta.ee/xml/ns/jakartaee https://jakarta.ee/xml/ns/jakartaee/web-app_6_0.xsd"
             version="6.0">
    
      <security-constraint>
        <web-resource-collection>
          <web-resource-name>Protected Area</web-resource-name>
          <url-pattern>/*</url-pattern>
        </web-resource-collection>
        <auth-constraint>
          <role-name>*</role-name>
        </auth-constraint>
      </security-constraint>
    
      <login-config>
        <auth-method>SPNEGO,BASIC</auth-method>
        <realm-name>AD Realm</realm-name>
      </login-config>
    
      <security-role>
        <role-name>*</role-name>
      </security-role>
    
    </web-app>
    
    На a18 проделываем аналогичную процедуру c раскладыванием файлов в /var/lib/tomcat9/webapps , попутно исправляя в файле index.jsp строки, содержащие SECURITY_PRINCIPAL и SECURITY_CREDENTIALS

  16. Перестартовываем tomcat'ы на на appsrv и a18 командами
    sudo systemctl restart tomcat11
    
    sudo systemctl restart tomcat9
    

  17. В Windows для работы SSO в браузере требуется настройка. Вот один из вариантов:

    В реальном домене такие вещи настраиваются не "руками", а через Group Policy Objects сразу для всех пользователей, которым это действительно нужно.

  18. На рабочей станции логинимся доменным пользователем и открываем браузер (в Windows лучше начать с Edge, в Astra Linux - с Firefox) и заходим по адресам http://appsrv.somedom.lan:8080/who/ и http://appsrv.somedom.lan:8080/who/ . Результат должен получиться примерно такой:


  19. Если все работает штатно, во всех конфигах меняем debug=true на debug=false и перестартовываем tomcat'ы. Если этого не сделать, то логи при реальной работе достигнут эпических размеров. Если что-то не работает, читаем логи (благо дебаг включен), гуглим, мучаем AIшечку вопросами и проклинаем тот день, когда было решено заняться настройкой всего этого безобразия.