Leitfaden zum Java-Authentifizierungs- und Autorisierungsdienst (JAAS)

Java Top

Ich habe gerade den neuen Learn Spring- Kurs angekündigt , der sich auf die Grundlagen von Spring 5 und Spring Boot 2 konzentriert:

>> Überprüfen Sie den Kurs

1. Übersicht

Der Java Authentication and Authorization Service (JAAS) ist ein Java SE-Sicherheitsframework auf niedriger Ebene, das das Sicherheitsmodell von codebasierter Sicherheit zu benutzerbasierter Sicherheit erweitert . Wir können JAAS für zwei Zwecke verwenden:

  • Authentifizierung: Identifizieren der Entität, auf der der Code derzeit ausgeführt wird
  • Autorisierung: Stellen Sie nach der Authentifizierung sicher, dass diese Entität über die erforderlichen Zugriffssteuerungsrechte oder -berechtigungen verfügt, um vertraulichen Code auszuführen

In diesem Tutorial erfahren Sie, wie Sie JAAS in einer Beispielanwendung einrichten, indem Sie die verschiedenen APIs, insbesondere das LoginModule, implementieren und konfigurieren .

2. Wie JAAS funktioniert

Bei der Verwendung von JAAS in einer Anwendung sind mehrere APIs beteiligt:

  • CallbackHandler : Wird zum Sammeln von Benutzeranmeldeinformationen verwendet und optional beim Erstellen des LoginContext bereitgestellt
  • Konfiguration : Verantwortlich für das Laden von LoginModule- Implementierungen und kann optional bei der LoginContext- Erstellung bereitgestellt werden
  • LoginModule : Wird effektiv zur Authentifizierung von Benutzern verwendet

Wir verwenden die Standardimplementierung für die Konfigurations- API und stellen unsere eigenen Implementierungen für die CallbackHandler- und die LoginModule- APIs bereit .

3. Bereitstellung der CallbackHandler- Implementierung

Bevor wir uns mit der LoginModule- Implementierung befassen , müssen wir zunächst eine Implementierung für die CallbackHandler- Schnittstelle bereitstellen, die zum Sammeln von Benutzeranmeldeinformationen verwendet wird .

Es gibt eine einzige Methode, handle () , die ein Array von Rückrufen akzeptiert . Darüber hinaus bietet JAAS bereits viele Callback- Implementierungen, und wir werden NameCallback und PasswordCallback zum Sammeln des Benutzernamens bzw. des Kennworts verwenden.

Sehen wir uns unsere Implementierung der CallbackHandler- Schnittstelle an:

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); } } } }

Um den Benutzernamen aufzufordern und zu lesen, haben wir Folgendes verwendet:

NameCallback nameCallback = (NameCallback) callback; nameCallback.setName(console.readLine(nameCallback.getPrompt()));

Um das Passwort aufzufordern und zu lesen:

PasswordCallback passwordCallback = (PasswordCallback) callback; passwordCallback.setPassword(console.readPassword(passwordCallback.getPrompt()));

Später erfahren Sie, wie Sie den CallbackHandler bei der Implementierung des LoginModule aufrufen .

4. Bereitstellung der LoginModule- Implementierung

Der Einfachheit halber stellen wir eine Implementierung bereit, in der fest codierte Benutzer gespeichert werden. Nennen wir es also InMemoryLoginModule :

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; //... }

In den nächsten Unterabschnitten geben wir eine Implementierung für die wichtigeren Methoden: initialize () , login () und commit () .

4.1. initialisieren()

Das LoginModule wird zuerst geladen und dann mit einem Betreff und einem CallbackHandler initialisiert . Darüber hinaus können LoginModules eine Karte zum Teilen von Daten untereinander und eine andere Karte zum Speichern privater Konfigurationsdaten verwenden:

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. Anmeldung()

In der login () -Methode rufen wir die CallbackHandler.handle () -Methode mit einem NameCallback und einem PasswordCallback auf, um den Benutzernamen und das Kennwort abzurufen und abzurufen . Anschließend vergleichen wir diese bereitgestellten Anmeldeinformationen mit den fest codierten:

@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; }

Die Methode login () sollte für eine erfolgreiche Operation true und für eine fehlgeschlagene Anmeldung false zurückgeben .

4.3. verpflichten()

Wenn alle Aufrufe von LoginModule # login erfolgreich sind, aktualisieren wir den Betreff mit einem zusätzlichen Principal :

@Override public boolean commit() throws LoginException { if (!loginSucceeded) { return false; } userPrincipal = new UserPrincipal(username); subject.getPrincipals().add(userPrincipal); return true; }

Andernfalls wird die abort () -Methode aufgerufen.

Zu diesem Zeitpunkt ist unsere LoginModule- Implementierung fertig und muss so konfiguriert werden, dass sie mithilfe des Konfigurationsdienstanbieters dynamisch geladen werden kann .

5. LoginModule- Konfiguration

JAAS verwendet den Konfigurationsdienstanbieter , um LoginModules zur Laufzeit zu laden . Standardmäßig wird die ConfigFile- Implementierung bereitgestellt und verwendet, bei der LoginModules über eine Anmeldedatei konfiguriert werden. Hier ist zum Beispiel der Inhalt der Datei, die für unser LoginModule verwendet wird :

jaasApplication { com.baeldung.jaas.loginmodule.InMemoryLoginModule required debug=true; };

As we can see, we've provided the fully qualified class name of the LoginModule implementation, a required flag, and an option for debugging.

Finally, note that we can also specify the login file through the java.security.auth.login.config system property:

$ java -Djava.security.auth.login.config=src/main/resources/jaas/jaas.login.config

We can also specify one or more login files through the property login.config.url in the Java security file, ${java.home}/jre/lib/security/java.security:

login.config.url.1=file:${user.home}/.java.login.config

6. Authentication

Firstly, an application initializes the authentication process by creating a LoginContext instance. To do so, we can take a look at the full constructor to have an idea about what we need as parameters:

LoginContext(String name, Subject subject, CallbackHandler callbackHandler, Configuration config)
  • name: used as an index for loading only the corresponding LoginModules
  • subject: represents a user or service that wants to log in
  • callbackHandler: responsible for passing user credentials from the application to the LoginModule
  • config: responsible for loading LoginModules that correspond to the name parameter

Here, we'll be using the overloaded constructor where we'll be providing our CallbackHandler implementation:

LoginContext(String name, CallbackHandler callbackHandler)

Now that we have a CallbackHandler and a configured LoginModule, we can start the authentication process by initializing a LoginContext object:

LoginContext loginContext = new LoginContext("jaasApplication", new ConsoleCallbackHandler());

At this point, we can invoke the login() method to authenticate the user:

loginContext.login();

The login() method, in turn, creates a new instance of our LoginModule and calls its login() method. And, upon successful authentication, we can retrieve the authenticated Subject:

Subject subject = loginContext.getSubject();

Now, let's run a sample application that has the LoginModule wired in:

$ 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

When we're prompted to provide the username and password, we'll use testuser and testpassword as credentials.

7. Authorization

Authorization comes into play when the user is first connected and associated with the AccessControlContext. Using the Java security policy, we can grant one or more access control rights to Principals. We can then prevent access to sensitive code by calling the SecurityManager#checkPermission method:

SecurityManager.checkPermission(Permission perm)

7.1. Defining Permissions

An access control right or permission is the ability to execute an action on a resource. We can implement a permission by subclassing the Permission abstract class. To do so, we need to provide a resource name and a set of possible actions. For example, we can use FilePermission to configure access control rights on files. Possible actions are read, write, execute, and so on. For scenarios where actions are not necessary, we may simply use the BasicPermision.

Next, we'll provide an implementation of permission through the ResourcePermission class where users may have permission to access a resource:

public final class ResourcePermission extends BasicPermission { public ResourcePermission(String name) { super(name); } }

Later, we'll configure an entry for this permission through the Java security policy.

7.2. Granting Permissions

Usually, we don't need to know the policy file syntax because we can always use the Policy Tool to create one. Let's take a look at our policy file:

grant principal com.sun.security.auth.UserPrincipal testuser { permission com.baeldung.jaas.ResourcePermission "test_resource" };

In this sample, we've granted the test_resource permission to the testuser user.

7.3. Checking Permissions

Once the Subject is authenticated and permissions are configured, we can check for access by calling the Subject#doAs or Subject#doAsPrivilieged static methods. For this purpose, we'll provide a PrivilegedAction where we can protect access to sensitive code. In the run() method, we call the SecurityManager#checkPermission method to ensure that the authenticated user has the test_resource permission:

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; } }

The last thing is to call the Subject#doAsPrivileged method:

Subject subject = loginContext.getSubject(); PrivilegedAction privilegedAction = new ResourceAction(); Subject.doAsPrivileged(subject, privilegedAction, null);

Wie bei der Authentifizierung führen wir eine einfache Anwendung für die Autorisierung aus, in der wir zusätzlich zum LoginModule eine Konfigurationsdatei für Berechtigungen bereitstellen:

$ 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. Fazit

In diesem Artikel haben wir gezeigt, wie JAAS implementiert wird, indem die Hauptklassen und Schnittstellen untersucht und ihre Konfiguration gezeigt werden. Insbesondere haben wir einen Dienstleister LoginModule implementiert .

Wie üblich ist der Code in diesem Artikel auf GitHub verfügbar.

Java unten

Ich habe gerade den neuen Learn Spring- Kurs angekündigt , der sich auf die Grundlagen von Spring 5 und Spring Boot 2 konzentriert:

>> Überprüfen Sie den Kurs