I'm working in a legacy environment where an LDAP server is used only for authentication and contains no roles, and authorization is done against a database which contains the user-role mapping, but no passwords.
My plan is to implement a new Tomcat Realm by extending JNDIRealm, and overriding the role methods to call an encapsulated JDBCRealm.
My realm is declared in server.xml:
<Realm className="com.example.LdapJdbcRealm"
connectionURL="ldap://ldaphost:389"
resourceName="LDAP Auth"
userPattern="uid={0}, ou=Portal, dc=example, dc=com"
dbConnectionURL="jdbc:oracle:thin:@oracledb:1521:dbname"
userTable="db_user" userNameCol="user_id"
userRoleTable="db_user_role_xref" roleNameCol="role_id" />
This is a combination of the standard property names for JNDIRealm & JDBCRealm, with a little change as they both use connectionURL.
package com.example;
import org.apache.catalina.Realm;
import org.apache.catalina.Context;
import org.apache.catalina.deploy.SecurityConstraint;
import org.apache.catalina.connector.Request;
import org.apache.catalina.connector.Response;
import org.apache.catalina.realm.JNDIRealm;
import org.apache.catalina.realm.JDBCRealm;
import java.security.Principal;
import java.io.IOException;
public class LdapJdbcRealm extends JNDIRealm implements Realm
{
private JDBCRealm jdbcRealm = new JDBCRealm();
protected static final String info = "com.example.LdapJdbcRealm/1.0";
protected static final String name = "LdapJdbcRealm";
public String getDbConnectionURL() {
return jdbcRealm.getConnectionURL();
}
public void setDbConnectionURL(String dbConnectionURL) {
jdbcRealm.setConnectionURL(dbConnectionURL);
}
public String getUserTable() {
return jdbcRealm.getUserTable();
}
public void setUserTable(String userTable) {
jdbcRealm.setUserTable(userTable);
}
public String getUserNameCol() {
return jdbcRealm.getUserNameCol();
}
public void setUserNameCol(String userNameCol) {
jdbcRealm.setUserNameCol(userNameCol);
}
public String getUserRoleTable() {
return jdbcRealm.getUserRoleTable();
}
public void setUserRoleTable(String userRoleTable) {
jdbcRealm.setUserRoleTable(userRoleTable);
}
public String getRoleNameCol() {
return jdbcRealm.getRoleNameCol();
}
public void setRoleNameCol(String roleNameCol) {
jdbcRealm.setRoleNameCol(roleNameCol);
}
public boolean hasResourcePermission(Request request,
Response response,
SecurityConstraint[]constraints,
Context context) throws IOException
{
return jdbcRealm.hasResourcePermission(request, response, constraints, context);
}
public boolean hasRole(Principal principal, String role) {
return jdbcRealm.hasRole(principal, role);
}
}
This mostly seems to work, the authorization returns a Principal from LDAP, which has no roles as expected. That same Principal enters hasResourcePermission()
and fails because it doesn't have the require roles in it. Clearly I'm missing some crucial code.
I'm looking for solutions. I could try extending JDBCRealm and adding LDAP authentication, but that seems like more work.
I also believe that this LDAP authentication/DB authorization is not an uncommon pattern. Is there an alternative solution already available?
It is not within my control to add roles to LDAP or passwords to the DB, so those are not solutions for me.