Class ActiveDirectoryRealm
- java.lang.Object
-
- org.apache.catalina.util.LifecycleBase
-
- org.apache.catalina.util.LifecycleMBeanBase
-
- org.apache.catalina.realm.RealmBase
-
- net.sf.michaelo.tomcat.realm.ActiveDirectoryRealmBase
-
- net.sf.michaelo.tomcat.realm.ActiveDirectoryRealm
-
- All Implemented Interfaces:
MBeanRegistration
,Contained
,JmxEnabled
,Lifecycle
,Realm
public class ActiveDirectoryRealm extends ActiveDirectoryRealmBase
A realm which retrieves already authenticated users from Active Directory via LDAP.Configuration
Following options can be configured:dirContextSourceName
: the name of theDirContextSource
in JNDI with which principals will be retrieved.localDirContextSource
: whether thisDirContextSource
is locally configured in thecontext.xml
or globally configured in theserver.xml
(optional). Default value isfalse
.roleFormats
: comma-separated list of role formats to be applied to user security groups. The following values are possible:sid
retrieves theobjectSid
andsIDHistory
attribute values,name
retrieves themsDS-PrincipalName
attribute value representing the down-level logon name format:{netbiosDomain}\{samAccountName}
, andnameEx
retrieves thedistinguishedName
andsAMAccountName
attribute values and converts the DC RDNs from the DN to the Kerberos realm and appends thesAMAccountName
(reversed RFC 2247) with format{realm}\{samAccountName}
. Default issid
.prependRoleFormat
: whether the role format is prepended to the role as{roleFormat}:{role}
. Default isfalse
.additionalAttributes
: comma-separated list of attributes to be retrieved for the principal. Binary attributes must end with;binary
and will be stored asbyte[]
, ordinary attributes will be stored asString
. If an attribute is multivalued, it will be stored asList
.connectionPoolSize
: the maximum amount of directory server connections the pool will hold. Default is zero which means no connections will be pooled.maxIdleTime
: the maximum amount of time in milliseconds a directory server connection should remain idle before it is closed. Default value is 15 minutes.
Connection Pooling
This realm offers a poor man's directory server connection pooling which can drastically improve access performance for non-session (stateless) applications. It utilizes a LIFO structure based onSynchronizedStack
. No background thread is managing the connections. They are acquired, validated, eventually closed and opened whengetPrincipal(GSSName, GSSCredential)
is invoked. Validation involves a minimal and limited query with at most 500 ms of wait time just to verify the connection is alive and healthy. If this query fails, the connection is closed immediately. If the amount of requested connections exceeds the ones available in the pool, new ones are opened and pushed onto the pool. If the pool does not accept any addtional connections they are closed immediately.Note: This connection pool feature has to be explicitly enabled by setting
connectionPoolSize
to greater than zero.On Usernames
This realm processes supplied usernames with different types.Supported Types
Only a subset of username types are accepted in contrast to other realm implementations. Namely, this realm must know what type is passed to properly map it into Active Directory search space with aUsernameSearchMapper
implementation. The supported username types are:GSSName
by inspecting the string name type,X509Certificate
by extracting theSAN:otherName
field and matching for MS UPN type id (1.3.6.1.4.1.311.20.2.3).
Note: Both types represent already authenticated users by means of a GSS and/or TLScontext.
Canonicalization
This realm will always try to canonicalize a given username type to a realGSSName
with the string name type ofKRB5_NT_PRINCIPAL
(1.2.840.113554.1.2.2.1) similar to thecanonicalize
flag in thekrb5.conf
file. This makes the finalGSSName
fully usable in subsequent GSS-API calls.Referral Handling
Active Directory uses two type of responses when it cannot complete a search request: referrals and search result references. Both are different in nature, read more about them here. For this section I will use the term referral for both types synomously as does the JNDI/LDAP Provider documentation.
When working with the default LDAP ports (not GC) or in a multi-forest environment, it is highly likely to receive referrals (either subordinate or cross) during a search or lookup. Sun's JNDI/LDAP Provider takes the following approach to handle referrals with thejava.naming.referral
property and its values:ignore
,throw
, andfollow
. You can ignore referrals altogether, but the provider will still signal aPartialResultException
when aNamingEnumeration
is iterated. The reason is because it adds aManageReferralControl
whenignore
is set and assumes that the target server will ignore referrals, but this is a misconception in this provider implementation, see here and here. It is also unclear whether Microsoft Active Directory supports this control.
This realm will catch this exception and continue to process the enumeration. If theDirContextSource
is set tothrow
, this realm will catch theReferralException
also, but avoid following referrals manually (for several reasons) and will continue with the process. Following referrals automatically is a completely opaque operation to the application, noReferralException
is thrown, but the referrals are handled internally and referral contexts are queried and closed. If you choose tofollow
referrals you must use my Active Directory DNS Locator otherwise the queries will fail and you will suffer from JDK-8161361 and JDK-8160768!Why do you need to use my Active Directory DNS Locator? Microsoft takes a very sophisticated approach on not to rely on hostnames because servers can be provisioned and decommissioned any time. Instead, they heavily rely on DNS domain names and DNS SRV records at runtime. I.e., an initial or a referral URL does not contain a hostname, but only a domain name. While you can connect to the service with this name, you cannot easily authenticate against it with Kerberos because one cannot bind the same SPN
ldap/<dnsDomainName>@<REALM>
, e.g.,ldap/example.com@EXAMPLE.COM
to more than one account. If you try authenticate anyway, you will receive a "Server not found in Kerberos database (7)" error. Therefore, one has to perform a DNS SRV query (_ldap._tcp.<dnsDomainName>
) to test whether this name is a hostname or a domain name served by one or more servers. If it turns out to be a domain name, you have to select one target host from the query response (according to RFC 2782), construct a domain-based SPNldap/<targetHost>/<dnsDomainName>@<REALM>
or a host-based oneldap/<targetHost>@<REALM>
, obtain a service ticket for and connect to that target host.How to handle referrals? There are several ways depending on your setup: Use the Global Catalog (port 3268) with a single forest and set referrals to
ignore
, or with multiple forests and set referrals to eitherfollow
with aDirContextSource
in your home forest and use my Active Directory DNS Locator, orignore
with multipleDirContextSources
, and create aCombinedRealm
with oneActiveDirectoryRealm
per forest.
You will then have the principal properly looked up in Active Directory.
Further references: How DNS Support for Active Directory Works is a good read on the DNS topic as well as Global Catalog and LDAP Searches and LDAP Referrals.
Note: Always remember, referrals incur an amplification in time and space and make the entire process slower.
Tip: Consider using thePacDataActiveDirectoryRealm
if you don't need all features and useSPNEGO authentication
only since it is orders of magnitude faster, but remember though thatX.509 authentication
still requires this realm.- See Also:
ActiveDirectoryPrincipal
-
-
Nested Class Summary
Nested Classes Modifier and Type Class Description protected static class
ActiveDirectoryRealm.DirContextConnection
protected static class
ActiveDirectoryRealm.User
-
Nested classes/interfaces inherited from class org.apache.catalina.realm.RealmBase
RealmBase.AllRolesMode
-
Nested classes/interfaces inherited from interface org.apache.catalina.Lifecycle
Lifecycle.SingleUse
-
-
Field Summary
Fields Modifier and Type Field Description protected String[]
additionalAttributes
protected String[]
attributes
protected SynchronizedStack<ActiveDirectoryRealm.DirContextConnection>
connectionPool
protected int
connectionPoolSize
protected String
dirContextSourceName
protected boolean
localDirContextSource
protected long
maxIdleTime
protected boolean
prependRoleFormat
protected String[]
roleAttributes
protected String[]
roleFormats
-
Fields inherited from class net.sf.michaelo.tomcat.realm.ActiveDirectoryRealmBase
logger, sm
-
Fields inherited from class org.apache.catalina.realm.RealmBase
allRolesMode, container, containerLog, realmPath, stripRealmForGss, support, USER_ATTRIBUTES_DELIMITER, USER_ATTRIBUTES_WILDCARD, userAttributes, userAttributesList, validate, x509UsernameRetriever, x509UsernameRetrieverClassName
-
Fields inherited from class org.apache.catalina.util.LifecycleMBeanBase
mserver
-
Fields inherited from interface org.apache.catalina.Lifecycle
AFTER_DESTROY_EVENT, AFTER_INIT_EVENT, AFTER_START_EVENT, AFTER_STOP_EVENT, BEFORE_DESTROY_EVENT, BEFORE_INIT_EVENT, BEFORE_START_EVENT, BEFORE_STOP_EVENT, CONFIGURE_START_EVENT, CONFIGURE_STOP_EVENT, PERIODIC_EVENT, START_EVENT, STOP_EVENT
-
-
Constructor Summary
Constructors Constructor Description ActiveDirectoryRealm()
-
Method Summary
All Methods Static Methods Instance Methods Concrete Methods Modifier and Type Method Description protected ActiveDirectoryRealm.DirContextConnection
acquire()
protected void
close(NamingEnumeration<?> results)
protected void
close(ActiveDirectoryRealm.DirContextConnection connection)
protected Name
getDistinguishedName(DirContext context, Name baseName, SearchResult result)
Returns the distinguished name of a search result.protected static String
getNextConnectionId()
protected Principal
getPrincipal(X509Certificate userCert)
protected Principal
getPrincipal(GSSName gssName, GSSCredential gssCredential)
protected Principal
getPrincipal(GSSName gssName, GSSCredential gssCredential, boolean retry)
protected Name
getRelativeName(DirContext context, String distinguishedName)
protected List<String>
getRoles(DirContext context, ActiveDirectoryRealm.User user)
protected ActiveDirectoryRealm.User
getUser(DirContext context, GSSName gssName)
protected void
initInternal()
protected void
open(ActiveDirectoryRealm.DirContextConnection connection)
protected void
release(ActiveDirectoryRealm.DirContextConnection connection)
void
setAdditionalAttributes(String additionalAttributes)
Sets a comma-separated list of Active Directory attributes retreived and stored for the user principal.void
setConnectionPoolSize(int connectionPoolSize)
Sets the maximum amount of directory server connections the pool will hold.void
setDirContextSourceName(String dirContextSourceName)
Sets the name of theDirContextSource
void
setLocalDirContextSource(boolean localDirContextSource)
Sets whether theDirContextSource
is locally (context.xml
defined or globallyserver.xml
.void
setMaxIdleTime(long maxIdleTime)
Sets the maximum amount of time in milliseconds a directory server connection should remain idle before it is closed.void
setPrependRoleFormat(boolean prependRoleFormat)
Sets whether the role format is prepended to the role.void
setRoleFormats(String roleFormats)
Sets a comma-separated list of role formats to be applied to user security groups from Active Directory.protected void
startInternal()
protected void
stopInternal()
protected boolean
validate(ActiveDirectoryRealm.DirContextConnection connection)
-
Methods inherited from class net.sf.michaelo.tomcat.realm.ActiveDirectoryRealmBase
getPassword, getPrincipal, getRoles, hasRoleInternal
-
Methods inherited from class org.apache.catalina.realm.RealmBase
addPropertyChangeListener, authenticate, authenticate, authenticate, authenticate, authenticate, authenticate, authenticate, backgroundProcess, findSecurityConstraints, getAllRolesMode, getContainer, getCredentialHandler, getDigest, getDigest, getDomainInternal, getObjectNameKeyProperties, getPrincipal, getPrincipal, getRealmPath, getRealmSuffix, getServer, getTransportGuaranteeRedirectStatus, getUserAttributes, getValidate, getX509UsernameRetrieverClassName, hasMessageDigest, hasResourcePermission, hasRole, hasUserDataPermission, isStripRealmForGss, main, parseUserAttributes, removePropertyChangeListener, setAllRolesMode, setContainer, setCredentialHandler, setRealmPath, setStripRealmForGss, setTransportGuaranteeRedirectStatus, setUserAttributes, setValidate, setX509UsernameRetrieverClassName, toString
-
Methods inherited from class org.apache.catalina.util.LifecycleMBeanBase
destroyInternal, getDomain, getObjectName, postDeregister, postRegister, preDeregister, preRegister, register, setDomain, unregister, unregister
-
Methods inherited from class org.apache.catalina.util.LifecycleBase
addLifecycleListener, destroy, findLifecycleListeners, fireLifecycleEvent, getState, getStateName, getThrowOnFailure, init, removeLifecycleListener, setState, setState, setThrowOnFailure, start, stop
-
Methods inherited from class java.lang.Object
clone, equals, finalize, getClass, hashCode, notify, notifyAll, wait, wait, wait
-
Methods inherited from interface org.apache.catalina.Realm
isAvailable
-
-
-
-
Field Detail
-
localDirContextSource
protected boolean localDirContextSource
-
dirContextSourceName
protected String dirContextSourceName
-
attributes
protected String[] attributes
-
additionalAttributes
protected String[] additionalAttributes
-
roleFormats
protected String[] roleFormats
-
roleAttributes
protected String[] roleAttributes
-
prependRoleFormat
protected boolean prependRoleFormat
-
connectionPoolSize
protected int connectionPoolSize
-
maxIdleTime
protected long maxIdleTime
-
connectionPool
protected SynchronizedStack<ActiveDirectoryRealm.DirContextConnection> connectionPool
-
-
Method Detail
-
getNextConnectionId
protected static String getNextConnectionId()
-
setLocalDirContextSource
public void setLocalDirContextSource(boolean localDirContextSource)
Sets whether theDirContextSource
is locally (context.xml
defined or globallyserver.xml
.- Parameters:
localDirContextSource
- the local directory context source indication
-
setDirContextSourceName
public void setDirContextSourceName(String dirContextSourceName)
Sets the name of theDirContextSource
- Parameters:
dirContextSourceName
- the directory context source name
-
setAdditionalAttributes
public void setAdditionalAttributes(String additionalAttributes)
Sets a comma-separated list of Active Directory attributes retreived and stored for the user principal.- Parameters:
additionalAttributes
- the additional attributes
-
setRoleFormats
public void setRoleFormats(String roleFormats)
Sets a comma-separated list of role formats to be applied to user security groups from Active Directory.- Parameters:
roleFormats
- the role formats
-
setPrependRoleFormat
public void setPrependRoleFormat(boolean prependRoleFormat)
Sets whether the role format is prepended to the role.- Parameters:
prependRoleFormat
- the prepend role format indication
-
setConnectionPoolSize
public void setConnectionPoolSize(int connectionPoolSize)
Sets the maximum amount of directory server connections the pool will hold.- Parameters:
connectionPoolSize
- the connection pool size
-
setMaxIdleTime
public void setMaxIdleTime(long maxIdleTime)
Sets the maximum amount of time in milliseconds a directory server connection should remain idle before it is closed.- Parameters:
maxIdleTime
- the maximum idle time
-
getPrincipal
protected Principal getPrincipal(X509Certificate userCert)
- Overrides:
getPrincipal
in classRealmBase
-
getPrincipal
protected Principal getPrincipal(GSSName gssName, GSSCredential gssCredential)
- Overrides:
getPrincipal
in classRealmBase
-
getPrincipal
protected Principal getPrincipal(GSSName gssName, GSSCredential gssCredential, boolean retry)
-
acquire
protected ActiveDirectoryRealm.DirContextConnection acquire() throws NamingException
- Throws:
NamingException
-
validate
protected boolean validate(ActiveDirectoryRealm.DirContextConnection connection)
-
release
protected void release(ActiveDirectoryRealm.DirContextConnection connection)
-
open
protected void open(ActiveDirectoryRealm.DirContextConnection connection) throws NamingException
- Throws:
NamingException
-
close
protected void close(ActiveDirectoryRealm.DirContextConnection connection)
-
close
protected void close(NamingEnumeration<?> results)
-
initInternal
protected void initInternal() throws LifecycleException
- Overrides:
initInternal
in classRealmBase
- Throws:
LifecycleException
-
startInternal
protected void startInternal() throws LifecycleException
- Overrides:
startInternal
in classRealmBase
- Throws:
LifecycleException
-
stopInternal
protected void stopInternal() throws LifecycleException
- Overrides:
stopInternal
in classRealmBase
- Throws:
LifecycleException
-
getUser
protected ActiveDirectoryRealm.User getUser(DirContext context, GSSName gssName) throws NamingException
- Throws:
NamingException
-
getRoles
protected List<String> getRoles(DirContext context, ActiveDirectoryRealm.User user) throws NamingException
- Throws:
NamingException
-
getDistinguishedName
protected Name getDistinguishedName(DirContext context, Name baseName, SearchResult result) throws NamingException
Returns the distinguished name of a search result.- Parameters:
context
- Our DirContextbaseName
- The base DNresult
- The search result- Returns:
- String containing the distinguished name
- Throws:
NamingException
- if DN cannot be build
-
getRelativeName
protected Name getRelativeName(DirContext context, String distinguishedName) throws NamingException
- Throws:
NamingException
-
-