LDAPRealmpublic final class LDAPRealm extends com.sun.enterprise.security.auth.realm.IASRealm Realm wrapper for supporting LDAP authentication.
See LDAPLoginModule documentation for more details on the operation
of the LDAP realm and login module.
The ldap realm needs the following properties in its configuration:
- directory - URL of LDAP directory to use
- base-dn - The base DN to use for user searches.
- jaas-ctx - JAAS context name used to access LoginModule for
authentication.
Besides JDK Context properties start with java.naming, javax.security,
one can also set connection pool related properties starting with
com.sun.jndi.ldap.connect.pool.
See http://java.sun.com/products/jndi/tutorial/ldap/connect/config.html
for details.
Also, the following optional attributes can also be specified:
- search-filter - LDAP filter to use for searching for the user
entry based on username given to iAS. The default value is
uid=%s where %s is expanded to the username.
- group-base-dn - The base DN to use for group searches. By default
its value is the same as base-dn.
- group-search-filter - The LDAP filter to use for searching group
membership of a given user. The default value is
uniquemember=%d where %d is expanded to the DN of the
user found by the user search.
- group-target - The attribute which value(s) are interpreted as
group membership names of the user. Default value is
cn .
- search-bind-dn - The dn of ldap user. optional and no default value.
- search-bind-password - The password of search-bind-dn.optional and
no default value.
- pool-size - The JNDI ldap connection pool size.
|
Fields Summary |
---|
public static final String | AUTH_TYPE | public static final String | PARAM_DIRURL | public static final String | PARAM_USERDN | public static final String | PARAM_SEARCH_FILTER | public static final String | PARAM_GRPDN | public static final String | PARAM_GRP_SEARCH_FILTER | public static final String | PARAM_GRP_TARGET | public static final String | PARAM_MODE | public static final String | PARAM_JNDICF | public static final String | PARAM_POOLSIZE | public static final String | PARAM_BINDDN | public static final String | PARAM_BINDPWD | public static final String | MODE_FIND_BIND | public static final String | SUBST_SUBJECT_NAME | public static final String | SUBST_SUBJECT_DN | private static final String | SEARCH_FILTER_DEFAULT | private static final String | GRP_SEARCH_FILTER_DEFAULT | private static final String | GRP_TARGET_DEFAULT | private static final String | MODE_DEFAULT | private static final String | JNDICF_DEFAULT | private static final int | POOLSIZE_DEFAULT | private final String[] | _dnOnly | private static final String | SUN_JNDI_POOL | private static final String | SUN_JNDI_POOL_ | private static final String | SUN_JNDI_POOL_PROTOCOL | private static final String | SUN_JNDI_POOL_MAXSIZE | private static final String | DYNAMIC_GROUP_OBJECT_FACTORY | public static final String | DYNAMIC_GROUP_FACTORY_OBJECT_PROPERTY | private static final String | DYNAMIC_GROUP_STATE_FACTORY | public static final String | DYNAMIC_GROUP_STATE_FACTORY_PROPERTY | public static final String | LDAP_SOCKET_FACTORY | public static final String | DEFAULT_SSL_LDAP_SOCKET_FACTORY | public static final String | LDAPS_URL | public static final String | DEFAULT_POOL_PROTOCOL | public static final String | DYNAMIC_GROUP_FILTER | public static final String | SSL | private HashMap | groupCache | private Vector | emptyVector | private Properties | ldapBindProps |
Methods Summary |
---|
private boolean | bindAsUser(java.lang.String bindDN, java.lang.String password)Attempt to bind as a specific DN.
boolean bindSuccessful=false;
Properties p = getLdapBindProps();
p.put(Context.SECURITY_PRINCIPAL, bindDN);
p.put(Context.SECURITY_CREDENTIALS, password);
DirContext ctx = null;
try {
ctx = new InitialDirContext(p);
bindSuccessful = true;
} catch (Exception e) {
if (_logger.isLoggable(Level.FINEST)) {
_logger.finest("Error binding to directory as: " + bindDN);
_logger.finest("Exception from JNDI: " + e.toString());
}
} finally {
if (ctx != null) {
try {
ctx.close();
} catch (NamingException e) {};
}
}
return bindSuccessful;
| private java.util.List | dynamicGroupSearch(javax.naming.directory.DirContext ctx, java.lang.String baseDN, java.lang.String target, java.lang.String userDN)Search for group membership using the given connection.
List groupList = new ArrayList();
String filter = DYNAMIC_GROUP_FILTER;
String[] targets = new String[] { target, "memberUrl" };
try {
SearchControls ctls = new SearchControls();
ctls.setReturningAttributes(targets);
ctls.setSearchScope(SearchControls.SUBTREE_SCOPE);
ctls.setReturningObjFlag(true);
NamingEnumeration e = ctx.search(baseDN, filter, ctls);
while(e.hasMore()) {
SearchResult res = (SearchResult)e.next();
Object searchedObject = res.getObject();
if (searchedObject instanceof GroupOfURLs){ // dynamic group
GroupOfURLs gurls = (GroupOfURLs) searchedObject;
Principal x500principal = new X500Principal(userDN);
if (gurls.isMember(x500principal)) {
Attribute grpAttr = res.getAttributes().get(target);
int sz = grpAttr.size();
for (int i=0; i<sz; i++) {
String s = (String)grpAttr.get(i);
groupList.add(s);
}
}
}
// recommended by Jaya Hangal from JDK team
if (searchedObject instanceof Context) {
((Context)searchedObject).close();
}
}
} catch (Exception e) {
_logger.log(Level.WARNING, "ldaplm.searcherror", filter);
_logger.log(Level.WARNING, "security.exception", e);
}
return groupList;
| public java.lang.String[] | findAndBind(java.lang.String _username, java.lang.String _password)Supports mode=find-bind. See class documentation.
// do search for user, substituting %s for username
StringBuffer sb = new StringBuffer(getProperty(PARAM_SEARCH_FILTER));
substitute(sb, SUBST_SUBJECT_NAME, _username);
String userid = sb.toString();
// attempt to bind as the user
DirContext ctx = null;
String srcFilter = null;
String[] grpList = null;
try {
ctx = new InitialDirContext(getLdapBindProps());
String realUserDN = userSearch(ctx, getProperty(PARAM_USERDN), userid);
if (realUserDN == null) {
String msg = sm.getString("ldaprealm.usernotfound", _username);
throw new LoginException(msg);
}
boolean bindSuccessful = bindAsUser(realUserDN, _password);
if (bindSuccessful == false) {
String msg = sm.getString("ldaprealm.bindfailed", realUserDN);
throw new LoginException(msg);
}
// search groups using above connection, substituting %d (and %s)
sb = new StringBuffer(getProperty(PARAM_GRP_SEARCH_FILTER));
substitute(sb, SUBST_SUBJECT_NAME, _username);
substitute(sb, SUBST_SUBJECT_DN, realUserDN);
srcFilter = sb.toString();
ArrayList groupsList = new ArrayList();
groupsList.addAll(groupSearch(ctx, getProperty(PARAM_GRPDN), srcFilter, getProperty(PARAM_GRP_TARGET)));
// search filter is constructed internally as
// as a groupofURLS
groupsList.addAll(dynamicGroupSearch(ctx, getProperty(PARAM_GRPDN), getProperty(PARAM_GRP_TARGET),
realUserDN));
grpList = new String[groupsList.size()];
groupsList.toArray(grpList);
} catch (Exception e) {
throw new LoginException(e.toString() );
} finally {
if (ctx != null) {
try {
ctx.close();
} catch (NamingException e) {};
}
}
if (_logger.isLoggable(Level.FINE)) {
_logger.log(Level.FINE, "LDAP:Group search filter: " + srcFilter);
StringBuffer gb = new StringBuffer();
gb.append("Group memberships found: ");
if (grpList != null) {
for (int i=0; i<grpList.length; i++) {
gb.append(" "+grpList[i]);
}
} else {
gb.append("(null)");
}
if (_logger.isLoggable(Level.FINE)) {
_logger.log(Level.FINE, "LDAP: "+ gb.toString());
}
}
grpList = addAssignGroups(grpList);
setGroupNames(_username, grpList);
if(_logger.isLoggable(Level.FINE)){
_logger.log(Level.FINE, "LDAP: login succeeded for: " + _username);
}
return grpList;
| public java.lang.String | getAuthType()Returns a short (preferably less than fifteen characters) description
of the kind of authentication which is supported by this realm.
return AUTH_TYPE;
| public java.util.Enumeration | getGroupNames(java.lang.String username)Returns the name of all the groups that this user belongs to.
Note that this information is only known after the user has
logged in. This is called from web path role verification, though
it should not be.
Vector v = (Vector)groupCache.get(username);
if (v == null) {
if (_logger.isLoggable(Level.FINE)) {
_logger.log(Level.FINE, "No groups available for: "+username);
}
// we don't load group here as we need to bind ctx to user with
// password before doing that and password is not available here
return emptyVector.elements();
} else {
return v.elements();
}
| private java.util.Properties | getLdapBindProps()Get binding properties defined in server.xml for LDAP server.
return (Properties)ldapBindProps.clone();
| private java.util.List | groupSearch(javax.naming.directory.DirContext ctx, java.lang.String baseDN, java.lang.String filter, java.lang.String target)Search for group membership using the given connection.
List groupList = new ArrayList();
try {
String[] targets = new String[1];
targets[0] = target;
SearchControls ctls = new SearchControls();
ctls.setReturningAttributes(targets);
ctls.setSearchScope(SearchControls.SUBTREE_SCOPE);
NamingEnumeration e = ctx.search(baseDN, filter, ctls);
while(e.hasMore()) {
SearchResult res = (SearchResult)e.next();
Attribute grpAttr = res.getAttributes().get(target);
int sz = grpAttr.size();
for (int i=0; i<sz; i++) {
String s = (String)grpAttr.get(i);
groupList.add(s);
}
}
} catch (Exception e) {
_logger.log(Level.WARNING, "ldaprealm.searcherror", filter);
_logger.log(Level.WARNING, "security.exception", e);
}
return groupList;
| public synchronized void | init(java.util.Properties props)Initialize a realm with some properties. This can be used
when instantiating realms from their descriptions. This
method may only be called a single time.
super.init(props);
String url = props.getProperty(PARAM_DIRURL);
this.setProperty(PARAM_DIRURL, url);
ldapBindProps.setProperty(Context.PROVIDER_URL, url);
String dn = props.getProperty(PARAM_USERDN);
this.setProperty(PARAM_USERDN, dn);
String jaasCtx = props.getProperty(IASRealm.JAAS_CONTEXT_PARAM);
this.setProperty(IASRealm.JAAS_CONTEXT_PARAM, jaasCtx);
if (url==null || dn==null || jaasCtx==null) {
String msg = sm.getString("ldaprealm.badconfig", url, dn, jaasCtx);
throw new BadRealmException(msg);
}
String mode = props.getProperty(PARAM_MODE, MODE_DEFAULT);
if (!MODE_DEFAULT.equals(mode)) {
String msg = sm.getString("ldaprealm.badmode", mode);
throw new BadRealmException(msg);
}
this.setProperty(PARAM_MODE, mode);
String ctxF = props.getProperty(PARAM_JNDICF, JNDICF_DEFAULT);
this.setProperty(PARAM_JNDICF, ctxF);
ldapBindProps.setProperty(Context.INITIAL_CONTEXT_FACTORY, ctxF);
String searchFilter = props.getProperty(
PARAM_SEARCH_FILTER, SEARCH_FILTER_DEFAULT);
this.setProperty(PARAM_SEARCH_FILTER,searchFilter);
String grpDN = props.getProperty(PARAM_GRPDN, dn);
this.setProperty(PARAM_GRPDN, grpDN);
String grpSearchFilter = props.getProperty(
PARAM_GRP_SEARCH_FILTER, GRP_SEARCH_FILTER_DEFAULT);
this.setProperty(PARAM_GRP_SEARCH_FILTER, grpSearchFilter);
String grpTarget = props.getProperty(
PARAM_GRP_TARGET, GRP_TARGET_DEFAULT);
this.setProperty(PARAM_GRP_TARGET, grpTarget);
String objectFactory = props.getProperty(
DYNAMIC_GROUP_FACTORY_OBJECT_PROPERTY, DYNAMIC_GROUP_OBJECT_FACTORY);
this.setProperty(DYNAMIC_GROUP_FACTORY_OBJECT_PROPERTY, objectFactory);
ldapBindProps.setProperty(DYNAMIC_GROUP_FACTORY_OBJECT_PROPERTY, objectFactory);
String stateFactory = props.getProperty(
DYNAMIC_GROUP_STATE_FACTORY_PROPERTY, DYNAMIC_GROUP_STATE_FACTORY);
this.setProperty(DYNAMIC_GROUP_STATE_FACTORY_PROPERTY, stateFactory);
ldapBindProps.setProperty(DYNAMIC_GROUP_STATE_FACTORY_PROPERTY, stateFactory);
String bindDN = props.getProperty(PARAM_BINDDN);
if (bindDN != null) {
this.setProperty(PARAM_BINDDN, bindDN);
ldapBindProps.setProperty(Context.SECURITY_PRINCIPAL, bindDN);
}
String bindPWD = props.getProperty(PARAM_BINDPWD);
if (bindPWD != null) {
this.setProperty(PARAM_BINDPWD, bindPWD);
ldapBindProps.setProperty(Context.SECURITY_CREDENTIALS, bindPWD);
}
Enumeration penum = props.propertyNames();
while (penum.hasMoreElements()) {
String propName = (String)penum.nextElement();
if (propName.startsWith("java.naming.") ||
propName.startsWith("javax.security.")) {
ldapBindProps.setProperty(propName, props.getProperty(propName));
} else if (propName.startsWith(SUN_JNDI_POOL_) &&
!SUN_JNDI_POOL_MAXSIZE.equals(propName)) {
if (System.getProperty(propName) == null) {
System.setProperty(propName, props.getProperty(propName));
}
}
}
String poolSize =
Integer.getInteger(PARAM_POOLSIZE,POOLSIZE_DEFAULT).toString();
String sunPoolSizeStr = props.getProperty(SUN_JNDI_POOL_MAXSIZE,
poolSize);
//Precedence rule: SUN_JNDI_POOL_MAXSIZE > PARAM_POOLSIZE > POOLSIZE_DEFAULT
try {
sunPoolSizeStr = Integer.valueOf(sunPoolSizeStr).toString();
} catch(Exception ex) {
sunPoolSizeStr = poolSize;
}
if (System.getProperty(SUN_JNDI_POOL_MAXSIZE) == null) {
System.setProperty(SUN_JNDI_POOL_MAXSIZE, sunPoolSizeStr);
}
this.setProperty(PARAM_POOLSIZE, sunPoolSizeStr);
String usePool = props.getProperty(SUN_JNDI_POOL, "true");
ldapBindProps.setProperty(SUN_JNDI_POOL, usePool);
if(url != null && url.startsWith(LDAPS_URL)) {
ldapBindProps.setProperty(LDAP_SOCKET_FACTORY,
DEFAULT_SSL_LDAP_SOCKET_FACTORY);
if (System.getProperty(SUN_JNDI_POOL_PROTOCOL) == null) {
System.setProperty(SUN_JNDI_POOL_PROTOCOL,DEFAULT_POOL_PROTOCOL);
}
if (_logger.isLoggable(Level.FINE)) {
_logger.log(Level.FINE, "LDAPRealm : Using custom socket factory for SSL with pooling");
}
}
if (_logger.isLoggable(Level.FINE)) {
Properties tempProps = (Properties)ldapBindProps.clone();
tempProps.remove(Context.SECURITY_CREDENTIALS);
_logger.log(Level.FINE, "LDAPRealm : " + tempProps);
}
groupCache = new HashMap();
emptyVector = new Vector();
| private void | setGroupNames(java.lang.String username, java.lang.String[] groups)Set group membership info for a user.
See bugs 4646133,4646270 on why this is here.
Vector v = new Vector(groups.length);
for (int i=0; i<groups.length; i++) {
v.add(groups[i]);
}
groupCache.put(username, v);
| private static void | substitute(java.lang.StringBuffer sb, java.lang.String target, java.lang.String value)Do string substitution. target is replaced by value for all
occurences.
int i = sb.indexOf(target);
while (i >= 0) {
sb.replace(i, i+target.length(), value);
i = sb.indexOf(target);
}
| private java.lang.String | userSearch(javax.naming.directory.DirContext ctx, java.lang.String baseDN, java.lang.String filter)Do anonymous search for the user. Should be unique if exists.
if (_logger.isLoggable(Level.FINEST)) {
_logger.log(Level.FINE, "search: baseDN: "+ baseDN +
" filter: " + filter);
}
String foundDN = null;
NamingEnumeration namingEnum = null;
SearchControls ctls = new SearchControls();
ctls.setReturningAttributes(_dnOnly);
ctls.setSearchScope(SearchControls.SUBTREE_SCOPE);
ctls.setCountLimit(1);
try {
namingEnum = ctx.search(baseDN, filter, ctls);
if (namingEnum.hasMore()) {
SearchResult res = (SearchResult)namingEnum.next();
StringBuffer sb = new StringBuffer();
//for dn name with '/'
CompositeName compDN = new CompositeName(res.getName());
String ldapDN = compDN.get(0);
sb.append(ldapDN);
if (res.isRelative()) {
sb.append(",");
sb.append(baseDN);
}
foundDN = sb.toString();
if (_logger.isLoggable(Level.FINEST)) {
_logger.log(Level.FINE, "Found user DN: " + foundDN);
}
}
} catch (Exception e) {
_logger.log(Level.WARNING, "ldaprealm.searcherror", filter);
_logger.log(Level.WARNING, "security.exception", e);
} finally {
if (namingEnum != null) {
try {
namingEnum.close();
} catch(Exception ex) {
}
}
}
return foundDN;
|
|