Panduan Untuk Layanan Otentikasi dan Otorisasi Java (JAAS)

Java Top

Saya baru saja mengumumkan kursus Learn Spring baru , yang berfokus pada dasar-dasar Spring 5 dan Spring Boot 2:

>> LIHAT KURSUSnya

1. Ikhtisar

Java Authentication And Authorization Service (JAAS) adalah kerangka kerja keamanan tingkat rendah Java SE yang menambah model keamanan dari keamanan berbasis kode ke keamanan berbasis pengguna . Kita dapat menggunakan JAAS untuk dua tujuan:

  • Otentikasi: Mengidentifikasi entitas yang saat ini menjalankan kode
  • Otorisasi: Setelah diautentikasi, pastikan bahwa entitas ini memiliki hak kontrol akses atau izin yang diperlukan untuk mengeksekusi kode sensitif

Dalam tutorial ini, kami akan membahas cara menyiapkan JAAS dalam aplikasi sampel dengan mengimplementasikan dan mengonfigurasi berbagai API-nya, terutama LoginModule .

2. Bagaimana JAAS Bekerja

Saat menggunakan JAAS dalam aplikasi, beberapa API terlibat:

  • CallbackHandler : Digunakan untuk mengumpulkan kredensial pengguna dan disediakan secara opsional saat membuat LoginContext
  • Konfigurasi : Bertanggung jawab untuk memuat implementasi LoginModule dan dapat disediakan secara opsional saat pembuatan LoginContext
  • LoginModule : Digunakan secara efektif untuk mengautentikasi pengguna

Kami akan menggunakan implementasi default untuk Configuration API dan menyediakan implementasi kami sendiri untuk CallbackHandler dan LoginModule API.

3. Menyediakan Implementasi CallbackHandler

Sebelum menggali implementasi LoginModule , pertama-tama kita perlu menyediakan implementasi untuk antarmuka CallbackHandler , yang digunakan untuk mengumpulkan kredensial pengguna .

Ini memiliki satu metode, handle () , yang menerima larik Callback . Selain itu, JAAS sudah menyediakan banyak implementasi Callback , dan kami akan menggunakan NameCallback dan PasswordCallback masing-masing untuk mengumpulkan nama pengguna dan kata sandi.

Mari kita lihat implementasi antarmuka CallbackHandler kami :

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

Jadi, untuk meminta dan membaca nama pengguna, kami telah menggunakan:

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

Demikian pula untuk meminta dan membaca kata sandi:

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

Nanti, kita akan melihat cara memanggil CallbackHandler saat mengimplementasikan LoginModule .

4. Memberikan Implementasi LoginModule

Untuk kesederhanaan, kami akan menyediakan implementasi yang menyimpan pengguna hard-code. Jadi, sebut saja 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; //... }

Di subbagian berikutnya, kita akan memberikan implementasi untuk metode yang lebih penting: inisialisasi () , login () , dan komit () .

4.1. menginisialisasi ()

The LoginModule pertama dimuat dan kemudian diinisialisasi dengan Subyek dan CallbackHandler . Selain itu, LoginModule dapat menggunakan Peta untuk berbagi data di antara mereka sendiri, dan Peta lain untuk menyimpan data konfigurasi pribadi:

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

Dalam metode login () , kita memanggil metode CallbackHandler.handle () dengan NameCallback dan PasswordCallback untuk meminta dan mendapatkan nama pengguna dan kata sandi. Kemudian, kami membandingkan kredensial yang diberikan ini dengan yang di-hardcode:

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

Metode login () harus mengembalikan nilai true untuk operasi yang berhasil dan false untuk login yang gagal .

4.3. melakukan()

Jika semua panggilan ke LoginModule # login berhasil, kami memperbarui Subjek dengan Kepala Sekolah tambahan :

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

Jika tidak, metode abort () akan dipanggil.

Pada titik ini, implementasi LoginModule kami sudah siap dan perlu dikonfigurasi agar dapat dimuat secara dinamis menggunakan penyedia layanan Konfigurasi .

5. Konfigurasi LoginModule

JAAS menggunakan penyedia layanan Konfigurasi untuk memuat LoginModule saat runtime. Secara default, ini menyediakan dan menggunakan implementasi ConfigFile di mana LoginModule dikonfigurasi melalui file login. Misalnya, berikut adalah konten file yang digunakan untuk LoginModule kami :

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

Seperti yang bisa kita lihat, kita telah menyediakan nama kelas yang sepenuhnya memenuhi syarat untuk implementasi LoginModule , tanda yang diperlukan , dan opsi untuk debugging.

Terakhir, perhatikan bahwa kita juga dapat menentukan file login melalui properti sistem java.security.auth.login.config :

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

Kita juga dapat menentukan satu atau lebih file login melalui properti login.config.url di file keamanan Java, $ {java.home} /jre/lib/security/java.security :

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

6. Otentikasi

Pertama, aplikasi menginisialisasi proses otentikasi dengan membuat instance LoginContext . Untuk melakukannya, kita dapat melihat konstruktor lengkap untuk mendapatkan gambaran tentang apa yang kita butuhkan sebagai parameter:

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

Seperti otentikasi, kami akan menjalankan aplikasi sederhana untuk otorisasi di mana, selain LoginModule , kami menyediakan file konfigurasi izin:

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

Dalam artikel ini, kami telah memamerkan cara mengimplementasikan JAAS dengan menjelajahi kelas dan antarmuka utama dan menunjukkan cara mengonfigurasinya. Terutama, kami telah menerapkan penyedia layanan LoginModule .

Seperti biasa, kode dalam artikel ini tersedia di GitHub.

Jawa bawah

Saya baru saja mengumumkan kursus Learn Spring baru , yang berfokus pada dasar-dasar Spring 5 dan Spring Boot 2:

>> LIHAT KURSUSnya