1. 概述
Java Authentication and Authorization Service(简称 JAAS)是 Java SE 提供的一套底层安全框架,它的核心价值在于 将 Java 安全模型从“代码级安全”扩展为“用户级安全”。这意味着我们不仅能控制哪些代码能运行,还能控制“谁”可以运行这些代码。
JAAS 主要用于两个关键场景:
✅ 认证(Authentication):确认当前执行代码的用户身份
✅ 授权(Authorization):在用户通过认证后,检查其是否具备执行敏感操作的权限
本文将通过一个实际示例,带你从零搭建 JAAS 认证授权体系,重点剖析其核心组件,尤其是 LoginModule
的实现与配置。
2. JAAS 核心工作原理
在应用中使用 JAAS,主要涉及以下几个核心接口:
CallbackHandler
:负责收集用户凭证(如用户名、密码),通常在创建LoginContext
时传入Configuration
:负责加载配置好的LoginModule
实现,可选地在LoginContext
创建时指定LoginModule
:真正执行用户认证逻辑的模块
本文中,我们将使用 JAAS 默认的 Configuration
实现,而 CallbackHandler
和 LoginModule
将由我们自定义实现。
3. 实现 CallbackHandler
在深入 LoginModule
之前,必须先实现 CallbackHandler
,因为它负责与用户交互,获取登录凭证。
CallbackHandler
接口只有一个 handle()
方法,接收一个 Callback
数组。JAAS 内置了多种 Callback
类型,这里我们使用:
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);
}
}
}
}
⚠️ 注意:System.console()
在某些 IDE 环境下可能为 null
,建议在命令行下测试。
4. 实现 LoginModule
为了简化示例,我们实现一个基于内存的 LoginModule
,命名为 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;
}
接下来我们逐步实现 LoginModule
的关键方法。
4.1 initialize()
LoginModule
在加载后,会先调用 initialize()
方法进行初始化,传入 Subject
、CallbackHandler
,以及两个用于数据共享和配置的 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()
login()
方法是认证的核心,它通过 CallbackHandler
获取用户输入,并与预设凭证比对:
@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) {
throw new LoginException("Callback error: " + e.getMessage());
}
return loginSucceeded;
}
✅ 关键点:认证成功返回 true
,失败返回 false
。异常情况下抛出 LoginException
。
4.3 commit()
如果 login()
成功,JAAS 会调用 commit()
方法,将认证成功的用户信息绑定到 Subject
上:
@Override
public boolean commit() throws LoginException {
if (!loginSucceeded) {
return false;
}
userPrincipal = new UserPrincipal(USERNAME);
subject.getPrincipals().add(userPrincipal);
return true;
}
这一步相当于“把用户信息写入会话”。如果认证失败,则调用 abort()
回滚。
5. LoginModule 配置
JAAS 通过 Configuration
服务提供者在运行时动态加载 LoginModule
。默认使用基于文件的 ConfigFile
实现。
我们需要创建一个配置文件(如 jaas.login.config
),内容如下:
jaasApplication {
com.baeldung.jaas.loginmodule.InMemoryLoginModule required debug=true;
};
其中:
jaasApplication
:应用名称,作为LoginContext
的参数com.baeldung.jaas.loginmodule.InMemoryLoginModule
:LoginModule
实现类的全限定名required
:表示该模块必须成功(JAAS 支持多种控制标志)debug=true
:开启调试日志
启动时通过 JVM 参数指定配置文件位置:
$ java -Djava.security.auth.login.config=src/main/resources/jaas/jaas.login.config
也可在 java.security
文件中通过 login.config.url.1
等属性指定。
6. 执行认证
认证流程从创建 LoginContext
开始。我们使用以下构造函数:
LoginContext(String name, CallbackHandler callbackHandler)
参数说明:
name
:对应配置文件中的应用名(如jaasApplication
)callbackHandler
:我们实现的凭证收集器
开始认证:
LoginContext loginContext = new LoginContext("jaasApplication", new ConsoleCallbackHandler());
loginContext.login(); // 触发认证流程
login()
方法会自动加载配置的 LoginModule
并调用其 login()
方法。
认证成功后,可通过以下方式获取用户主体:
Subject subject = loginContext.getSubject();
完整启动命令示例:
$ 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. 执行授权
认证通过后,JAAS 会将 Subject
与当前的 AccessControlContext
关联。接下来就可以基于策略进行授权控制。
7.1 定义权限
权限(Permission)代表对某个资源执行某种操作的能力。我们可以继承 BasicPermission
定义自定义权限:
public final class ResourcePermission extends BasicPermission {
public ResourcePermission(String name) {
super(name);
}
}
这里定义了一个名为 test_resource
的资源权限。
7.2 授予权限
通过 Java 安全策略文件(policy file)为特定用户授予权限。策略文件内容如下:
grant principal com.sun.security.auth.UserPrincipal testuser {
permission com.baeldung.jaas.ResourcePermission "test_resource";
};
✅ 这行配置表示:授予 testuser
用户对 test_resource
的访问权限。
7.3 检查权限
在敏感代码执行前,使用 Subject.doAsPrivileged()
并调用 SecurityManager.checkPermission()
进行权限校验:
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 subject = loginContext.getSubject();
PrivilegedAction privilegedAction = new ResourceAction();
Subject.doAsPrivileged(subject, privilegedAction, null);
完整启动命令(启用安全管理器):
$ 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
如果用户没有权限,会抛出 AccessControlException
。
8. 总结
本文通过一个完整示例,演示了 JAAS 的核心组件实现与配置流程:
- ✅ 自定义
CallbackHandler
收集用户凭证 - ✅ 实现
LoginModule
完成认证逻辑 - ✅ 配置 JAAS 登录模块与安全策略
- ✅ 结合
Subject.doAsPrivileged()
实现细粒度授权
JAAS 虽然学习曲线较陡,但其模块化设计非常适合需要高安全级别的企业级应用。踩坑提示:务必在真实环境中测试 System.console()
的可用性,并合理使用 debug
模式排查配置问题。
文中所有代码已托管至 GitHub:
https://github.com/eugenp/tutorials/tree/master/core-java-modules/core-java-security-2