Guide To The Java Authentication And Authorization Service (JAAS) – Java认证和授权服务(JAAS)指南

最后修改: 2020年 3月 14日

中文/混合/英文(键盘快捷键:t)

1. Overview

1.概述

Java Authentication And Authorization Service (JAAS) is a Java SE low-level security framework that augments the security model from code-based security to user-based security. We can use JAAS for two purposes:

Java Authentication And Authorization Service(JAAS)是一个Java SE底层安全框架,将安全模型从基于代码的安全提升到基于用户的安全。我们可以将JAAS用于两个目的。

  • Authentication: Identifying the entity that is currently running the code
  • Authorization: Once authenticated, ensure that this entity has the required access control rights or permissions to execute sensitive code

In this tutorial, we’ll cover how to set up JAAS in a sample application by implementing and configuring its various APIs, especially the LoginModule.

在本教程中,我们将介绍如何通过实现和配置JAAS的各种API,特别是LoginModule,在一个示例应用程序中设置JAAS。

2. How JAAS Works

2.JAAS是如何工作的

When using JAAS in an application, several APIs are involved:

当在一个应用程序中使用JAAS时,涉及到几个API。

  • CallbackHandler: Used for gathering user credentials and optionally provided when creating the LoginContext
  • Configuration: Responsible for loading LoginModule implementations and can be optionally provided at LoginContext creation
  • LoginModule: Effectively used for authenticating users

We’ll use the default implementation for the Configuration API and provide our own implementations for the CallbackHandler and the LoginModule APIs.

我们将使用Configuration API的默认实现,并为CallbackHandlerLoginModule API提供我们自己的实现。

3. Providing CallbackHandler Implementation

3.提供CallbackHandler实现

Before digging into the LoginModule implementation, we first need to provide an implementation for the CallbackHandler interface, which is used for gathering user credentials.

在深入研究LoginModule实现之前,我们首先需要CallbackHandler接口提供一个实现,该接口用于收集用户证书

It has a single method, handle(), that accepts an array of Callbacks. In addition, JAAS already provides many Callback implementations, and we’ll be using the NameCallback and PasswordCallback for gathering the username and password, respectively.

它有一个方法,handle(),接受一个Callback数组。此外,JAAS已经提供了许多Callback实现,我们将使用NameCallbackPasswordCallback来分别收集用户名和密码。

Let’s see our implementation of the CallbackHandler interface:

让我们看看我们对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);
            }
        }
    }
}

So, to prompt and read the username, we’ve used:

所以,为了提示和读取用户名,我们使用了。

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

Similarly, to prompt and read the password:

同样地,要提示并读取密码。

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

Later, we’ll see how to call the CallbackHandler when implementing the LoginModule.

稍后,我们将看到在实现LoginModule时如何调用CallbackHandler

4. Providing LoginModule Implementation

4.提供LoginModule实现

For simplicity, we’ll provide an implementation that stores hard-coded users. So, let’s call it InMemoryLoginModule:

为了简单起见,我们将提供一个存储硬编码用户的实现。所以,让我们把它叫做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<String, ?> sharedState;
    private Map<String, ?> options;
    
    private boolean loginSucceeded = false;
    private Principal userPrincipal;
    //...
}

In the next subsections, we’ll be giving an implementation for the more important methods: initialize(), login(), and commit().

在接下来的小节中,我们将给出更重要的方法的实现。initialize(), login(), 和commit()

4.1. initialize()

4.1.initialize()

The LoginModule is first loaded and then initialized with a Subject and a CallbackHandler. Additionally, LoginModules can use a Map for sharing data among themselves, and another Map for storing private configuration data:

首先加载LoginModule,然后用一个Subject和一个CallbackHandler来初始化。此外,LoginModules可以使用一个Map在它们之间共享数据,还有一个Map用于存储私有配置数据。

public void initialize(
  Subject subject, CallbackHandler callbackHandler, Map<String, ?> sharedState, Map<String, ?> options) {
    this.subject = subject;
    this.callbackHandler = callbackHandler;
    this.sharedState = sharedState;
    this.options = options;
}

4.2. login()

4.2. login()

In the login() method, we invoke the CallbackHandler.handle() method with a NameCallback and a PasswordCallback to prompt and get the username and password. Then, we compare these provided credentials with the hardcoded ones:

login()方法中,我们调用CallbackHandler.handle()方法的NameCallbackPasswordCallback来提示并获得用户名和密码。然后,我们将这些提供的证书与硬编码的证书进行比较。

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

The login() method should return true for a successful operation and false for a failed login.

login()方法应该在操作成功时返回true,在登录失败时返回false

4.3. commit()

4.3.commit()

If all calls to LoginModule#login succeed, we update the Subject with an additional Principal:

如果对LoginModule#login的所有调用都成功了,我们更新Subject,增加Principal

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

Otherwise, the abort() method is called.

否则,将调用abort()方法。

At this point, our LoginModule implementation is ready and needs to be configured so that it can be loaded dynamically using the Configuration service provider.

在这一点上,我们的LoginModule实现已经准备好了,需要对其进行配置,以便使用Configuration服务提供者动态加载。

5. LoginModule Configuration

5.LoginModule 配置

JAAS uses the Configuration service provider to load LoginModules at runtime. By default, it provides and uses the ConfigFile implementation where LoginModules are configured through a login file. For example, here is the content of the file used for our LoginModule:

JAAS使用Configuration服务提供者在运行时加载LoginModules。默认情况下,它提供并使用ConfigFile实现,其中LoginModules是通过一个登录文件配置的。例如,这里是用于我们的LoginModule的文件内容。

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.

我们可以看到,我们提供了LoginModule实现的完全合格的类名,一个required标志,以及一个用于调试的选项。

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

最后,请注意,我们也可以通过java.security.Auth.login.config系统属性指定登录文件。

$ 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:

我们还可以通过Java安全文件login.config.url中的属性${java.home}/jre/lib/security/java.security指定一个或多个登录文件。

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

6. Authentication

6.身份验证

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实例来初始化认证过程。为此,我们可以看一下完整的构造函数,以了解我们需要哪些参数。

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:

在这里,我们将使用重载的构造函数,提供我们的CallbackHandler实现。

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:

现在我们有了一个CallbackHandler和一个配置好的LoginModule我们可以通过初始化一个LoginContext对象来启动认证过程

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

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

在这一点上,我们可以调用login()方法来验证用户

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:

login()方法则创建一个新的LoginModule实例,并调用其login()方法。而且,一旦认证成功,我们就可以检索到认证的Subject

Subject subject = loginContext.getSubject();

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

现在,让我们运行一个有LoginModule连接的示例应用程序。

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

当我们被提示提供用户名和密码时,我们将使用testusertestpassword作为凭证。

7. Authorization

7.授权

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:

当用户首次连接并与AccessControlContext关联时,授权就开始发挥作用。使用Java安全策略,我们可以向Principals授予一个或多个访问控制权。然后我们可以通过调用SecurityManager#checkPermission方法来防止对敏感代码的访问:

SecurityManager.checkPermission(Permission perm)

7.1. Defining Permissions

7.1.定义权限

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.

访问控制权或权限是在资源上执行动作的能力。我们可以通过子类化Permission抽象类来实现一个许可。为此,我们需要提供一个资源名称和一组可能的动作。例如,我们可以使用FilePermission来配置文件的访问控制权。可能的动作有执行,等等。对于不需要操作的情况,我们可以简单地使用BasicPermision

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

接下来,我们将通过ResourcePermission类提供一个权限的实现,用户可以有权限访问一个资源。

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.

稍后,我们将通过Java安全策略为这个权限配置一个条目。

7.2. Granting Permissions

7.2.授予权限

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.

在这个例子中,我们将test_resource权限授予testuser用户

7.3. Checking Permissions

7.3.检查权限

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:

一旦Subject被验证并且配置了权限,我们可以通过调用Subject#doAsSubject#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;
    }
}

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

最后是调用Subject#doAsPrivileged方法。

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

Like the authentication, we’ll run a simple application for the authorization where, in addition to the LoginModule, we provide a permissions configuration file:

和认证一样,我们将为授权运行一个简单的应用程序,除了LoginModule之外,我们还提供一个权限配置文件。

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

8.结论

In this article, we have showcased how to implement JAAS by exploring the principal classes and interfaces and showing how to configure them. Especially, we have implemented a service provider LoginModule.

在这篇文章中,我们展示了如何通过探索主要的类和接口来实现JAAS,并展示了如何配置它们。特别是,我们实现了一个服务提供者LoginModule

As usual, the code in this article is available over on GitHub.

像往常一样,本文中的代码可以在GitHub上找到