Рубрики
Без рубрики

Руководство По Службе Аутентификации И Авторизации Java (JAAS)

Узнайте о структуре безопасности JAAS, которая расширяет модель безопасности от безопасности на основе кода до безопасности на основе пользователей

Автор оригинала: baeldung.

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 Map sharedState;
    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, Map sharedState, 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 .