I've managed to write a LoginService for embedded Jetty that seems to look into LDAP without annoying "jvm patameters that you have to use as it is a jvm requirement" (or what did they mean when they closed an issue here https://github.com/eclipse/jetty.project/issues/1349)
Please note that I wasn't always understanding what I was doing due to lack of experience with "naming" and mostly LDAP, so feel free to improve this code.
This is an anonymous class that I am creating inline right before putting it into ConstraintSecurityHandler. Groups are treated as roles.
LoginService loginService = new AbstractLoginService() {
private final InitialLdapContext _ldap = _getLdap(
"cn=" + CONFIG.getString("ldap.manager") + "," + CONFIG.getString("ldap.baseDn"),
CONFIG.getString("ldap.managerPassword"));
@Override
protected void finalize() throws Throwable {
_ldap.close();
}
private InitialLdapContext _getLdap(String userDn, String password) {
Hashtable env = new Hashtable();
env.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.ldap.LdapCtxFactory");
env.put(Context.SECURITY_AUTHENTICATION, "simple");
env.put(Context.PROVIDER_URL, CONFIG.getString("ldap.server"));
env.put(Context.SECURITY_PRINCIPAL, userDn);
env.put(Context.SECURITY_CREDENTIALS, password);//dn user password
try {
InitialLdapContext ldap = new InitialLdapContext(env, null);
return ldap;
} catch (AuthenticationException e) {
return null;
} catch (NamingException e) {
return null;
}
}
// Based on https://www.owasp.org/index.php/Preventing_LDAP_Injection_in_Java
private String _escapeLDAPSearchFilter(String filter) {
StringBuilder sb = new StringBuilder();
for (int i = 0; i < filter.length(); i++) {
char curChar = filter.charAt(i);
switch (curChar) {
case '\\':
sb.append("\\5c");
break;
case '*':
sb.append("\\2a");
break;
case '(':
sb.append("\\28");
break;
case ')':
sb.append("\\29");
break;
case '\u0000':
sb.append("\\00");
break;
default:
sb.append(curChar);
}
}
return sb.toString();
}
@Override
protected String[] loadRoleInfo(AbstractLoginService.UserPrincipal user) {
String groupBaseDn = CONFIG.getString("ldap.groupBaseDn") + "," + CONFIG.getString("ldap.baseDn");
String search = CONFIG.getString("ldap.groupFilter");
String userDn;
if (CONFIG.getBoolean("ldap.usePosixGroups", true)) {
userDn = user.getName();
} else {
userDn = "uid=" + user.getName() + "," + CONFIG.getString("ldap.userBaseDn") + "," + CONFIG.getString("ldap.baseDn"); // TODO: not sure in this, never tested
}
search = search + "(" + CONFIG.getString("ldap.groupMemberAttribute") + "=" + _escapeLDAPSearchFilter(userDn) + ")";
search = "(&" + search + ")";
SearchControls searchControls = new SearchControls();
searchControls.setSearchScope(SearchControls.SUBTREE_SCOPE);
searchControls.setTimeLimit(30000);
NamingEnumeration<SearchResult> enumeration = null;
ArrayList<String> roles = new ArrayList<>();
try {
enumeration = _ldap.search(groupBaseDn, search, searchControls);
while(enumeration.hasMore()){
SearchResult result = enumeration.nextElement();
final Attributes attributes = result.getAttributes();
Attribute attribute = attributes.get(CONFIG.getString("ldap.groupIdAttribute"));
if (attribute != null) {
roles.add((String) attribute.get());
}
}
} catch (NamingException e) {
} finally {
if (enumeration != null) {
try {
enumeration.close();
} catch (NamingException ee) {
}
}
}
String[] ret = new String[roles.size()];
return roles.toArray(ret);
}
@Override
protected AbstractLoginService.UserPrincipal loadUserInfo(String username) {
final Credential credential = new Credential() {
@Override
public boolean check(Object credentials) {
InitialLdapContext myLdap = _getLdap(
"uid=" + username + "," + CONFIG.getString("ldap.userBaseDn") + "," + CONFIG.getString("ldap.baseDn"),
(String) credentials);
if (myLdap == null) {
return false;
} else {
try {
myLdap.close();
} catch (NamingException e) {
//okay...
}
return true;
}
}
};
final AbstractLoginService.UserPrincipal webUser = new UserPrincipal(username, credential);
return webUser;
}
};
CONFIG is my config file that looks like following:
ldap.server=ldap://192.168.100.200
ldap.manager=admin
ldap.managerPassword=ldapadmin
ldap.baseDn=dc=example,dc=com
ldap.userBaseDn=ou=People
ldap.groupBaseDn=ou=Groups
ldap.groupMemberAttribute=memberUid
ldap.usePosixGroups=true
ldap.userFilter=(objectClass=inetOrgPerson)
ldap.groupFilter=(objectClass=posixGroup)
ldap.groupIdAttribute=cn
Ldap was configured on ldap://192.168.100.200 with the following settings (i think, it was long ago)
dn: ou=People,dc=example,dc=com
objectClass: organizationalUnit
ou: People
dn: ou=Groups,dc=example,dc=com
objectClass: organizationalUnit
ou: Groups
dn: uid=testuser01,ou=People,dc=example,dc=com
objectClass: inetOrgPerson
objectClass: posixAccount
objectClass: shadowAccount
uid: testuser01
sn: User01
givenName: Test01
cn: testuser01
displayName: Test User 01
uidNumber: 10001
gidNumber: 10001
userPassword: 12345qw
homeDirectory: /home/testuser01
dn: uid=testuser02,ou=People,dc=example,dc=com
objectClass: inetOrgPerson
objectClass: posixAccount
objectClass: shadowAccount
uid: testuser02
sn: User02
givenName: Test02
cn: testuser02
displayName: Test User 02
uidNumber: 10002
gidNumber: 10002
userPassword: 12345qw
homeDirectory: /home/testuser02
dn: uid=testuser03,ou=People,dc=example,dc=com
objectClass: inetOrgPerson
objectClass: posixAccount
objectClass: shadowAccount
uid: testuser03
sn: User03
givenName: Test03
cn: testuser03
displayName: Test User 03
uidNumber: 10003
gidNumber: 10003
userPassword: 12345qw
homeDirectory: /home/testuser03
dn: cn=admins,ou=Groups,dc=example,dc=com
objectClass: posixGroup
cn: admins
gidNumber: 5000
memberUid: testuser01
dn: cn=staff,ou=Groups,dc=example,dc=com
objectClass: posixGroup
cn: staff
gidNumber: 5001
memberUid: testuser01
memberUid: testuser02
memberUid: testuser03
dn: cn=management,ou=Groups,dc=example,dc=com
objectClass: posixGroup
cn: management
gidNumber: 5003
memberUid: testuser02
Would be nice if there was a two-three method wrapper lib anywhere that does the same but tested in different environments, so that I woudn't have to write this.