Overview
Java Authentication and Authorization Service (JAAS) is a Java security framework that provides a pluggable and extensible architecture for authenticating users and enforcing access controls. It separates the concerns of authentication from authorization and allows for flexible security implementations.
Core Concepts
- Subject: Represents the current user or service
- Principal: Identity attribute (username, role, group)
- Credential: Proof of identity (password, certificate)
- LoginContext: Entry point for authentication
- LoginModule: Pluggable authentication modules
- CallbackHandler: Handles user interaction during login
Basic Setup and Dependencies
Maven Dependencies
<dependencies>
<!-- Core JAAS (part of Java SE) -->
<dependency>
<groupId>javax.security.auth</groupId>
<artifactId>jaas</artifactId>
<version>1.0.1</version>
<scope>system</scope>
<systemPath>${java.home}/lib/jaas.jar</systemPath>
</dependency>
<!-- For custom LoginModules -->
<dependency>
<groupId>javax.security</groupId>
<artifactId>jacc</artifactId>
<version>1.0</version>
</dependency>
</dependencies>
Basic JAAS Authentication
Example 1: Simple File-Based Authentication
import javax.security.auth.*;
import javax.security.auth.login.*;
import javax.security.auth.callback.*;
import java.io.*;
import java.util.*;
public class BasicJAASAuthentication {
public static void main(String[] args) {
try {
// Create LoginContext with configuration name and callback handler
LoginContext loginContext = new LoginContext("SimpleFileLogin", new SimpleCallbackHandler());
// Perform authentication
loginContext.login();
System.out.println("Authentication successful!");
// Get the authenticated subject
Subject subject = loginContext.getSubject();
System.out.println("Subject: " + subject);
System.out.println("Principals: " + subject.getPrincipals());
// Perform secured action
performSecuredAction(subject);
// Logout
loginContext.logout();
System.out.println("Logout successful!");
} catch (LoginException e) {
System.err.println("Authentication failed: " + e.getMessage());
}
}
private static void performSecuredAction(Subject subject) {
// Execute action with subject's permissions
Subject.doAs(subject, new PrivilegedAction<Void>() {
@Override
public Void run() {
System.out.println("Performing secured action as: " +
Subject.getSubject(AccessController.getContext()));
// Perform some secured operation here
return null;
}
});
}
}
// Custom CallbackHandler for handling user interaction
class SimpleCallbackHandler implements CallbackHandler {
@Override
public void handle(Callback[] callbacks) throws IOException, UnsupportedCallbackException {
for (Callback callback : callbacks) {
if (callback instanceof NameCallback) {
NameCallback nameCallback = (NameCallback) callback;
System.out.print(nameCallback.getPrompt());
nameCallback.setName(new Scanner(System.in).nextLine());
} else if (callback instanceof PasswordCallback) {
PasswordCallback passwordCallback = (PasswordCallback) callback;
System.out.print(passwordCallback.getPrompt());
passwordCallback.setPassword(new Scanner(System.in).nextLine().toCharArray());
} else {
throw new UnsupportedCallbackException(callback, "Unsupported callback type");
}
}
}
}
JAAS Configuration File (jaas.config)
SimpleFileLogin {
com.example.SimpleFileLoginModule required debug=true;
};
DatabaseLogin {
com.example.DatabaseLoginModule sufficient
dbUrl="jdbc:mysql://localhost:3306/authdb"
dbUser="auth_user"
dbPassword="auth_pass";
};
LDAPLogin {
com.example.LDAPLoginModule sufficient
ldapUrl="ldap://localhost:389"
baseDN="dc=example,dc=com";
};
CompositeLogin {
com.example.LDAPLoginModule sufficient
ldapUrl="ldap://localhost:389"
baseDN="dc=example,dc=com";
com.example.DatabaseLoginModule sufficient
dbUrl="jdbc:mysql://localhost:3306/authdb";
com.example.SimpleFileLoginModule required;
};
Custom LoginModule Implementation
Example 2: File-Based LoginModule
import javax.security.auth.*;
import javax.security.auth.spi.*;
import javax.security.auth.callback.*;
import javax.security.auth.login.*;
import java.io.*;
import java.util.*;
import java.security.Principal;
public class SimpleFileLoginModule implements LoginModule {
private Subject subject;
private CallbackHandler callbackHandler;
private Map<String, ?> sharedState;
private Map<String, ?> options;
private String username;
private Set<Principal> principals;
private boolean authenticated = false;
private boolean committed = false;
// Configuration options
private String userFile;
private boolean debug;
@Override
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;
// Read configuration options
this.userFile = (String) options.get("userFile");
this.debug = "true".equals(options.get("debug"));
if (this.userFile == null) {
this.userFile = "users.properties";
}
if (debug) {
System.out.println("SimpleFileLoginModule initialized");
}
}
@Override
public boolean login() throws LoginException {
if (callbackHandler == null) {
throw new LoginException("No CallbackHandler available");
}
try {
// Create callbacks for username and password
NameCallback nameCallback = new NameCallback("Username: ");
PasswordCallback passwordCallback = new PasswordCallback("Password: ", false);
Callback[] callbacks = new Callback[]{nameCallback, passwordCallback};
callbackHandler.handle(callbacks);
username = nameCallback.getName();
char[] password = passwordCallback.getPassword();
if (username == null || password == null) {
throw new LoginException("Username or password cannot be null");
}
// Authenticate against user file
authenticated = authenticateUser(username, new String(password));
if (authenticated) {
principals = new HashSet<>();
principals.add(new UsernamePrincipal(username));
principals.add(new RolePrincipal("USER")); // Default role
if (debug) {
System.out.println("Authentication successful for user: " + username);
}
return true;
} else {
throw new FailedLoginException("Invalid username or password");
}
} catch (IOException | UnsupportedCallbackException e) {
LoginException le = new LoginException("Authentication process failed");
le.initCause(e);
throw le;
}
}
private boolean authenticateUser(String username, String password) {
Properties users = new Properties();
try (InputStream input = new FileInputStream(userFile)) {
users.load(input);
String storedPassword = users.getProperty(username);
return storedPassword != null && storedPassword.equals(password);
} catch (IOException e) {
System.err.println("Error reading user file: " + e.getMessage());
return false;
}
}
@Override
public boolean commit() throws LoginException {
if (!authenticated) {
return false;
}
// Add principals to subject
subject.getPrincipals().addAll(principals);
committed = true;
if (debug) {
System.out.println("Login committed for user: " + username);
}
return true;
}
@Override
public boolean abort() throws LoginException {
if (!authenticated) {
return false;
}
if (!committed) {
authenticated = false;
username = null;
principals = null;
} else {
logout();
}
if (debug) {
System.out.println("Login aborted for user: " + username);
}
return true;
}
@Override
public boolean logout() throws LoginException {
subject.getPrincipals().removeAll(principals);
authenticated = false;
committed = false;
username = null;
principals = null;
if (debug) {
System.out.println("User logged out");
}
return true;
}
}
// Custom Principal implementations
class UsernamePrincipal implements Principal {
private final String name;
public UsernamePrincipal(String name) {
this.name = name;
}
@Override
public String getName() {
return name;
}
@Override
public boolean equals(Object obj) {
if (this == obj) return true;
if (obj == null || getClass() != obj.getClass()) return false;
UsernamePrincipal that = (UsernamePrincipal) obj;
return Objects.equals(name, that.name);
}
@Override
public int hashCode() {
return Objects.hash(name);
}
@Override
public String toString() {
return "UsernamePrincipal: " + name;
}
}
class RolePrincipal implements Principal {
private final String role;
public RolePrincipal(String role) {
this.role = role;
}
@Override
public String getName() {
return role;
}
@Override
public boolean equals(Object obj) {
if (this == obj) return true;
if (obj == null || getClass() != obj.getClass()) return false;
RolePrincipal that = (RolePrincipal) obj;
return Objects.equals(role, that.role);
}
@Override
public int hashCode() {
return Objects.hash(role);
}
@Override
public String toString() {
return "RolePrincipal: " + role;
}
}
Example 3: Database LoginModule
import javax.security.auth.*;
import javax.security.auth.spi.*;
import javax.security.auth.callback.*;
import javax.security.auth.login.*;
import java.sql.*;
import java.util.*;
import java.security.Principal;
public class DatabaseLoginModule implements LoginModule {
private Subject subject;
private CallbackHandler callbackHandler;
private Map<String, ?> options;
private String username;
private Set<Principal> principals;
private boolean authenticated = false;
private boolean committed = false;
// Database configuration
private String dbUrl;
private String dbUser;
private String dbPassword;
private boolean debug;
@Override
public void initialize(Subject subject, CallbackHandler callbackHandler,
Map<String, ?> sharedState, Map<String, ?> options) {
this.subject = subject;
this.callbackHandler = callbackHandler;
this.options = options;
this.dbUrl = (String) options.get("dbUrl");
this.dbUser = (String) options.get("dbUser");
this.dbPassword = (String) options.get("dbPassword");
this.debug = "true".equals(options.get("debug"));
if (dbUrl == null) {
throw new IllegalArgumentException("Database URL is required");
}
}
@Override
public boolean login() throws LoginException {
if (callbackHandler == null) {
throw new LoginException("No CallbackHandler available");
}
try {
NameCallback nameCallback = new NameCallback("Username: ");
PasswordCallback passwordCallback = new PasswordCallback("Password: ", false);
Callback[] callbacks = new Callback[]{nameCallback, passwordCallback};
callbackHandler.handle(callbacks);
username = nameCallback.getName();
char[] password = passwordCallback.getPassword();
if (username == null || password == null) {
throw new LoginException("Username or password cannot be null");
}
// Authenticate against database
authenticated = authenticateAgainstDatabase(username, new String(password));
if (authenticated) {
principals = new HashSet<>();
principals.add(new UsernamePrincipal(username));
// Get user roles from database
Set<String> roles = getUserRoles(username);
for (String role : roles) {
principals.add(new RolePrincipal(role));
}
if (debug) {
System.out.println("Database authentication successful for user: " + username);
System.out.println("Roles: " + roles);
}
return true;
} else {
throw new FailedLoginException("Invalid database credentials");
}
} catch (Exception e) {
LoginException le = new LoginException("Database authentication failed");
le.initCause(e);
throw le;
}
}
private boolean authenticateAgainstDatabase(String username, String password) {
String sql = "SELECT password FROM users WHERE username = ? AND active = true";
try (Connection conn = DriverManager.getConnection(dbUrl, dbUser, dbPassword);
PreparedStatement stmt = conn.prepareStatement(sql)) {
stmt.setString(1, username);
ResultSet rs = stmt.executeQuery();
if (rs.next()) {
String storedPassword = rs.getString("password");
return storedPassword != null && storedPassword.equals(hashPassword(password));
}
} catch (SQLException e) {
System.err.println("Database error: " + e.getMessage());
}
return false;
}
private Set<String> getUserRoles(String username) {
Set<String> roles = new HashSet<>();
String sql = "SELECT r.role_name FROM user_roles ur " +
"JOIN roles r ON ur.role_id = r.id " +
"JOIN users u ON ur.user_id = u.id " +
"WHERE u.username = ?";
try (Connection conn = DriverManager.getConnection(dbUrl, dbUser, dbPassword);
PreparedStatement stmt = conn.prepareStatement(sql)) {
stmt.setString(1, username);
ResultSet rs = stmt.executeQuery();
while (rs.next()) {
roles.add(rs.getString("role_name"));
}
} catch (SQLException e) {
System.err.println("Error fetching user roles: " + e.getMessage());
}
return roles;
}
private String hashPassword(String password) {
// Simple hash for demonstration - use proper hashing in production
return Integer.toHexString(password.hashCode());
}
@Override
public boolean commit() throws LoginException {
if (!authenticated) {
return false;
}
subject.getPrincipals().addAll(principals);
committed = true;
return true;
}
@Override
public boolean abort() throws LoginException {
if (!authenticated) {
return false;
}
if (!committed) {
authenticated = false;
username = null;
principals = null;
} else {
logout();
}
return true;
}
@Override
public boolean logout() throws LoginException {
subject.getPrincipals().removeAll(principals);
authenticated = false;
committed = false;
username = null;
principals = null;
return true;
}
}
Authorization with JAAS
Example 4: Role-Based Authorization
import javax.security.auth.Subject;
import java.security.Principal;
import java.security.PrivilegedAction;
import java.util.Set;
public class JAASAuthorizationExample {
public static void main(String[] args) {
try {
// Authenticate user
LoginContext loginContext = new LoginContext("DatabaseLogin", new ConsoleCallbackHandler());
loginContext.login();
Subject subject = loginContext.getSubject();
System.out.println("Authenticated subject: " + subject);
// Check authorization
if (hasRole(subject, "ADMIN")) {
performAdminAction(subject);
} else if (hasRole(subject, "USER")) {
performUserAction(subject);
} else {
System.out.println("Insufficient privileges");
}
// Logout
loginContext.logout();
} catch (LoginException e) {
System.err.println("Authentication failed: " + e.getMessage());
}
}
public static boolean hasRole(Subject subject, String role) {
Set<RolePrincipal> roles = subject.getPrincipals(RolePrincipal.class);
return roles.stream().anyMatch(principal -> principal.getName().equals(role));
}
public static void performAdminAction(final Subject subject) {
Subject.doAsPrivileged(subject, new PrivilegedAction<Void>() {
@Override
public Void run() {
// Check permission at runtime
SecurityManager sm = System.getSecurityManager();
if (sm != null) {
sm.checkPermission(new AdminPermission());
}
System.out.println("Performing ADMIN action");
// Admin-specific logic here
return null;
}
}, null);
}
public static void performUserAction(final Subject subject) {
Subject.doAs(subject, new PrivilegedAction<Void>() {
@Override
public Void run() {
SecurityManager sm = System.getSecurityManager();
if (sm != null) {
sm.checkPermission(new UserPermission());
}
System.out.println("Performing USER action");
// User-specific logic here
return null;
}
});
}
}
// Custom Permissions
class AdminPermission extends java.security.BasicPermission {
public AdminPermission() {
super("admin");
}
public AdminPermission(String name, String actions) {
super(name, actions);
}
}
class UserPermission extends java.security.BasicPermission {
public UserPermission() {
super("user");
}
}
// Console-based CallbackHandler
class ConsoleCallbackHandler implements CallbackHandler {
@Override
public void handle(Callback[] callbacks) throws IOException, UnsupportedCallbackException {
Scanner scanner = new Scanner(System.in);
for (Callback callback : callbacks) {
if (callback instanceof NameCallback) {
NameCallback nameCallback = (NameCallback) callback;
System.out.print(nameCallback.getPrompt());
nameCallback.setName(scanner.nextLine());
} else if (callback instanceof PasswordCallback) {
PasswordCallback passwordCallback = (PasswordCallback) callback;
System.out.print(passwordCallback.getPrompt());
passwordCallback.setPassword(scanner.nextLine().toCharArray());
} else {
throw new UnsupportedCallbackException(callback, "Unsupported callback");
}
}
}
}
Example 5: Policy-Based Authorization
import java.io.File;
import java.io.FilePermission;
import java.security.Policy;
import java.security.Principal;
import java.security.ProtectionDomain;
import java.security.CodeSource;
import java.security.Permission;
import java.security.PermissionCollection;
import java.security.AllPermission;
import java.util.*;
public class JAASPolicy extends Policy {
private final Map<String, Set<Permission>> rolePermissions;
public JAASPolicy() {
rolePermissions = new HashMap<>();
initializeRolePermissions();
}
private void initializeRolePermissions() {
// Define permissions for each role
Set<Permission> adminPermissions = new HashSet<>();
adminPermissions.add(new AllPermission());
adminPermissions.add(new FilePermission("<<ALL FILES>>", "read,write,execute,delete"));
rolePermissions.put("ADMIN", adminPermissions);
Set<Permission> userPermissions = new HashSet<>();
userPermissions.add(new FilePermission("/home/user/-", "read,write"));
userPermissions.add(new RuntimePermission("accessDeclaredMembers"));
userPermissions.put("USER", userPermissions);
Set<Permission> guestPermissions = new HashSet<>();
guestPermissions.add(new FilePermission("/home/user/public/-", "read"));
rolePermissions.put("GUEST", guestPermissions);
}
@Override
public PermissionCollection getPermissions(ProtectionDomain domain) {
Permissions permissions = new Permissions();
// Add default permissions
addDefaultPermissions(permissions);
// Add role-based permissions
Principal[] principals = domain.getPrincipals();
if (principals != null) {
for (Principal principal : principals) {
if (principal instanceof RolePrincipal) {
String role = principal.getName();
Set<Permission> rolePerms = rolePermissions.get(role);
if (rolePerms != null) {
for (Permission perm : rolePerms) {
permissions.add(perm);
}
}
}
}
}
return permissions;
}
@Override
public PermissionCollection getPermissions(CodeSource codesource) {
return new Permissions(); // Empty permissions
}
@Override
public boolean implies(ProtectionDomain domain, Permission permission) {
return getPermissions(domain).implies(permission);
}
private void addDefaultPermissions(Permissions permissions) {
// Add basic permissions for all authenticated users
permissions.add(new RuntimePermission("accessDeclaredMembers"));
permissions.add(new PropertyPermission("user.*", "read"));
permissions.add(new FilePermission("/tmp/-", "read,write"));
}
}
// Policy setup
class PolicySetup {
static {
// Set custom policy
Policy.setPolicy(new JAASPolicy());
System.setSecurityManager(new SecurityManager());
}
public static void setup() {
// Static initializer does the setup
}
}
Advanced JAAS Features
Example 6: Multi-Module Authentication Stack
public class MultiModuleAuthentication {
public static void main(String[] args) {
try {
// Configuration with multiple LoginModules
LoginContext loginContext = new LoginContext(
"CompositeLogin",
new InteractiveCallbackHandler()
);
loginContext.login();
Subject subject = loginContext.getSubject();
System.out.println("Authentication successful!");
System.out.println("Subject principals:");
subject.getPrincipals().forEach(System.out::println);
// Execute with subject's permissions
Subject.doAs(subject, (PrivilegedAction<Void>) () -> {
System.out.println("Executing with subject's permissions");
// Perform authorized actions
return null;
});
loginContext.logout();
} catch (LoginException e) {
System.err.println("Authentication failed: " + e.getMessage());
}
}
}
class InteractiveCallbackHandler implements CallbackHandler {
private Scanner scanner = new Scanner(System.in);
@Override
public void handle(Callback[] callbacks) throws IOException, UnsupportedCallbackException {
for (Callback callback : callbacks) {
if (callback instanceof NameCallback) {
NameCallback nameCallback = (NameCallback) callback;
System.out.print(nameCallback.getPrompt());
String name = scanner.nextLine();
nameCallback.setName(name);
} else if (callback instanceof PasswordCallback) {
PasswordCallback passwordCallback = (PasswordCallback) callback;
System.out.print(passwordCallback.getPrompt());
String password = scanner.nextLine();
passwordCallback.setPassword(password.toCharArray());
} else if (callback instanceof TextOutputCallback) {
TextOutputCallback textCallback = (TextOutputCallback) callback;
switch (textCallback.getMessageType()) {
case TextOutputCallback.INFORMATION:
System.out.println("INFO: " + textCallback.getMessage());
break;
case TextOutputCallback.WARNING:
System.out.println("WARNING: " + textCallback.getMessage());
break;
case TextOutputCallback.ERROR:
System.out.println("ERROR: " + textCallback.getMessage());
break;
}
} else if (callback instanceof ChoiceCallback) {
ChoiceCallback choiceCallback = (ChoiceCallback) callback;
System.out.println(choiceCallback.getPrompt());
String[] options = choiceCallback.getOptions();
for (int i = 0; i < options.length; i++) {
System.out.println((i + 1) + ". " + options[i]);
}
System.out.print("Enter choice: ");
int choice = scanner.nextInt();
scanner.nextLine(); // consume newline
choiceCallback.setSelectedIndex(choice - 1);
} else {
throw new UnsupportedCallbackException(callback, "Unsupported callback type");
}
}
}
}
Example 7: JAAS in Web Applications
// Web application authentication filter
public class JAASAuthFilter implements Filter {
private static final String LOGIN_CONFIG = "WebAppLogin";
@Override
public void doFilter(ServletRequest request, ServletResponse response,
FilterChain chain) throws IOException, ServletException {
HttpServletRequest httpRequest = (HttpServletRequest) request;
HttpServletResponse httpResponse = (HttpServletResponse) response;
HttpSession session = httpRequest.getSession();
try {
// Check if user is already authenticated
Subject subject = (Subject) session.getAttribute("javax.security.auth.subject");
if (subject == null) {
// Attempt authentication
subject = authenticateUser(httpRequest);
if (subject != null) {
session.setAttribute("javax.security.auth.subject", subject);
} else {
httpResponse.sendRedirect("/login.jsp");
return;
}
}
// Continue with authenticated user
chain.doFilter(request, response);
} catch (LoginException e) {
httpResponse.sendRedirect("/login.jsp?error=auth_failed");
}
}
private Subject authenticateUser(HttpServletRequest request) throws LoginException {
String username = request.getParameter("username");
String password = request.getParameter("password");
if (username == null || password == null) {
return null;
}
CallbackHandler callbackHandler = new HttpRequestCallbackHandler(username, password);
LoginContext loginContext = new LoginContext(LOGIN_CONFIG, callbackHandler);
loginContext.login();
return loginContext.getSubject();
}
@Override
public void init(FilterConfig filterConfig) throws ServletException {
// Initialization code
}
@Override
public void destroy() {
// Cleanup code
}
}
// CallbackHandler for HTTP requests
class HttpRequestCallbackHandler implements CallbackHandler {
private final String username;
private final String password;
public HttpRequestCallbackHandler(String username, String password) {
this.username = username;
this.password = password;
}
@Override
public void handle(Callback[] callbacks) throws IOException, UnsupportedCallbackException {
for (Callback callback : callbacks) {
if (callback instanceof NameCallback) {
((NameCallback) callback).setName(username);
} else if (callback instanceof PasswordCallback) {
((PasswordCallback) callback).setPassword(password.toCharArray());
} else {
throw new UnsupportedCallbackException(callback, "Unsupported callback type");
}
}
}
}
// Role-based web authorization
public class RoleAuthorizationFilter implements Filter {
@Override
public void doFilter(ServletRequest request, ServletResponse response,
FilterChain chain) throws IOException, ServletException {
HttpServletRequest httpRequest = (HttpServletRequest) request;
HttpServletResponse httpResponse = (HttpServletResponse) response;
HttpSession session = httpRequest.getSession();
Subject subject = (Subject) session.getAttribute("javax.security.auth.subject");
if (subject == null || !hasRequiredRole(subject, httpRequest)) {
httpResponse.sendError(HttpServletResponse.SC_FORBIDDEN, "Insufficient privileges");
return;
}
chain.doFilter(request, response);
}
private boolean hasRequiredRole(Subject subject, HttpServletRequest request) {
String requiredRole = getRequiredRole(request);
if (requiredRole == null) {
return true; // No specific role required
}
return subject.getPrincipals(RolePrincipal.class).stream()
.anyMatch(principal -> principal.getName().equals(requiredRole));
}
private String getRequiredRole(HttpServletRequest request) {
// Extract required role from request URL or configuration
String path = request.getServletPath();
if (path.startsWith("/admin/")) {
return "ADMIN";
} else if (path.startsWith("/user/")) {
return "USER";
} else if (path.startsWith("/api/")) {
return "API_USER";
}
return null;
}
@Override
public void init(FilterConfig filterConfig) throws ServletException {}
@Override
public void destroy() {}
}
JAAS Configuration Management
Example 8: Dynamic Configuration
import javax.security.auth.login.Configuration;
import java.security.Security;
import java.util.*;
public class DynamicJAASConfiguration extends Configuration {
private final Map<String, AppConfigurationEntry[]> configurations;
public DynamicJAASConfiguration() {
this.configurations = new HashMap<>();
}
public void addConfiguration(String name, AppConfigurationEntry[] entries) {
configurations.put(name, entries);
}
public void removeConfiguration(String name) {
configurations.remove(name);
}
@Override
public AppConfigurationEntry[] getAppConfigurationEntry(String name) {
return configurations.get(name);
}
// Method to create configuration from properties
public static DynamicJAASConfiguration fromProperties(Properties props) {
DynamicJAASConfiguration config = new DynamicJAASConfiguration();
// Parse properties and create configurations
Set<String> configNames = new HashSet<>();
for (String key : props.stringPropertyNames()) {
if (key.contains(".")) {
configNames.add(key.substring(0, key.indexOf('.')));
}
}
for (String configName : configNames) {
AppConfigurationEntry[] entries = parseConfigurationEntries(props, configName);
config.addConfiguration(configName, entries);
}
return config;
}
private static AppConfigurationEntry[] parseConfigurationEntries(Properties props, String configName) {
List<AppConfigurationEntry> entries = new ArrayList<>();
int index = 0;
while (true) {
String entryKey = configName + "." + index;
String moduleClass = props.getProperty(entryKey + ".module");
if (moduleClass == null) {
break;
}
String flag = props.getProperty(entryKey + ".flag", "required");
Map<String, String> options = new HashMap<>();
// Parse options
for (String propName : props.stringPropertyNames()) {
if (propName.startsWith(entryKey + ".option.")) {
String optionName = propName.substring(entryKey.length() + 8); // ".option." length
options.put(optionName, props.getProperty(propName));
}
}
AppConfigurationEntry.LoginModuleControlControlFlag controlFlag;
switch (flag.toLowerCase()) {
case "required":
controlFlag = AppConfigurationEntry.LoginModuleControlControlFlag.REQUIRED;
break;
case "requisite":
controlFlag = AppConfigurationEntry.LoginModuleControlControlFlag.REQUISITE;
break;
case "sufficient":
controlFlag = AppConfigurationEntry.LoginModuleControlControlFlag.SUFFICIENT;
break;
case "optional":
controlFlag = AppConfigurationEntry.LoginModuleControlControlFlag.OPTIONAL;
break;
default:
controlFlag = AppConfigurationEntry.LoginModuleControlControlFlag.REQUIRED;
}
entries.add(new AppConfigurationEntry(
moduleClass,
controlFlag,
options
));
index++;
}
return entries.toArray(new AppConfigurationEntry[0]);
}
}
// Usage example
public class DynamicConfigurationExample {
public static void main(String[] args) {
Properties props = new Properties();
props.setProperty("WebLogin.0.module", "com.example.DatabaseLoginModule");
props.setProperty("WebLogin.0.flag", "required");
props.setProperty("WebLogin.0.option.dbUrl", "jdbc:mysql://localhost/auth");
props.setProperty("WebLogin.0.option.debug", "true");
props.setProperty("WebLogin.1.module", "com.example.LDAPLoginModule");
props.setProperty("WebLogin.1.flag", "sufficient");
props.setProperty("WebLogin.1.option.ldapUrl", "ldap://localhost");
DynamicJAASConfiguration config = DynamicJAASConfiguration.fromProperties(props);
Configuration.setConfiguration(config);
// Now use the configuration
try {
LoginContext ctx = new LoginContext("WebLogin", new SimpleCallbackHandler());
ctx.login();
// ... rest of authentication logic
} catch (LoginException e) {
e.printStackTrace();
}
}
}
Best Practices and Security Considerations
- Use strong password hashing in LoginModules
- Implement proper error handling to avoid information leakage
- Use secure communication for remote authentication
- Regularly update and patch custom LoginModules
- Implement proper session management in web applications
- Use principle of least privilege in authorization policies
- Audit and log authentication attempts
- Secure configuration files and sensitive data
- Use HTTPS for web-based authentication
- Implement account lockout mechanisms for brute force protection
JAAS provides a robust framework for implementing authentication and authorization in Java applications. By following these patterns and best practices, you can build secure, flexible, and maintainable security solutions.