Java Authentication and Authorization Service

Java Authentication and Authorization Service (англ. Java Authentication and Authorization Service, JAAS) — это компонент платформы Java, представляющий собой реализацию стандартной концепции плагин-модулей аутентификации (PAM) для языка Java[1].

Основное описание

JAAS является стандартной частью платформы Java с версии Java 2 SDK, Standard Edition (J2SDK), начиная с версии 1.4. В версии 1.3 JAAS входил в состав опциональных библиотек. Внедрение JAAS было связано с низкой гибкостью первоначальной системы безопасности, особенно при разработке серверных приложений. Как часть системы безопасности Java 2, JAAS обеспечивает выдачу разрешений и контроль их соблюдения. Все проверки выполняются вне кода приложения, поэтому нет необходимости модифицировать исходный код. Приложение при этом не осведомлено о факте аутентификации и авторизации.

JAAS используется для решения двух задач:

  • Аутентификация — проверка подлинности пользователя (например, путём ввода имени пользователя и пароля). Такой способ обеспечивает безопасное и надёжное подтверждение личности.
  • Авторизация — определение, имеет ли аутентифицированный пользователь права доступа к определённой части системы.

В связи с удалением Security Manager (начиная с Java 17, JEP 411) встроенный механизм авторизации JAAS на основе политик больше не функционирует. Роль JAAS сместилась исключительно на аутентификацию, а авторизация теперь должна реализовываться на уровне самого приложения[2][3].

Состав фреймворка

Основные классы

  • Subject — представляет источник запроса (пользователя или сервис). После аутентификации с Subject могут быть связаны один или несколько объектов Principal и Credential[1].
  • Principal — представляет сущность пользователя. Может быть, например, табельным номером, именем и т. д. Также отражает роли пользователя в системе; у Subject может быть несколько ролей.
  • Credential — объект, доказывающий личность пользователя (имя и пароль, отпечаток пальца и т. д.). Это не часть библиотеки JAAS, так как любая реализация может выступать в роли Credential. Рекомендуется реализовать методы Refreshable и Destroyable, позволяющие обновлять или уничтожать объект. Различают публичные (например, имя) и приватные (например, пароль) Credential.

Классы и интерфейсы аутентификации

  • LoginContext — содержит базовые методы для аутентификации Subject.
  • LoginModule — интерфейс для интеграции пользовательских технологий аутентификации.
  • CallbackHandler — интерфейс для связки между пользователем и LoginModule.
  • Callback — интерфейс для передачи различных аутентификационных сведений.

Классы авторизации

  • Policy — абстрактный класс, описывающий политику безопасности: определяет разрешения, выданные коду, либо пользователю (Principal). Класс помечен как устаревший и подлежащий удалению из-за отказа от Security Manager[4].
  • AuthPermission — базовый класс разрешений в JAAS, использующий предопределённые имена для отдельных видов разрешений. Помечен как устаревший и подлежащий удалению из-за отказа от Security Manager[5].
  • PrivateCredentialPermission — класс для защиты приватных Credential. Помечен как устаревший и подлежащий удалению в будущих версиях Java из-за отказа от Security Manager[6].

Процесс аутентификации и авторизации

Аутентификация

  • Приложение создаёт экземпляр LoginContext[1].
  • LoginContext, используя конфигурацию, загружает все связанные с приложением модули LoginModule.
  • Приложение вызывает метод login у LoginContext.
  • Метод login поочерёдно вызывает все LoginModule, которые осуществляют попытку аутентификации Subject. В случае успеха соответствующие Principal и Credential закрепляются за Subject — субъект считается аутентифицированным.
  • LoginContext сообщает приложению об успешной или неудачной аутентификации.
  • При успехе приложение получает объект Subject из LoginContext.

Авторизация

В связи с удалением Security Manager автоматическая проверка прав через файлы политик больше не осуществляется. После успешной аутентификации приложение должно самостоятельно проверять наличие необходимых Principal (ролей или прав) у полученного Subject[2][7].

Для выполнения кода от имени субъекта вместо устаревшего метода Subject.doAs теперь следует использовать Subject.callAs[8][7].

Примеры

Простой пример проверки разрешения на запись в файл cheese.txt

Примечание: данный пример использует Policy и Security Manager, поддержка которых прекращена (JEP 411), поэтому в современных версиях Java этот код не будет выполнять принудительную проверку разрешений и приводится в исторических целях[4].

Приложение[9]:

package chp02;
import java.io.File;
import java.io.IOException;
public class Chp02aMain {
  public static void main(String[] args) throws IOException {
    File file = new File("build/conf/cheese.txt");
    try {
      file.canWrite();
      System.out.println("Можем записать в cheese.txt");
    } catch (SecurityException e) {
      System.out.println("Не можем записать в cheese.txt");
    }
  }
}
  • Приложение создаёт объект файла cheese.txt.
  • Проверяется наличие разрешения на запись.
  • В зависимости от установленной политики либо осуществляется запись (выводится «Можем записать в cheese.txt»), либо выбрасывается исключение и выводится «Не можем записать в cheese.txt».

Файл конфигурации Policy:

grant
{
 permission java.io.FilePermission "build/conf/cheese.txt", "write";
};
  • Гарантируется разрешение FilePermission для записи в указанный файл.

Пример входа в приложение

Приложение[10]:

CallbackHandler handler = new RanchCallbackHandler(userName, password); 
try {
   LoginContext loginContext = new LoginContext("RanchLogin", handler);
   // Вход
   loginContext.login();
} catch (LoginException e) {
   // Ошибка входа, не удалось аутентифицировать пользователя
   e.printStackTrace();
}
  • Приложение создаёт экземпляр LoginContext.
  • Производится попытка входа в систему.

Файл конфигурации Login:

RanchLogin {
   com.javaranch.auth.RanchLoginModule required;
};
  • Этот файл задаёт, какой модуль LoginModule будет использоваться для аутентификации, и режим его работы.
  • Здесь для имени "RanchLogin" назначен модуль RanchLoginModule, его запуск обязателен для аутентификации пользователя.
  • Настраивается для Java виртуальной машины через параметр: -Djava.security.auth.login.config="JAAS_CONFIG_FILENAME"

Модуль RanchLoginModule:

public boolean login() throws LoginException {
   boolean returnValue = true;
   if (callbackHandler == null){
       throw new LoginException("No callback handler supplied.");
   }
   Callback[] callbacks = new Callback[2];
   callbacks[0] = new NameCallback("Username");
   callbacks[1] = new PasswordCallback("Password", false);
   try {
       callbackHandler.handle(callbacks);
       String userName = ((NameCallback) callbacks[0]).getName();
       char [] passwordCharArray = ((PasswordCallback)  
         callbacks[1]).getPassword();
       String password = new String(passwordCharArray);
//-->аутентификация производится по совпадению имени и пароля         
       returnValue = userName.equals(password);
   } catch (IOException ioe)  {
       ioe.printStackTrace();
       throw new LoginException("IOException occurred:
         "+ioe.getMessage());
   } catch (UnsupportedCallbackException ucbe) {
       ucbe.printStackTrace();
       throw new LoginException("UnsupportedCallbackException encountered:
         "+ucbe.getMessage());
   }
   System.out.println("Вход выполнен");
   return returnValue;
}
  • С помощью Callback и CallbackHandler получают имя пользователя и пароль.
  • Проверяется, совпадают ли имя и пароль.
  • Возвращается значение true или false в зависимости от результата сравнения.

Конфигурационный файл Policy:

grant {
   permission java.util.PropertyPermission "user", "read";
   permission java.util.PropertyPermission "pass", "read";
   permission java.util.PropertyPermission
    "java.security.auth.login.config", "read";
   permission java.util.PropertyPermission "java.security.policy", "read";
   permission javax.security.auth.AuthPermission
    "createLoginContext.RanchLogin";
};
  • Настройка разрешений для создания объекта LoginContext и доступа к атрибутам.
  • Требуется только при активном менеджере безопасности.
  • Примечание: конфигурация Policy больше не применяется в современных версиях Java из-за удаления Security Manager.[2]

Результаты запуска приложения:

Одинаковые имя и пароль

java -Duser=rahul
     -Dpass=rahul
     -Djava.security.auth.login.config=jaas.config
     -jar jaas-example.jar
  • Вход выполнен успешно.

Различные имя и пароль

java -Duser=rahul
     -Dpass=notrahul
     -Djava.security.auth.login.config=jaas.config
     -jar jaas-example.jar
  • Имя и пароль не совпали — вход не выполнен.

Одинаковые имя и пароль + включён менеджер безопасности

java -Duser=rahul
     -Dpass=rahul
     -Djava.security.auth.login.config=jaas.config
     -Djava.security.manager
     -jar jaas-example.jar
  • Не установлены необходимые права — операция доступа не может быть выполнена, вход не выполнен.

Одинаковые имя и пароль + включён менеджер безопасности + установлены права

java -Duser=rahul
     -Dpass=rahul
     -Djava.security.auth.login.config=jaas.config
     -Djava.security.manager
     -Djava.security.policy=policy.config
     -jar jaas-example.jar
  • Вход выполнен успешно.
  • Примечание: запуск с флагом -Djava.security.manager устарел и в новых версиях JDK приведёт к ошибке или будет проигнорирован.

Конфигурационный файл Login подробно

RanchLogin {
   com.javaranch.auth.FirstLoginModule
       requisite debug=true ;
   com.javaranch.auth.SecondLoginModule
       required debug=false email=admin@mydomain.com ;
};

Как видно из примера, для одной схемы аутентификации (RanchLogin) можно определить несколько LoginModule, каждый со своими параметрами (например, debug для FirstLoginModule, debug и email для SecondLoginModule). Различают несколько режимов загрузки модулей[10]:

  • Required — модуль должен аутентифицировать пользователя, иначе осуществляется переход к следующему LoginModule.
  • Requisite — при ошибке входа управление немедленно возвращается приложению, остальные LoginModule не вызываются.
  • Sufficient — если модуль успешно выполнил аутентификацию, считается, что вход совершен, и управление возвращается приложению. Если нет, вызов переходят к следующим модулям.
  • Optional — независимо от результата, всегда передаёт управление следующему LoginModule.

Помимо традиционной файловой конфигурации, в современных приложениях предпочтительным является программный подход с использованием метода Configuration.setConfiguration(). Это обеспечивает большую гибкость и независимость от внешних файлов, позволяя динамически управлять настройками непосредственно из кода приложения[11].

Современное состояние и альтернативы

Для новых проектов JAAS считается устаревающим. Стандартом де-факто для обеспечения безопасности стал фреймворк Spring Security, предоставляющий встроенную поддержку современных протоколов OAuth2, OpenID Connect (OIDC) и SAML[12]. В экосистеме Jakarta EE JAAS сохраняет роль фундаментального компонента, однако взаимодействие с ним происходит через более высокоуровневые API, такие как Jakarta Security и Jakarta Authentication[13].

Примечания

Категории