1. Обзор
Служба аутентификации и авторизации Java (JAAS) – это низкоуровневая структура безопасности Java SE, которая расширяет модель безопасности от безопасности на основе кода до безопасности на основе пользователя . Мы можем использовать JAAS для двух целей:
- Аутентификация: идентификация сущности, на которой в данный момент выполняется код.
- Авторизация: После проверки подлинности убедитесь, что этот объект имеет необходимые права управления доступом или разрешения для выполнения конфиденциального кода.
В этом руководстве мы рассмотрим, как настроить JAAS в примере приложения, реализуя и настраивая его различные API, особенно LoginModule .
2. Как работает JAAS
При использовании JAAS в приложении задействовано несколько API:
- CallbackHandler : Используется для сбора учетных данных пользователя и дополнительно предоставляется при создании LoginContext
- Конфигурация : Отвечает за загрузку LoginModule реализаций и может быть дополнительно предоставлена в LoginContext creation
- Модуль входа : Эффективно используется для аутентификации пользователей
Мы будем использовать реализацию по умолчанию для Конфигурации API и предоставим наши собственные реализации для CallbackHandler и LoginModule API.
3. Обеспечение реализации обратного вызова
Прежде чем копаться в реализации LoginModule , нам сначала нужно предоставить реализацию для интерфейса CallbackHandler , который используется для сбора учетных данных пользователя .
У него есть один метод/| handle() , который принимает массив Callback s. Кроме того, JAAS уже предоставляет множество реализаций Обратного вызова , и мы будем использовать NameCallback и PasswordCallback для сбора имени пользователя и пароля соответственно.
Давайте посмотрим на нашу реализацию интерфейса CallbackHandler :
public class ConsoleCallbackHandler implements CallbackHandler { @Override public void handle(Callback[] callbacks) throws UnsupportedCallbackException { Console console = System.console(); for (Callback callback : callbacks) { if (callback instanceof NameCallback) { NameCallback nameCallback = (NameCallback) callback; nameCallback.setName(console.readLine(nameCallback.getPrompt())); } else if (callback instanceof PasswordCallback) { PasswordCallback passwordCallback = (PasswordCallback) callback; passwordCallback.setPassword(console.readPassword(passwordCallback.getPrompt())); } else { throw new UnsupportedCallbackException(callback); } } } }
Итак, чтобы запросить и прочитать имя пользователя, мы использовали:
NameCallback nameCallback = (NameCallback) callback; nameCallback.setName(console.readLine(nameCallback.getPrompt()));
Аналогично, чтобы запросить и прочитать пароль:
PasswordCallback passwordCallback = (PasswordCallback) callback; passwordCallback.setPassword(console.readPassword(passwordCallback.getPrompt()));
Позже мы увидим, как вызвать CallbackHandler при реализации модуля Login .
4. Обеспечение реализации ЛогинМодуля
Для простоты мы предоставим реализацию, которая хранит жестко закодированных пользователей. Итак, давайте назовем его В памяти LoginModule :
public class InMemoryLoginModule implements LoginModule { private static final String USERNAME = "testuser"; private static final String PASSWORD = "testpassword"; private Subject subject; private CallbackHandler callbackHandler; private MapsharedState; private Map options; private boolean loginSucceeded = false; private Principal userPrincipal; //... }
В следующих подразделах мы дадим реализацию для более важных методов: initialize() , login () и commit() .
4.1. инициализация()
LoginModule сначала загружается, а затем инициализируется с помощью Subject и CallbackHandler . Кроме того, LoginModule s может использовать Map для обмена данными между собой, а другой Map для хранения личных данных конфигурации:
public void initialize( Subject subject, CallbackHandler callbackHandler, MapsharedState, Map options) { this.subject = subject; this.callbackHandler = callbackHandler; this.sharedState = sharedState; this.options = options; }
4.2. вход в систему()
В методе login() мы вызываем метод CallbackHandler.handler() с помощью NameCallback и PasswordCallback для запроса и получения имени пользователя и пароля. Затем мы сравниваем эти предоставленные учетные данные с жестко закодированными:
@Override public boolean login() throws LoginException { NameCallback nameCallback = new NameCallback("username: "); PasswordCallback passwordCallback = new PasswordCallback("password: ", false); try { callbackHandler.handle(new Callback[]{nameCallback, passwordCallback}); String username = nameCallback.getName(); String password = new String(passwordCallback.getPassword()); if (USERNAME.equals(username) && PASSWORD.equals(password)) { loginSucceeded = true; } } catch (IOException | UnsupportedCallbackException e) { //... } return loginSucceeded; }
Метод login() должен возвращать true для успешной операции и false для неудачного входа в систему .
4.3. фиксация()
Если все вызовы LoginModule#login завершатся успешно, мы обновим Subject дополнительным Principal :
@Override public boolean commit() throws LoginException { if (!loginSucceeded) { return false; } userPrincipal = new UserPrincipal(username); subject.getPrincipals().add(userPrincipal); return true; }
В противном случае вызывается метод abort () .
На данный момент наша реализация модуля Login готова и должна быть настроена таким образом, чтобы ее можно было динамически загружать с помощью поставщика услуг Configuration .
5. Конфигурация модуля Входа в систему
JAAS использует Конфигурацию поставщика услуг для загрузки Модуля входа s во время выполнения. По умолчанию он предоставляет и использует реализацию Config File , в которой Login Module s настраиваются через файл входа. Например, вот содержимое файла, используемого для нашего модуля Login :
jaasApplication { com.baeldung.jaas.loginmodule.InMemoryLoginModule required debug=true; };
Как мы видим, мы предоставили полное имя класса LoginModule implementation , флаг required и опцию для отладки.
Наконец, обратите внимание, что мы также можем указать файл входа в систему через java.security.auth.login.config системное свойство:
$ java -Djava.security.auth.login.config=src/main/resources/jaas/jaas.login.config
Мы также можем указать один или несколько файлов входа через свойство login.config.url в файле безопасности Java, ${java.home}/jre/lib/security/java.security :
login.config.url.1=file:${user.home}/.java.login.config
6. Аутентификация
Во-первых, приложение инициализирует процесс аутентификации, создавая LoginContext экземпляр . Для этого мы можем взглянуть на полный конструктор, чтобы иметь представление о том, что нам нужно в качестве параметров:
LoginContext(String name, Subject subject, CallbackHandler callbackHandler, Configuration config)
- имя : используется в качестве индекса для загрузки только соответствующего LoginModule s
- тема : представляет пользователя или службу, которая хочет войти в систему
- CallbackHandler : отвечает за передачу учетных данных пользователя из приложения в модуль Login
- config : отвечает за загрузку Модуля входа s, соответствующих параметру name
Здесь мы будем использовать перегруженный конструктор, в котором мы будем предоставлять нашу реализацию CallbackHandler :
LoginContext(String name, CallbackHandler callbackHandler)
Теперь, когда у нас есть CallbackHandler и настроенный Модуль входа в систему , мы можем запустить процесс аутентификации, инициализировав LoginContext объект :
LoginContext loginContext = new LoginContext("jaasApplication", new ConsoleCallbackHandler());
На этом этапе мы можем вызвать метод login() для аутентификации пользователя :
loginContext.login();
Метод login () , в свою очередь, создает новый экземпляр нашего модуля Login и вызывает его метод login () . И, после успешной аутентификации, мы можем получить аутентифицированный Субъект :
Subject subject = loginContext.getSubject();
Теперь давайте запустим пример приложения с подключенным модулем Login :
$ mvn clean package $ java -Djava.security.auth.login.config=src/main/resources/jaas/jaas.login.config \ -classpath target/core-java-security-2-0.1.0-SNAPSHOT.jar com.baeldung.jaas.JaasAuthentication
Когда нам будет предложено ввести имя пользователя и пароль, мы будем использовать testuser и testpassword в качестве учетных данных.
7. Авторизация
Авторизация вступает в игру, когда пользователь впервые подключен и связан с AccessControlContext . Используя политику безопасности Java, мы можем предоставить одно или несколько прав управления доступом Principal s. Затем мы можем предотвратить доступ к конфиденциальному коду, вызвав метод SecurityManager#checkPermission :
SecurityManager.checkPermission(Permission perm)
7.1. Определение разрешений
Право управления доступом или разрешение-это возможность выполнить действие над ресурсом . Мы можем реализовать разрешение, создав подкласс абстрактного класса Permission . Для этого нам нужно указать имя ресурса и набор возможных действий. Например, мы можем использовать Разрешение файла для настройки прав управления доступом к файлам. Возможные действия: чтение , запись , выполнение и так далее. Для сценариев, в которых действия не требуются, мы можем просто использовать разрешение Basic .
Затем мы предоставим реализацию разрешения через класс ResourcePermission , в котором пользователи могут иметь разрешение на доступ к ресурсу:
public final class ResourcePermission extends BasicPermission { public ResourcePermission(String name) { super(name); } }
Позже мы настроим запись для этого разрешения с помощью политики безопасности Java.
7.2. Предоставление Разрешений
Обычно нам не нужно знать синтаксис файла политики, потому что мы всегда можем использовать инструмент Policy/| для его создания. Давайте взглянем на наш файл политики:
grant principal com.sun.security.auth.UserPrincipal testuser { permission com.baeldung.jaas.ResourcePermission "test_resource" };
В этом примере мы предоставили разрешение test_resource пользователю testuser .
7.3. Проверка разрешений
Как только Субъект аутентифицирован и настроены разрешения, мы можем проверить наличие доступа, вызвав Субъект#doAs или Субъект#doAsPrivilieged статические методы . Для этой цели мы предоставим PrivilegedAction , где мы сможем защитить доступ к конфиденциальному коду. В методе run() мы вызываем метод SecurityManager#checkPermission , чтобы убедиться, что аутентифицированный пользователь имеет разрешение test_resource :
public class ResourceAction implements PrivilegedAction { @Override public Object run() { SecurityManager sm = System.getSecurityManager(); if (sm != null) { sm.checkPermission(new ResourcePermission("test_resource")); } System.out.println("I have access to test_resource !"); return null; } }
Последнее, что нужно сделать, это вызвать метод Subject#doAsPrivileged :
Subject subject = loginContext.getSubject(); PrivilegedAction privilegedAction = new ResourceAction(); Subject.doAsPrivileged(subject, privilegedAction, null);
Как и аутентификация, мы запустим простое приложение для авторизации, где в дополнение к модулю Login мы предоставим файл конфигурации разрешений:
$ mvn clean package $ java -Djava.security.manager -Djava.security.policy=src/main/resources/jaas/jaas.policy \ -Djava.security.auth.login.config=src/main/resources/jaas/jaas.login.config \ -classpath target/core-java-security-2-0.1.0-SNAPSHOT.jar com.baeldung.jaas.JaasAuthorization
8. Заключение
В этой статье мы продемонстрировали, как реализовать JAAS, изучив основные классы и интерфейсы и показав, как их настроить. В частности, мы внедрили модуль входа поставщика услуг .
Как обычно, код в этой статье доступен на GitHub .