JNDI support for Eclipse Jetty web server including dependency injection, lifecycle management, JNDI resource binding, and database-backed security services
—
Database-backed authentication and authorization using JNDI DataSources with configurable user/role schema and full Jetty security integration for enterprise applications.
Comprehensive database-backed login service that obtains user credentials and role information via JNDI DataSource with configurable table schema and SQL generation.
class DataSourceLoginService extends AbstractLoginService {
// Constructors
DataSourceLoginService();
DataSourceLoginService(String name);
DataSourceLoginService(String name, IdentityService identityService);
// JNDI DataSource configuration
void setJndiName(String jndi);
String getJndiName();
// Server integration
void setServer(Server server);
Server getServer();
// Database schema management
void setCreateTables(boolean createTables);
boolean getCreateTables();
// User table configuration
void setUserTableName(String name);
String getUserTableName();
void setUserTableKey(String tableKey);
String getUserTableKey();
void setUserTableUserField(String tableUserField);
String getUserTableUserField();
void setUserTablePasswordField(String tablePasswordField);
String getUserTablePasswordField();
// Role table configuration
void setRoleTableName(String tableName);
String getRoleTableName();
void setRoleTableKey(String tableKey);
String getRoleTableKey();
void setRoleTableRoleField(String tableRoleField);
String getRoleTableRoleField();
// User-role junction table configuration
void setUserRoleTableName(String roleTableName);
String getUserRoleTableName();
void setUserRoleTableUserKey(String roleTableUserKey);
String getUserRoleTableUserKey();
void setUserRoleTableRoleKey(String roleTableRoleKey);
String getUserRoleTableRoleKey();
// Core authentication methods
UserPrincipal loadUserInfo(String username);
List<RolePrincipal> loadRoleInfo(UserPrincipal user);
// Database initialization
void initDb() throws NamingException, SQLException;
}Enhanced user principal that includes database key information for efficient role lookups and database operations.
static class DBUserPrincipal extends UserPrincipal {
// Get database primary key for this user
int getKey();
}// Create and configure basic database login service
DataSourceLoginService loginService = new DataSourceLoginService("MyRealm");
// Configure JNDI DataSource
loginService.setJndiName("jdbc/SecurityDB");
// Use default table schema (users, roles, user_roles tables)
loginService.setCreateTables(false); // Don't auto-create tables
// Add to server
Server server = new Server();
server.addBean(loginService);// Configure custom database schema
DataSourceLoginService loginService = new DataSourceLoginService("CustomRealm");
loginService.setJndiName("jdbc/AuthDB");
// Custom user table
loginService.setUserTableName("app_users");
loginService.setUserTableKey("user_id");
loginService.setUserTableUserField("username");
loginService.setUserTablePasswordField("password_hash");
// Custom role table
loginService.setRoleTableName("app_roles");
loginService.setRoleTableKey("role_id");
loginService.setRoleTableRoleField("role_name");
// Custom user-role junction table
loginService.setUserRoleTableName("user_role_assignments");
loginService.setUserRoleTableUserKey("user_id");
loginService.setUserRoleTableRoleKey("role_id");
// Enable automatic table creation (for development)
loginService.setCreateTables(true);// Set up complete database security for Jetty server
Server server = new Server(8080);
// 1. Create and configure login service
DataSourceLoginService loginService = new DataSourceLoginService("SecureRealm");
loginService.setJndiName("jdbc/SecurityDB");
loginService.setServer(server);
// Custom schema configuration
loginService.setUserTableName("users");
loginService.setUserTableUserField("username");
loginService.setUserTablePasswordField("password");
loginService.setRoleTableName("roles");
loginService.setRoleTableRoleField("rolename");
loginService.setUserRoleTableName("user_roles");
server.addBean(loginService);
// 2. Create security handler
SecurityHandler security = new SecurityHandler();
security.setLoginService(loginService);
// 3. Configure authentication method
FormAuthenticator authenticator = new FormAuthenticator();
authenticator.setLoginPage("/login.jsp");
authenticator.setErrorPage("/login-error.jsp");
security.setAuthenticator(authenticator);
// 4. Define security constraints
Constraint constraint = new Constraint();
constraint.setName("auth");
constraint.setAuthenticate(true);
constraint.setRoles(new String[] {"user", "admin"});
ConstraintMapping mapping = new ConstraintMapping();
mapping.setPathSpec("/secure/*");
mapping.setConstraint(constraint);
security.setConstraintMappings(Arrays.asList(mapping));
// 5. Create webapp with security
WebAppContext webapp = new WebAppContext();
webapp.setContextPath("/");
webapp.setWar("myapp.war");
webapp.setSecurityHandler(security);
server.setHandler(webapp);The DataSourceLoginService expects the following default table structure:
-- Users table
CREATE TABLE users (
id INTEGER PRIMARY KEY,
username VARCHAR(100) NOT NULL UNIQUE,
password VARCHAR(100) NOT NULL
);
-- Roles table
CREATE TABLE roles (
id INTEGER PRIMARY KEY,
rolename VARCHAR(100) NOT NULL UNIQUE
);
-- User-Role junction table
CREATE TABLE user_roles (
user_id INTEGER NOT NULL,
role_id INTEGER NOT NULL,
PRIMARY KEY (user_id, role_id),
FOREIGN KEY (user_id) REFERENCES users(id),
FOREIGN KEY (role_id) REFERENCES roles(id)
);-- Custom table names and fields
CREATE TABLE app_users (
user_id INTEGER PRIMARY KEY,
username VARCHAR(50) NOT NULL UNIQUE,
password_hash VARCHAR(255) NOT NULL,
email VARCHAR(100),
created_date TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
CREATE TABLE app_roles (
role_id INTEGER PRIMARY KEY,
role_name VARCHAR(50) NOT NULL UNIQUE,
description VARCHAR(200)
);
CREATE TABLE user_role_assignments (
user_id INTEGER NOT NULL,
role_id INTEGER NOT NULL,
assigned_date TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (user_id, role_id),
FOREIGN KEY (user_id) REFERENCES app_users(user_id),
FOREIGN KEY (role_id) REFERENCES app_roles(role_id)
);// Set up JNDI DataSource for security database
DataSource securityDataSource = createSecurityDataSource();
Resource securityDB = new Resource("jdbc/SecurityDB", securityDataSource);
// Alternative: scoped to specific webapp
Resource webappSecurityDB = new Resource(webapp, "jdbc/SecurityDB", securityDataSource);// Example of programmatic user/role management
// (Note: This requires direct database access, not through DataSourceLoginService)
DataSource ds = (DataSource) new InitialContext().lookup("jdbc/SecurityDB");
try (Connection conn = ds.getConnection()) {
// Create user
PreparedStatement createUser = conn.prepareStatement(
"INSERT INTO users (username, password) VALUES (?, ?)");
createUser.setString(1, "newuser");
createUser.setString(2, hashedPassword);
createUser.executeUpdate();
// Get user ID
PreparedStatement getUser = conn.prepareStatement(
"SELECT id FROM users WHERE username = ?");
getUser.setString(1, "newuser");
ResultSet rs = getUser.executeQuery();
int userId = rs.next() ? rs.getInt(1) : -1;
// Assign role
PreparedStatement assignRole = conn.prepareStatement(
"INSERT INTO user_roles (user_id, role_id) " +
"SELECT ?, id FROM roles WHERE rolename = ?");
assignRole.setInt(1, userId);
assignRole.setString(2, "user");
assignRole.executeUpdate();
}// Test authentication programmatically
DataSourceLoginService loginService = new DataSourceLoginService("TestRealm");
loginService.setJndiName("jdbc/SecurityDB");
// Initialize the service
loginService.start();
// Test user authentication
UserPrincipal user = loginService.login("testuser", "password");
if (user != null) {
System.out.println("Authentication successful for: " + user.getName());
// Check roles
if (loginService.getUserPrincipal("testuser") instanceof DataSourceLoginService.DBUserPrincipal) {
DataSourceLoginService.DBUserPrincipal dbUser =
(DataSourceLoginService.DBUserPrincipal) user;
System.out.println("User database key: " + dbUser.getKey());
}
// Get user roles
List<RolePrincipal> roles = loginService.loadRoleInfo(user);
for (RolePrincipal role : roles) {
System.out.println("User has role: " + role.getName());
}
} else {
System.out.println("Authentication failed");
}// Complete web security configuration
WebAppContext webapp = new WebAppContext();
// Set up database login service
DataSourceLoginService loginService = new DataSourceLoginService("WebRealm");
loginService.setJndiName("jdbc/SecurityDB");
// Configure form authentication
SecurityHandler security = new SecurityHandler();
security.setLoginService(loginService);
FormAuthenticator authenticator = new FormAuthenticator("/login", "/login?error=1");
security.setAuthenticator(authenticator);
// Define role-based constraints
Constraint adminConstraint = new Constraint();
adminConstraint.setName("admin");
adminConstraint.setAuthenticate(true);
adminConstraint.setRoles(new String[] {"admin"});
ConstraintMapping adminMapping = new ConstraintMapping();
adminMapping.setPathSpec("/admin/*");
adminMapping.setConstraint(adminConstraint);
Constraint userConstraint = new Constraint();
userConstraint.setName("user");
userConstraint.setAuthenticate(true);
userConstraint.setRoles(new String[] {"user", "admin"});
ConstraintMapping userMapping = new ConstraintMapping();
userMapping.setPathSpec("/user/*");
userMapping.setConstraint(userConstraint);
security.setConstraintMappings(Arrays.asList(adminMapping, userMapping));
webapp.setSecurityHandler(security);DataSourceLoginService loginService = new DataSourceLoginService("MyRealm");
loginService.setJndiName("jdbc/SecurityDB");
try {
loginService.initDb();
System.out.println("Database connection successful");
} catch (NamingException e) {
System.err.println("JNDI lookup failed: " + e.getMessage());
// Handle missing DataSource
} catch (SQLException e) {
System.err.println("Database error: " + e.getMessage());
// Handle database connectivity issues
}// Custom login service with logging
DataSourceLoginService loginService = new DataSourceLoginService("MonitoredRealm") {
@Override
public UserPrincipal loadUserInfo(String username) {
long start = System.currentTimeMillis();
try {
UserPrincipal user = super.loadUserInfo(username);
long duration = System.currentTimeMillis() - start;
if (user != null) {
LOG.info("User '{}' authentication successful in {}ms", username, duration);
} else {
LOG.warn("User '{}' authentication failed in {}ms", username, duration);
}
return user;
} catch (Exception e) {
long duration = System.currentTimeMillis() - start;
LOG.error("User '{}' authentication error in {}ms: {}", username, duration, e.getMessage());
throw e;
}
}
};// Use connection pooling for better performance
HikariDataSource hikariDS = new HikariDataSource();
hikariDS.setJdbcUrl("jdbc:postgresql://localhost:5432/security");
hikariDS.setUsername("security_user");
hikariDS.setPassword("password");
hikariDS.setMaximumPoolSize(10);
hikariDS.setMinimumIdle(2);
hikariDS.setConnectionTimeout(30000);
Resource securityDB = new Resource("jdbc/SecurityDB", hikariDS);
// Configure login service
DataSourceLoginService loginService = new DataSourceLoginService("PooledRealm");
loginService.setJndiName("jdbc/SecurityDB");// For high-traffic applications, consider implementing caching
// Note: This would be custom implementation extending DataSourceLoginService
public class CachedDataSourceLoginService extends DataSourceLoginService {
private final Map<String, UserPrincipal> userCache = new ConcurrentHashMap<>();
private final Map<String, List<RolePrincipal>> roleCache = new ConcurrentHashMap<>();
@Override
public UserPrincipal loadUserInfo(String username) {
// Check cache first
UserPrincipal cached = userCache.get(username);
if (cached != null && isCacheValid(cached)) {
return cached;
}
// Load from database
UserPrincipal user = super.loadUserInfo(username);
if (user != null) {
userCache.put(username, user);
}
return user;
}
// Implement cache invalidation, TTL, etc.
}Install with Tessl CLI
npx tessl i tessl/maven-org-eclipse-jetty--jetty-plus