public class ActiveDirectoryRealm extends ActiveDirectoryRealmBase
dirContextSourceName
: the name of the DirContextSource
in JNDI with which
principals will be retrieved.localDirContextSource
: whether this DirContextSource
is locally configured in
the context.xml
or globally configured in the server.xml
(optional). Default
value is false
.additionalAttributes
: comma-separated list of attributes to be retrieved for the
principal. Binary attributes must end with ;binary
and will be stored as byte[]
,
ordinary attributes will be stored as String
. If an attribute is multivalued, it will be
stored as List
.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.objectSid
and sIDHistory
) of the
Active Directory security groups will be retrieved and no further configuration is required for
them.
SynchronizedStack
. No background thread is managing
the connections. They are acquired, validated, eventually closed and opened when
getPrincipal(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 the query fails, the connection is closed immediately. If the amount of requested
connections exceeds ones the available in the pool, new ones are opened and pushed onto the pool.
If the pool does not accept any addtional connetions they are closed immediately. connectionPoolSize
to greater than zero.
java.naming.referral
property and its values: ignore
,
throw
, and follow
. You can ignore referrals altogether, but the Active Directory
will still signal a PartialResultException
when a NamingEnumeration
is iterated.
The reason is because Oracle's LDAP implementation adds a ManageReferralControl
when
ignore
is set but Active Directory does not support it and returns a referral anyway.
This realm will catch this and continue to process the enumeration. If the
DirContextSource
is set to throw
, this realm will catch the
ReferralException
but avoid to follow the referral(s) manually (for several reasons) and
will continue with the process. Following referrals automatically is a completely opaque
operation to the application, the ReferralException
is handled internally and referral
contexts are queried and closed. Unfortunately, Oracle's LDAP implementation is not able to
handle this properly and only Oracle can fix this shortcoming. Issues have already been reported
(Review IDs 9089870 and 9089874, public issues
JDK-8161361 and
JDK-8161361)!
What is the shortcoming and how can it be solved? Microsoft takes a very sophisticated
approach on not to rely on host names 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 host name, but only a DNS 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 host
name or a DNS domain name served by one or more machines. If it turns out to be a DNS domain
name, you have to select one target host from the query response (according to RFC 2782),
construct a domain-based SPN ldap/<targetHost>/<dnsDomainName>@<REALM>
or a host-based
one ldap/<targetHost>@<REALM>
, obtain a service ticket for and connect to that target
host. If it is a regular host name, which is not the usual case with Active Directory, Oracle's
internal implementation will behave correctly.
The follow
implementation cannot be made to work because there is no way to tell the
internal classes to perform this DNS SRV query and pass the appropriate server name(s) for the
SPN to the SaslClient
. It is deemed to fail. Note, that host name canocalization might
sound reasonable within the SaslClient
, but this is deemed to fail too for two reasons:
First, the SaslClient
will receive an arbitrary IP address without knowing whether the
LDAP client socket will use the same one. You will have a service ticket issued for another host
and your authentication will fail. Second, most Kerberos implementations rely on reverse DNS
records, but Microsoft's Active Directory concept does not care about reverse DNS, it does not
canonicalize host names by default and there is no guarantee, that reverse DNS is set up
properly. Some environments do not even have control over the reverse DNS zone (PTR
records). Using throw
will not make it any better because the referral URL returned by
ReferralException.getReferralInfo()
cannot be changed with the calculated value(s) from
DNS. ReferralException.getReferralContext()
will unconditionally reuse that value. The
only way (theoretically) to achieve this is to construct an InitialDirContext
with the
new URL manually and work with it appropriately. Though, this approach has not been evaluated and
at this time and won't be implemented. (Changing the URLs manually in the debugger makes it work
actually)
How to work around this issue? There are several ways depending on your setup: Use the Global Catalog (port 3268) with
ignore
, orfollow
or throw
with a DirContextSource
in your home forest, patch
com.sun.jndi.ldap.LdapCtxFactory
to properly resolve DNS domain names to host names and
prepend it to the boot classpath and all referrals will be cleanly resolved, orignore
with multiple DirContextSources
, and create a CombinedRealm
with one ActiveDirectoryRealm
per forest.This issue is also documented on Stack Overflow. Additionally, 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.
ActiveDirectoryPrincipal
Modifier and Type | Class and Description |
---|---|
protected static class |
ActiveDirectoryRealm.DirContextConnection |
protected static class |
ActiveDirectoryRealm.User |
RealmBase.AllRolesMode
Lifecycle.SingleUse
Modifier and Type | Field and Description |
---|---|
protected String[] |
additionalAttributes |
protected SynchronizedStack<ActiveDirectoryRealm.DirContextConnection> |
connectionPool |
protected int |
connectionPoolSize |
protected String |
dirContextSourceName |
protected boolean |
localDirContextSource |
protected long |
maxIdleTime |
protected static String |
name
Descriptive information about this Realm implementation.
|
logger, sm
allRolesMode, container, containerLog, realmPath, stripRealmForGss, support, validate, x509UsernameRetriever, x509UsernameRetrieverClassName
mserver
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 and Description |
---|
ActiveDirectoryRealm() |
Modifier and Type | Method and Description |
---|---|
protected ActiveDirectoryRealm.DirContextConnection |
acquire() |
protected void |
close(ActiveDirectoryRealm.DirContextConnection connection) |
protected void |
close(NamingEnumeration<?> results) |
protected Name |
getDistinguishedName(DirContext context,
Name baseName,
SearchResult result)
Returns the distinguished name of a search result.
|
protected String |
getName() |
protected Principal |
getPrincipal(GSSName gssName,
GSSCredential gssCredential) |
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 |
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 the
DirContextSource |
void |
setLocalDirContextSource(boolean localDirContextSource)
Sets whether the
DirContextSource is locally (context.xml defined or globally
server.xml . |
void |
setMaxIdleTime(long maxIdleTime)
Sets the maximum amount of time in milliseconds a directory server connection should remain
idle before it is closed.
|
protected void |
startInternal() |
protected void |
stopInternal() |
protected boolean |
validate(ActiveDirectoryRealm.DirContextConnection connection) |
getPassword, getPrincipal, getRoles, hasRoleInternal
addPropertyChangeListener, authenticate, authenticate, authenticate, authenticate, authenticate, authenticate, backgroundProcess, Digest, findSecurityConstraints, getAllRolesMode, getContainer, getCredentialHandler, getDigest, getDomainInternal, getObjectNameKeyProperties, getPrincipal, getPrincipal, getRealmPath, getRealmSuffix, getServer, getTransportGuaranteeRedirectStatus, getValidate, getX509UsernameRetrieverClassName, hasMessageDigest, hasResourcePermission, hasRole, hasUserDataPermission, initInternal, isAvailable, isStripRealmForGss, main, removePropertyChangeListener, setAllRolesMode, setContainer, setCredentialHandler, setRealmPath, setStripRealmForGss, setTransportGuaranteeRedirectStatus, setValidate, setX509UsernameRetrieverClassName, toString
destroyInternal, getDomain, getObjectName, postDeregister, postRegister, preDeregister, preRegister, register, setDomain, unregister
addLifecycleListener, destroy, findLifecycleListeners, fireLifecycleEvent, getState, getStateName, getThrowOnFailure, init, removeLifecycleListener, setState, setState, setThrowOnFailure, start, stop
protected boolean localDirContextSource
protected String dirContextSourceName
protected String[] additionalAttributes
protected int connectionPoolSize
protected long maxIdleTime
protected SynchronizedStack<ActiveDirectoryRealm.DirContextConnection> connectionPool
protected static final String name
public void setLocalDirContextSource(boolean localDirContextSource)
DirContextSource
is locally (context.xml
defined or globally
server.xml
.localDirContextSource
- the local directory context source indicationpublic void setDirContextSourceName(String dirContextSourceName)
DirContextSource
dirContextSourceName
- the directory context source namepublic void setAdditionalAttributes(String additionalAttributes)
additionalAttributes
- the additional attributespublic void setConnectionPoolSize(int connectionPoolSize)
connectionPoolSize
- the connection pool sizepublic void setMaxIdleTime(long maxIdleTime)
maxIdleTime
- the maximum idle timeprotected Principal getPrincipal(GSSName gssName, GSSCredential gssCredential)
getPrincipal
in class RealmBase
protected ActiveDirectoryRealm.DirContextConnection acquire()
protected boolean validate(ActiveDirectoryRealm.DirContextConnection connection)
protected void release(ActiveDirectoryRealm.DirContextConnection connection)
protected void open(ActiveDirectoryRealm.DirContextConnection connection)
protected void close(ActiveDirectoryRealm.DirContextConnection connection)
protected void close(NamingEnumeration<?> results)
protected void startInternal() throws LifecycleException
startInternal
in class RealmBase
LifecycleException
protected void stopInternal() throws LifecycleException
stopInternal
in class RealmBase
LifecycleException
protected ActiveDirectoryRealm.User getUser(DirContext context, GSSName gssName) throws NamingException
NamingException
protected List<String> getRoles(DirContext context, ActiveDirectoryRealm.User user) throws NamingException
NamingException
protected Name getDistinguishedName(DirContext context, Name baseName, SearchResult result) throws NamingException
context
- Our DirContextbaseName
- The base DNresult
- The search resultNamingException
- if DN cannot be buildprotected Name getRelativeName(DirContext context, String distinguishedName) throws NamingException
NamingException
Copyright © 2013–2020 Michael Osipov. All rights reserved.