This is an automated email from the ASF dual-hosted git repository.
markt-asf pushed a commit to branch 9.0.x
in repository https://gitbox.apache.org/repos/asf/tomcat.git
The following commit(s) were added to refs/heads/9.0.x by this push:
new 8efd51f061 Various improvements to the DataSourceRealm and JDBCRealm
8efd51f061 is described below
commit 8efd51f061c026f6339bfa4fe4ef919a04ef130a
Author: Mark Thomas <[email protected]>
AuthorDate: Fri Jul 31 08:49:10 2026 +0100
Various improvements to the DataSourceRealm and JDBCRealm
A failure to connect to the database or an exception during either user
or role lookup will now result in an authentication failure rather than
a partially populated Principal.
For CLIENT-CERT and SPNEGO authentication, the user must exist in the
database for authentication to succeed.
---
.../org/apache/catalina/realm/DataSourceRealm.java | 66 ++++++++++++++++++----
java/org/apache/catalina/realm/JDBCRealm.java | 65 ++++++++++++++++++---
.../apache/catalina/realm/LocalStrings.properties | 1 +
.../apache/catalina/realm/TestDataSourceRealm.java | 9 ++-
webapps/docs/changelog.xml | 8 +++
5 files changed, 129 insertions(+), 20 deletions(-)
diff --git a/java/org/apache/catalina/realm/DataSourceRealm.java b/java/org/apache/catalina/realm/DataSourceRealm.java
index bfa4572dc0..198260a41e 100644
--- a/java/org/apache/catalina/realm/DataSourceRealm.java
+++ b/java/org/apache/catalina/realm/DataSourceRealm.java
@@ -23,6 +23,7 @@ import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
+import java.util.List;
import javax.naming.Context;
import javax.sql.DataSource;
@@ -317,9 +318,20 @@ public class DataSourceRealm extends RealmBase {
}
// Validate the user's credentials
- boolean validated = getCredentialHandler().matches(credentials, dbCredentials);
+ boolean authenticationSuccess = getCredentialHandler().matches(credentials, dbCredentials);
- if (validated) {
+ ArrayList<String> roles = null;
+
+ if (authenticationSuccess) {
+ // Obtain the roles
+ roles = getRoles(dbConnection, username);
+ if (roles == null) {
+ // Role lookup failed so fail authentication
+ authenticationSuccess = false;
+ }
+ }
+
+ if (authenticationSuccess) {
if (containerLog.isTraceEnabled()) {
containerLog.trace(sm.getString("dataSourceRealm.authenticateSuccess", username));
}
@@ -330,10 +342,8 @@ public class DataSourceRealm extends RealmBase {
return null;
}
- ArrayList<String> list = getRoles(dbConnection, username);
-
// Create and return a suitable Principal for this user
- return new GenericPrincipal(username, credentials, list);
+ return new GenericPrincipal(username, credentials, roles);
}
@@ -447,27 +457,59 @@ public class DataSourceRealm extends RealmBase {
}
+ /**
+ * Confirms if the given user exists in the database.
+ *
+ * @param dbConnection The database connection to be used
+ * @param username Username to check
+ *
+ * @return {@code true} if the user exists, otherwise {@code false}
+ */
+ protected boolean validateUser(Connection dbConnection, String username) {
+ // Use the credentials lookup as a proxy for whether the user exists
+ try (PreparedStatement stmt = dbConnection.prepareStatement(preparedCredentials)) {
+ stmt.setString(1, username);
+
+ try (ResultSet rs = stmt.executeQuery()) {
+ if (rs.next()) {
+ return true;
+ }
+ }
+ } catch (SQLException e) {
+ containerLog.error(sm.getString("dataSourceRealm.validateUser.exception", username), e);
+ }
+
+ return false;
+ }
+
+
@Override
protected Principal getPrincipal(String username) {
Connection dbConnection = open();
if (dbConnection == null) {
- return new GenericPrincipal(username, null, null);
+ return null;
}
try {
- return new GenericPrincipal(username, getPassword(dbConnection, username),
- getRoles(dbConnection, username));
+ if (!validateUser(dbConnection, username)) {
+ return null;
+ }
+ List<String> roles = getRoles(dbConnection, username);
+ if (roles == null) {
+ return null;
+ }
+ return new GenericPrincipal(username, getPassword(dbConnection, username), roles);
} finally {
close(dbConnection);
}
-
}
+
/**
* Return the roles associated with the given username.
*
* @param username Username for which roles should be retrieved
*
- * @return an array list of the role names
+ * @return an array list of the role names or {@code null} if the lookup fails
*/
protected ArrayList<String> getRoles(String username) {
@@ -491,14 +533,14 @@ public class DataSourceRealm extends RealmBase {
* @param dbConnection The database connection to be used
* @param username Username for which roles should be retrieved
*
- * @return an array list of the role names
+ * @return an array list of the role names or {@code null} if the lookup fails
*/
protected ArrayList<String> getRoles(Connection dbConnection, String username) {
if (allRolesMode != AllRolesMode.STRICT_MODE && !isRoleStoreDefined()) {
// Using an authentication only configuration and no role store has
// been defined so don't spend cycles looking
- return null;
+ return new ArrayList<>(0);
}
try (PreparedStatement stmt = dbConnection.prepareStatement(preparedRoles)) {
diff --git a/java/org/apache/catalina/realm/JDBCRealm.java b/java/org/apache/catalina/realm/JDBCRealm.java
index df75fd40c8..ffb5a2bae8 100644
--- a/java/org/apache/catalina/realm/JDBCRealm.java
+++ b/java/org/apache/catalina/realm/JDBCRealm.java
@@ -24,6 +24,7 @@ import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
+import java.util.List;
import java.util.Properties;
import org.apache.catalina.LifecycleException;
@@ -373,9 +374,20 @@ public class JDBCRealm extends RealmBase {
}
// Validate the user's credentials
- boolean validated = getCredentialHandler().matches(credentials, dbCredentials);
+ boolean authenticationSuccess = getCredentialHandler().matches(credentials, dbCredentials);
- if (validated) {
+ ArrayList<String> roles = null;
+
+ if (authenticationSuccess) {
+ // Obtain the roles
+ roles = getRoles(username);
+ if (roles == null) {
+ // Role lookup failed so fail authentication
+ authenticationSuccess = false;
+ }
+ }
+
+ if (authenticationSuccess) {
if (containerLog.isTraceEnabled()) {
containerLog.trace(sm.getString("jdbcRealm.authenticateSuccess", username));
}
@@ -386,8 +398,6 @@ public class JDBCRealm extends RealmBase {
return null;
}
- ArrayList<String> roles = getRoles(username);
-
// Create and return a suitable Principal for this user
return new GenericPrincipal(username, credentials, roles);
}
@@ -536,6 +546,39 @@ public class JDBCRealm extends RealmBase {
return null;
}
+
+ /**
+ * Confirms if the given user exists in the database.
+ *
+ * @param username Username to check
+ *
+ * @return {@code true} if the user exists, otherwise {@code false}
+ */
+ protected boolean validateUser(String username) {
+ // Use the credentials lookup as a proxy for whether the user exists
+ try {
+ // Ensure that we have an open database connection
+ open();
+
+ PreparedStatement stmt = credentials(dbConnection, username);
+ try (ResultSet rs = stmt.executeQuery()) {
+ if (rs.next()) {
+ return true;
+ }
+ }
+ } catch (SQLException e) {
+ containerLog.error(sm.getString("jdbcRealm.exception"), e);
+ }
+
+ // Close the connection so that it gets reopened next time
+ if (dbConnection != null) {
+ close(dbConnection);
+ }
+
+ return false;
+ }
+
+
/**
* Get the principal associated with the specified user.
*
@@ -545,9 +588,17 @@ public class JDBCRealm extends RealmBase {
*/
@Override
protected synchronized Principal getPrincipal(String username) {
+ if (!validateUser(username)) {
+ return null;
+ }
+ List<String> roles = getRoles(username);
+ if (roles == null) {
+ return null;
+ }
- return new GenericPrincipal(username, getPassword(username), getRoles(username));
+ String password = getPassword(username);
+ return new GenericPrincipal(username, password, roles);
}
@@ -556,14 +607,14 @@ public class JDBCRealm extends RealmBase {
*
* @param username The user name
*
- * @return an array list of the role names
+ * @return an array list of the role names or {@code null} if the lookup fails
*/
protected ArrayList<String> getRoles(String username) {
if (allRolesMode != AllRolesMode.STRICT_MODE && !isRoleStoreDefined()) {
// Using an authentication only configuration and no role store has
// been defined so don't spend cycles looking
- return null;
+ return new ArrayList<>(0);
}
// Number of tries is the number of attempts to connect to the database
diff --git a/java/org/apache/catalina/realm/LocalStrings.properties b/java/org/apache/catalina/realm/LocalStrings.properties
index 38d68a1003..df97683981 100644
--- a/java/org/apache/catalina/realm/LocalStrings.properties
+++ b/java/org/apache/catalina/realm/LocalStrings.properties
@@ -40,6 +40,7 @@ dataSourceRealm.noUserCredCol=No column was specified for user credentials
dataSourceRealm.noUserNameCol=No column was specified for user names
dataSourceRealm.noUserTable=No user table was specified
dataSourceRealm.roleConfigMismatch=Role configuration is incomplete: both userRoleTable and roleNameCol must be specified together
+dataSourceRealm.validateUser.exception=Exception validating that user [{0}] exists
jaasCallback.username=Returned username [{0}]
diff --git a/test/org/apache/catalina/realm/TestDataSourceRealm.java b/test/org/apache/catalina/realm/TestDataSourceRealm.java
index 6c09624e1f..cd1471a799 100644
--- a/test/org/apache/catalina/realm/TestDataSourceRealm.java
+++ b/test/org/apache/catalina/realm/TestDataSourceRealm.java
@@ -152,7 +152,14 @@ public class TestDataSourceRealm extends LoggingBaseTest {
List<String> roles = db.getRoles("tomcat");
Assert.assertEquals(2, roles.size());
- db.stop();
+ p = db.getPrincipal("blabla");
+ Assert.assertNull(p);
+
+ p = db.getPrincipal("random");
+ Assert.assertTrue(p instanceof GenericPrincipal);
+ gp = (GenericPrincipal) p;
+ Assert.assertEquals(0, gp.getRoles().length);
+ db.stop();
}
}
diff --git a/webapps/docs/changelog.xml b/webapps/docs/changelog.xml
index b471d6b599..7b24439277 100644
--- a/webapps/docs/changelog.xml
+++ b/webapps/docs/changelog.xml
@@ -202,6 +202,14 @@
<code>doAuthenticate()</code> and/or <code>restoreRequest()</code> will
require modification. (markt)
</fix>
+ <fix>
+ Various improvements to the <code>DataSourceRealm</code>. A failure to
+ connect to the database or an exception during either user or role
+ lookup will now result in an authentication failure rather than a
+ partially populated Principal. For <code>CLIENT-CERT</code> and
+ <code>SPNEGO</code> authentication, the user must exist in the database
+ for authentication to succeed. (markt)
+ </fix>
</changelog>
</subsection>
<subsection name="Coyote">
lmpx.com only provides a reader for public news (NNTP) servers. It is not
affiliated with the servers or forums shown here and is not responsible for
the content of articles, which is written by their respective authors.