public class LdapSecurityProvider extends SecurityProvider
SecurityProvider
implementation that provides a simple API to search, retrieve, create, update and delete accounts and entries, validate credentials, check group membership and set and change passwords on accounts in an LDAP directory such as AD DS, Azure AD DS or OpenLDAP.
This API is designed to make performing common LDAP operations as simple as possible, efficient and with a minimal amount of code. This API does not actively expose search controls, extended requests and other more complex operations however the underlying authenticated JNDI context can be retrieved for these scenarios and in some cases the JNDI and Jespa LDAP APIs can be leveraged together (see the Mixing JNDI and Jespa LDAP APIs section).
search(java.lang.String[], java.lang.String)
method to retrieve multiple entries based on a search filter.getAccount(java.lang.String, java.lang.String[])
method to retrieve accounts using conventional account names.getEntry(java.lang.Object, java.lang.String[])
method to retrieve entries by DN.authenticate(java.lang.Object)
method to validate plaintext credentials.LdapEntry.create()
, LdapEntry.update()
and LdapEntry.delete()
methods (of the LdapAccount
class) to create, update and delete accounts.LdapEntry.create()
, LdapEntry.update()
and LdapEntry.delete()
methods to create, update and delete entries in the directory.LdapAccount.setPassword(char[])
and LdapAccount.changePassword(char[], char[])
methods to set and change passwords on accounts.LdapAccount.isMemberOf(java.lang.String)
method to check group membership of an account.getProperty(java.lang.String, java.lang.Object)
method with the key ldap.context to retrieve the underlying JNDI context.Other parts of the SecurityProvider interface are not be implemented (for example initSecContext and acceptSecContext cannot be implemented because LDAP is not an authentication protocol).
The authenticate and isMemberOf methods of NtlmSecurityProvider
should be favored wherever possible because it's implementations of those methods are significantly more efficient.
Property Name | Description | Example | ||||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
bindstr | An RFC2255 style LDAP URL. See The bindstr Property section below for details. | ldap://busicorp.local/CN=Users,DC=busicorp,DC=local |
||||||||||||||||||
ldap.disposition |
A string that indicates that the authority identified by the bindstr property has certain characteristics.
The default value is currently "ADS2003" to indicate that the authority is AD DS or Azure AD DS.
A value that begins with "RFC" (such as RFC2251 or RFC2307) will indicate that the authority is an RFC based implementation like OpenLDAP.
Currently this implementation only supports two dispositions.
Any value that begins with "ADS" indicates that the server is AD DS.
Any value that beings with "RFC" indicates that the server is an RFC-based implementation
The following table describes what these two dispositions control.
|
RFC2251 | ||||||||||||||||||
service.acctname |
The default account name used to authenticate an LDAP bind.
This value must be appropriate for the authentication mechanism being used (controlled by ldap.disposition and the JNDI SECURITY_AUTHENTICATION property).
If the server is AD DS, this property should be a principal name whereas if the server is an RFC based server it will almost certainly need to be a DN (see the Distinguished Names and Relative Distinguished Names section below).
This property is not required when the authentication mechanism being used does not need an account name.
For example, this is true when credentials are passed using the JAAS model such as with the Sun Krb5LoginModule or Jespa NtlmSecurityProvider through a PrivilegedExceptionAction.
|
jespa2@busicorp.local |
||||||||||||||||||
service.password |
The default password corresponding to the service.acctname property.
Note: This class will automatically convert this property into the encrypted service.password.encrypted property and it will also automatically decrypt it when it is retrieved with getProperty.
|
moonbike69 |
||||||||||||||||||
service.password.encrypted |
The encrypted form of the default password corresponding to the service.acctname property.
This property is automatically generated by the SecurityProvider class when the service.password property is set.
Note: This property is not portable outside of the JVM because the encryption key used to encrypt properties is a static and accessible only to the class containing the encryption and decryption routines.
|
Btuh2l11VwURpDLFpFxFRnuUkNwuIpzP |
||||||||||||||||||
ldap.authenticate.setcredential |
If set to true and the authenticate(java.lang.Object) method successfully validates a PasswordCredential, the supplied credential will be set on this LdapSecurityProvider as the service.acctname and service.password properties so that subsequent uses of this security provider will use the supplied credentials.
Meaning the authenticated user will be impersonated.
IMPORTANT: Using this feature will allow the developer to recover the user's plaintext password.
If this property is set to true, the user's credentials will persist when exported and imported (such as when used with the HttpSecurityService).
Even though the password property is encrypted (see the service.password.encrypted property above) and therefore never exported as plaintext, the plaintext password can be recovered within the JVM by simply calling
getProperty("service.password") (although the encryption of the password will prevent it from being leaked outside of the JVM such as onto disk).
Because LDAP can only validate plaintext credentials, it must be possible to recover the plaintext password to impersonate a user.
|
|||||||||||||||||||
flags.confidentiality |
Indicates as to whether or not LDAP communication should be encrypted.
If this property is not set and the Jespa NtlmSecurityProvider is used, NTLM session security is used to encrypt LDAP communication. This is the default behavior when using the LdapSecurityProvider with the ADS2003 disposition. If this property is set to false, no encryption will be used. If this property is set to true, TLS encryption will be used.
IMPORTANT: If the SecurityProvider authority is not AD DS, it is strongly recommended that this property is set to true (especially if the provider is being used to validate credentials such as with the Jespa HttpSecurityService).
TLS encryption may require importing a PKI certificate from the target LDAP server. |
true | ||||||||||||||||||
ldap.tls.trustall |
If this property is set to true and TLS transport encryption is used (by setting flags.confidentiality = true ) a "dummy" X509TrustManager will be installed which will completely disable server certificate verification.
This elimiates the requirement for exporting and configuring a certificate trust store (such as with -Djavax.net.ssl.trustStore=filename on the commandline).
Traffic will still be encrypted but that will not provide protection if the client can be tricked into connecting to a compermised server.
Note: Unlike SSL, the java.naming.ldap.factory.socket property has no effect on TLS certificate verification.
|
true | ||||||||||||||||||
ldap.tls.sslsocketfactory.classname |
When set to the class name of an instance of javax.net.ssl.SSLSocketFactory, this provider will attempt to load this class and pass it to StartTlsResponse.negotiate() when TLS is used (by setting flags.confidentiality = true ).
Note: Unlike SSL, the java.naming.ldap.factory.socket property has no effect on TLS behavior.
|
true | ||||||||||||||||||
ldap.account.filter |
A format string to be used as an LDAP filter for querying LDAP accounts.
This string must contain a single %s which will be substituted with the account name being queried.
This property is ignored if the ldap.disposition begins with "ADS".
The default value of this property is (&(objectClass=posixAccount)(uid=%s)) .
|
(&(objectClass=posixAccount)(mail=%s)) |
||||||||||||||||||
ldap.account.attrname | The name of the attribute used to query the identity of an account. The default value depends on the ldap.disposition value. If ldap.disposition begins with "ADS" the default value is sAMAccountName. Otherwise the default value is uid. | |||||||||||||||||||
ldap.attributes.definitions |
A HashMap<String,LdapAttrDef> object containing attribute definitions.
See the LdapAttrDef class for details.
|
|||||||||||||||||||
ldap.attributes.date.format |
The SimpleDateFormat format string used to format dates if the dates are converted to a string representation using the CONV_DATESTR_X_TIMEUTC or CONV_DATESTR_X_TIME1601 LdapAttrDef conversions.
The default value is yyyy-MM-dd HH:mm:ss .
See the LdapAttrDef class for details.
|
MM-dd-yyyy |
||||||||||||||||||
ldap.search.maxcount |
The maximum number of entries to return from the search(java.lang.String[], java.lang.String) method.
The default value is 1000.
Setting this value higher when using multiple concurrent searches can use large amounts of JVM memory because the underlying Sun JNDI implementation does not appear to immediately release search results regardless of how careful the developer is about destroying contexts.
|
5000 | ||||||||||||||||||
ldap.search.timeout | The number of milliseconds to wait for the search to complete. The default value is 60000. A value of 0 indicates that the LDAP client should wait indefinitely. | 240000 | ||||||||||||||||||
ldap.context | This read-only property returns the underlying JNDI InitialLdapContext object. This is useful for performing more advanced LDAP operations using an already established context or for obtaining an LDAP context that has been authenticated using a Jespa SecurityProvider like the NtlmSecurityProvider which provides SASL confidentiality and integrity that would otherwise require SSL or TLS. | |||||||||||||||||||
authority.dns.name |
The fully qualified DNS hostname of the specific host to which this LdapSecurityProvider is bound.
This property should only be read after the LdapSecurityProvider has been bound.
This property can also be set as a hint to the LdapSecurityProvider that it should prefer a particular host. In this case, this property must be set before the LdapSecurityProvider has been bound to the authority. Setting this property may be critical if entries are modified and queried in quick succession using different provider instances because modifications may not have time to replicate between multiple servers in a domain. For example, if an HTTP request is used to create an entry and then immediately afterward a separate HTTP request (and thus separate provider context) is used to query the new entry, the entry may not be found because a different server was chosen and the entry did not yet replicate to that server. Regarding HTTP applications in general, when a new HTTP session is established, this property should be queried and stored in the HTTP session and subsequently set so that the same LDAP server is reused. This behavior is referred to as "server stickiness". |
dc1.busicorp.local |
||||||||||||||||||
javax.naming.* | All properties that begin with "javax.naming." will be copied into the InitialLdapContext environment. | |||||||||||||||||||
com.sun.jndi.* | All properties that begin with "com.sun.jndi." will be copied into the InitialLdapContext environment. | |||||||||||||||||||
ldap.base | This read-only property returns the LDAP base supplied in the LDAP URL. If the special AD DS specific base identifier DefaultNamingContext was supplied in the LDAP URL, the real base DN will be retrieved from AD. If the special base identifier RootDSE was supplied in the LDAP URL, an empty string will be returned. |
All other properties are copied into the InitialLdapContext environment with the "jespa." prefix. These include, but are not limited to, SecurityProvider and SaslClient properties. For example, the "javax.security.sasl.maxbuffer" property sets the SASL buffer size just as described on the Java API documentation for the javax.security.sasl.SaslClient.MAX_BUFFER constant (although when used with a component like the HttpSecurityService the property would need to be prefixed with "jespa." like "jespa.javax.security.sasl.maxbuffer").
ldap://domain:port/base?attrs?scope?filter
Every LdapSecurityProvider instance MUST be supplied with a bindstr property. The attrs, scope and filter fields represent default values for searching and as such these fields are not commonly used because explicit values may be supplied to methods that require these fields. Each field is described briefly as follows:
LDAP URL Field | Description |
---|---|
domain | The DNS domain representing the "authority" for the LdapSecurityProvider. In practice, this field can also be a fully qualified DNS hostname of a specific server or even just an IP address although in these cases much of the fault tolerance and redundacy logic will not be exercised. |
port |
The port number to which the LDAP client should connect.
The default is 389 or 636 if the scheme is ldaps:// .
|
base | A DN or the special identifiers DefaultNamingContext or RootDSE that indicates the default base container used for searching entries and constructing DNs from RDNs. The identifier RootDSE indicates that the base is the special root of the directory tree. The identifier DefaultNamingContext refers to the special AD DS specific defaultNamingContext attribute of the RootDSE and that the value of this attribute should be used as the base. If the base field is empty, the base is the RootDSE. |
attrs | A comma separated list of attribute names that indicates the default attributes to be retrieved in a search operation. If the attrs field is empty or '*', this indicates that all attributes should be retrieved. |
scope | Either base, one or sub indicating the default scope of a search operation. If no scope is supplied, the default is sub. |
filter |
The default LDAP search filter of a search operation.
If no filter is supplied, the default value is (objectClass=*) .
|
For additional details regarding the above fields, see the search(java.lang.String[], java.lang.String)
method.
All fields are optional except the domain.
The following are some examples of bindstr property values.
Example bindstr Value | Notes |
---|---|
busicorp.local |
The DNS domain name is the simplest and most common bindstr. LDAP servers will be located using DNS SRV lookups which provides fault tolerance and redundancy. |
dc1.busicorp.local |
If a hostname is supplied in a bindstr, that hostname must still be listed in the DNS SRV results. If the host does not have proper SRV records in DNS, it will be necessary to either a) set authority.dns.names.resolve = false to disable DNS SRV lookups or b) use the dns.records.path property to override DNS with custom records loaded from a file or c) use an IP address so that DNS is not used at all. |
172.14.30.22 |
Although not recommended, in many instances, an IP address is sufficient to bind a specific server.
Note that an IP address would not work if PKI encryption was requested (by using ldaps:// or setting flags.confidentiality = true ) because the hostname is required to select a PKI certificate.
|
ldap://busicorp.local/ |
This is equivalent to busicorp.local .
|
ldap://dc1.busicorp.local/ |
This is equivalent to dc1.busicorp.local .
|
ldap://172.14.30.22/ |
This is equivalent to 172.14.30.22 .
|
ldap://ldap1.openbook.edu:1389/ |
This example demonstrates that the port number can be specified in the bindstr. |
ldaps://dc1.busicorp.local/ |
If the scheme is ldaps:// , secure LDAPS transport on port 636 is used.
This type of bindstr will likely require importing a PKI certificiate for the target host.
Note that if a domain name were supplied instead of a specific hostname, PKI certificates would likely be required for all hosts with SRV records for the supplied domain.
|
ldap://busicorp.local/OU=Research,DC=busicorp,DC=local |
If a base DN is supplied, it will be used to constrain searches for accounts using getAccount and entries using getEntry and will be used as the default base when searching using the search method and used when constructing a DN given an RDN. For example, if this bindstr were used with the HttpSecurityService for authenticating users using the LdapSecurityProvider, only users within the Research OU container would be successfully authenticated whereas users in other containers would not be found. |
ldap://busicorp.local/DefaultNamingContext |
If the base in the bindstr is DefaultNamingContext, this indicates that the special AD DS defaultNamingContext attribute of the RootDSE should be first queried and then used as the base.
Meaning if the defaultNamingContext atttribute was DC=busicorp,DC=local, this example would be equivalent to a bindstr of ldap://busicorp.local/DC=busicorp,DC=local .
Note that this type of binding does NOT work with LDAP servers other than AD DS.
|
ldap://busicorp.local/RootDSE |
If the base in the bindstr is RootDSE, this indicates that the base should be the the speacial LDAP directory tree root known as the RootDSE.
Note that there is no DN corresponding to the RootDSE - it is simply an empty string.
Meaning the bindstr ldap://busicorp.local/ is equivalent to ldap://busicorp.local/RootDSE .
|
ldap://busicorp.local/CN=Users,DC=busicorp,DC=local?displayName,mail?sub?(sAMAccountName=abaker) |
This example illustrates most of the possible bindstr components. The attributes, scope and filter will be used as default values when performing searches such as with the search method. This is the type of bindstr that might be used with the jespa.ldap.LdapSearch utility. |
ldap://192.168.5.119/RootDSE?*,+ |
This bindstr demonstrates the special + attribute which some LDAP servers (OpenLDAP but not AD) require to retrieve many RootDSE specific attributes.
|
ldap://busicorp.local/DefaultNamingContext??sub?(sAMAccountName=abaker) |
This example retrieves all attrbutes of the account with the Windows username abaker. |
ldap://busicorp.local/CN=Users,DC=busicorp,DC=local?cn?sub?(sAMAccountName=h*) |
This example might be used with the jespa.ldap.LdapSearch utility to query all users with a Windows username begining with the letter h. |
ldap://busicorp.local/CN=Hans Müller,CN=Users,DC=busicorp,DC=local?tokenGroups?base |
In AD DS some attributes are "constructed". These attributes must be queried with a full DN using base scope. This example might be used with the jespa.ldap.LdapSearch utility to retrive the special constructed tokenGroups attribute containing an array of SIDs. |
ldap:///DC=busicorp,DC=local |
This bindstr is not valid. A domain or hostname is required. |
ldap://ldap2.openbook.edu/DC=openbook,DC=edu |
Just the hostname and base. |
Property Name | Description |
---|---|
ldap.disposition | If not set, AD DS server location behavior is used. Specifically, DNS SRV lookups including Microsoft specific _msdcs and _sites names will be used (unless the bindstr is an IP address). If this property value begins with "RFC", DNS SRV lookups will be used (unless the bindstr is an IP address) but without Microsoft specific names. |
dns.site | If dns.site is set, it will augment DNS SRV lookups to include the Active Directory Sites and Services "site" name. |
authority.dns.names.resolve | If set to false, the LdapSecurityProvider will not use DNS SRV lookups at all in which case the bindstr must be an FQDN hostname or IP address. |
authority.dns.names.resolve.sld | If set to true, the LdapSecurityProvider will try to resolve single-label DNS domain names. The default value is false because single-label domain names are depricated. |
dns.server.capabilities | If dns.server.capabilities is not set, SRV lookups including the Microsoft specific _msdcs and _sites will be used. |
These default values for these properties assume that Microsoft DNS and AD DS are being used.
For non-Microsoft environments dns.server.capabilites should be set to either 0x00
to disable SRV lookups entirely (which is the same as setting authority.dns.names.resolve to false) or 0x01
to use SRV lookups without the Microsoft specific _msdcs or _sites behavior.
The Jespa library has advanced support for Active Directory Sites and Services. AD Sites and Services is a feature of Microsoft DNS that adds an additional label to DNS SRV queries called a "site" and allows clients to query for only AD servers in close network proximity for a particular domain. Using this feature can be a significant performance improvement and may be required if some servers are firewalled from distant networks. To enable this feature in the Jespa library, simply set the dns.site property to the correct site name.
For example, if AD servers for the domain busicorp.local
are located in both Paris and Quebec, servers in Paris might be configured to be in the site named "Paris". If your Jespa installation is closer to Paris then Quebec, the operator should set dns.site = Paris
and then Jespa will only connect to servers in that location. Otherwise, without the dns.site property, Jespa may try to connect to servers at distant networks or to servers that are not accessible at all because of network filtering.
The LdapSecrurityProvider will attempt to connect to each LDAP server in the list returned by the DNS SRV lookup in order until it is successful. Once the LdapSecurityProvider has successfully established a connection with a suitable LDAP server, it will continue to use the same server. This feature is called "server stickyness".
Once the LdapSecurityProvider has established a connection with a particular LDAP server, it will persist the DNS hostname of that server as the authority.dns.name property so that it can transparently reconnect with the same server later if necessary. This feature is called "server stickyness" and using it may be very important when updating and querying attributes. Without it, querying data immediately after updating that data on a different server will likely yield incorrect or inconsistent results because the data will not have had time to replicate between servers.
It may be desirable to set or remove the authority.dns.name property to use or not use the same LDAP server. If the developer creates a new LdapSecurityProvider object and wants it to use the same AD server as another LdapSecurityProvider (or NtlmSecurityProvider) instance, they would copy the authority.dns.name property with a code fragment like:
secondProvider.setProperty("authority.dns.name", firstProvider.getProperty("authority.dns.name"));
This class does not implement transparent failover by itself because it cannot know if the operation being performed can be accommodated by any server in the domain (although components like the HttpSecurityService
do implement transparent failover).
If an LDAP connection is dropped and the LdapSecurityProvider fails to reestablish the connection with that particular LDAP server, such as because that server has been shut down, an exception will be thrown.
To retry this operation with another server, the developer should place the code of interest into a loop with logic to catch this type of exception, remove the authority.dns.name property and retry the operation.
This will cause the LdapSecurityProvider to re-evaluate the available servers.
This LdapSecurityProvider will authenticate with servers as necessary using a wide range of mechanisms including NTLM, "simple" binding or any other mechanism supported by the underlying InitialLdapContext such as Kerberos. Client binding and authentication behavior depends on the provided credential and mechanism selected which are determined as follows:
Otherwise, the authentication mechanism may be defined by the JNDI Context.SECURITY_AUTHENTICATION property. If the SECURITY_AUTHENTICATION property is not specified and the ldap.disposition property starts with "RFC", the mechanism defaults to "simple".
PasswordCredential
object (such as because the operation is being called through a PrivilegedExceptionAction), it will be retrieved and used with the mechanism selected as described above.
If the "simple" bind mechanism is selected (such as because the ldap.disposition begins with "RFC" or because it was specified explicitly using the SECURITY_AUTHENTICATION property), the connection should be protected with TLS or SSL (by either setting flags.confidentiality = true
or using an ldaps://
URL in the bindstr).
CN=Hans Müller,CN=Users,DC=busicorp,DC=local
Entries in an LDAP directory are origanized hierarchinally like a filesystem.
DNs uniquely identify each entry much like a filesystem path.
However, unlike a filesystem path, DN components are evaluated in reverse.
Meaning the above DN represents an entry in the container CN=Users,DC=busicorp,DC=local
which in turn is an entry in the container DC=busicorp,DC=local
.
A relative distinguished name (RDN) is a prefix of a DN relative to some base DN.
For example, the RDN CN=Hans Müller
and base DN CN=Users,DC=busicorp,DC=local
can be concatonated to form the example DN above.
An RDN is like a filesystem file name without the parent path.
Some constructors and methods will accept an RDN in place of a DN if a suitable base DN is supplied in the bindstr URL (described in The bindstr Property section below).
These include the getAccount(java.lang.String, java.lang.String[])
and getEntry(java.lang.Object, java.lang.String[])
methods and LdapEntry
and LdapAccount
constructors.
For example, consider the following code fragment:
HashMap props = new HashMap(); props.put("bindstr", "ldap://busicorp.local/"); ... LdapSecurityProvider provider = new LdapSecurityProvider(props); Account acct = provider.getAccount("CN=Hans Müller,CN=Users,DC=busicorp,DC=local", null);
is equivalent to:
HashMap props = new HashMap(); props.put("bindstr", "ldap://busicorp.local/CN=Users,DC=busicorp,DC=local"); ... LdapSecurityProvider provider = new LdapSecurityProvider(props); Account acct = provider.getAccount("CN=Hans Müller", null);
Special characters in DNs and RDNs may need to be escaped. Consider the following example which has a comma in the CN value. This comma must be escape with a backslash:
CN=Baker\, Alice,CN=Users,DC=busicorp,DC=local
Characters that must be escape in DN and RDN values are:
, = + < > # ; \ "
The escapeDnValue(java.lang.String)
method is provided to escape these characters in a String.
The below example illustrates how to use the JNDI PagedSearchControl in concert with the Jespa API. This combines an existing search control from the JNDI API with the simple interface and attribute conversions of the Jespa API.
LdapSecurityProvider p = new LdapSecurityProvider(props); try { String[] attrs = new String[] { "sAMAccountName" }; String filter = "(objectCategory=Person)"; InitialLdapContext ctx = (InitialLdapContext)p.getProperty("ldap.context", null); int pageSize = 20; Control[] ctls = new Control[] { new PagedResultsControl(pageSize, Control.CRITICAL) }; ctx.setRequestControls(ctls); byte[] cookie = null; do { List results = p.search(attrs, filter); System.out.println(results); ctls = ctx.getResponseControls(); if (ctls != null) { for (int ci = 0; ci < ctls.length; ci++) { if (ctls[ci] instanceof PagedResultsResponseControl) { PagedResultsResponseControl prrc = (PagedResultsResponseControl)ctls[ci]; cookie = prrc.getCookie(); break; } } ctls = new Control[] { new PagedResultsControl(pageSize, cookie, Control.CRITICAL) }; ctx.setRequestControls(ctls); } } while (cookie != null); } finally { p.dispose(); }
This example also illustrates how to use the LdapSecurityProvider to construct and retrieve an InitialLdapContext that will use NTLMv2 authentication and transport security with AD DS.
The following code fragment constructs an LdapSecurityProvider for an AD DS authority:
HashMap props = new HashMap(); props.put("bindstr", "ldap://busicorp.local/CN=users,DC=busicorp,DC=local"); props.put("service.acctname", "user1@busicorp.local"); props.put("service.password", "pass1"); props.put("dns.servers", "192.168.44.110,192.168.22.115"); props.put("dns.site", "Paris"); LdapSecurityProvider provider = new LdapSecurityProvider(props);
Some notes regarding the above code fragment are:
CN=Users,DC=busicorp,DC=local
was supplied in the bindstr, the getEntry, getAccount and isMemberOf methods will accept an RDN string like provider.getAccount("CN=Hans Müller", null)
whereas otherwise a full DN would be required like provider.getAccount("CN=Hans Müller,CN=Users,DC=busicorp,DC=local", null)
.
The following code fragment constructs an LdapSecurityProvider for an RFC-based authority such as OpenLDAP:
HashMap props = new HashMap(); props.put("bindstr", "ldap://ldap2.openbook.edu/OU=Sales,DC=openbook,DC=edu"); props.put("authority.dns.names.resolve", "false"); props.put("ldap.disposition", "RFC2251"); props.put("flags.confidentiality", "true"); props.put("service.acctname", "CN=user1,DC=openbook,DC=edu"); props.put("service.password", "pass1"); LdapSecurityProvider provider = new LdapSecurityProvider(props);
Some notes regarding the above code fragment are:
flags.confidentiality = true
is set to enable TLS (AKA SSLv2) encryption (or an ldaps:// URL could be used for SSL to port 636).
The following code fragment constructs an LdapSecurityProvider that will authenticate using GSSAPI (presumably Kerberos) with an AD DS:
HashMap props = new HashMap(); props.put("bindstr", "ldap://busicorp.local/cn=users,dc=busicorp,dc=local"); props.put("dns.servers", "192.168.44.110,192.168.22.115"); props.put("dns.site", "Paris"); props.put(Context.SECURITY_AUTHENTICATION, "GSSAPI"); LdapSecurityProvider lsp = new LdapSecurityProvider(props); try { InitialLdapContext ctx = (InitialLdapContext)lsp.getProperty("ldap.context", null); ctx.addToEnvironment("javax.security.sasl.qop", "auth-conf"); // or auth-int // use lsp } finally { lsp.dispose(); }
Some notes regarding the above code fragment are:
RunAs.runAs(PrivilegedExceptionAction, Object, String, Map)
method for one technique for acquiring this credential).
identity, isComplete
Constructor and Description |
---|
LdapSecurityProvider(java.util.Map properties)
Construct a new LdapSecurityProvider instance with a copy of the supplied properties.
|
LdapSecurityProvider(java.util.Map properties,
java.lang.String[] pnames)
Create an LdapSecurityProvider instance with a copy of the supplied properties but only properties named in the pnames array.
|
Modifier and Type | Method and Description |
---|---|
void |
authenticate(java.lang.Object credential)
Attempt to bind the target LDAP service using the supplied credentials.
|
static java.lang.String |
canonicalizeDn(java.lang.String dn)
Remove extraneous spaces from a DN or RDN.
|
protected java.lang.Object |
decodeObject(ByteBuffer bb)
Deserialize an LdapSecurityProvider, it's properties and any sub-objects that constitute it's state from the supplied buffer.
|
void |
dispose()
Remove the service.password.encrypted property and destroy any underlying InitialLdapContext.
|
protected void |
encodeObject(ByteBuffer bb,
java.lang.Object obj)
Serialize an LdapSecurityProvider, it's properties and any sub-objects (such as the LdapAccount) that constitute it's state into the supplied buffer.
|
static java.lang.String |
escapeDnValue(java.lang.String str)
Return the supplied string but with all characters reserved for DNs escaped as defined in RFC2253.
|
static java.lang.String |
escapeFilterValue(java.lang.String str)
Return the supplied string but with all characters reserved for LDAP filters escaped as defined in RFC2254.
|
java.lang.Object |
exportState()
Returns a compact byte[] array representing the state of this
security provider.
|
Account |
getAccount(java.lang.String acctname,
java.lang.String[] attrs)
Return an LdapAccount instance representing the named account.
|
java.util.Map |
getEntry(java.lang.Object key,
java.lang.String[] attrs)
Return an LdapEntry constructed of the named attributes for the entry identified by the key parameter.
|
java.lang.Object |
getProperty(java.lang.String name,
java.lang.Object def)
Retrieve a property by name or return the supplied default value if the
property is not set.
|
void |
importState(java.lang.Object ostate)
Initialize this security provider with a byte[] array returned in a
previous call to
exportState() with another instance of this security provider. |
static boolean |
parseDn(java.lang.String dn,
java.util.ArrayList keys,
java.util.ArrayList vals)
Parse an LDAP distinguished name (DN) or relative distinguished name (RDN) into keys and values.
|
java.lang.Object |
put(java.lang.Object name,
java.lang.Object value)
Set the named property to the supplied value.
|
java.util.List |
search(java.lang.String[] attrs,
java.lang.String filter)
Return a List of
LdapEntry objects for the supplied search parameters. |
acceptSecContext, getDomain, getFlag, getIdentity, getName, initSecContext, isComplete, setFlag, setProperty, unwrap, wrap
getEncryptedProperty, getFilteredProperties, getFilteredProperties, getProperty, getPropertyAsBoolean, getPropertyAsLong, setEncryptedProperty
clear, clone, compute, computeIfAbsent, computeIfPresent, containsKey, containsValue, entrySet, forEach, get, getOrDefault, isEmpty, keySet, merge, putAll, putIfAbsent, remove, remove, replace, replace, replaceAll, size, values
public LdapSecurityProvider(java.util.Map properties, java.lang.String[] pnames) throws SecurityProviderException
properties
- properties that control the behavior of this SecurityProviderSecurityProviderException
public LdapSecurityProvider(java.util.Map properties)
properties
- properties that control the behavior of this SecurityProviderpublic static java.lang.String escapeFilterValue(java.lang.String str)
Consider the following example:
String filter = "(&(objectCategory=person)(sAMAccountName=" + lsp.escapeFilterValue(username) + "))";
This method iterates through each character of the input string and replaces the characters * ( ) \ /
and \0
(nil) with the hexadecimal representation of that character preceeded with a backslash.
public static boolean parseDn(java.lang.String dn, java.util.ArrayList keys, java.util.ArrayList vals)
Consider the following example:
ArrayList keys = new ArrayList(); ArrayList vals = new ArrayList(); if (LdapSecurityProvider.parseDn("CN=Bob Carter,OU = Students,DC=openbook, DC=edu", keys, vals)) System.out.println("keys: " + keys + " vals: " + vals); -- output -- keys: [CN, OU, DC, DC] vals: [Bob Carter, Students, openbook, edu]
dn
- the input string to attempt to parse as a DN or RDNkeys
- if not null, all keys will be added to this listvals
- if not null, all values will be added to this listpublic static java.lang.String canonicalizeDn(java.lang.String dn)
Consider the following example:
Srring dn = LdapSecurityProvider.canonicalizeDn("CN=Bob Carter, OU=Students,DC = openbook,DC=edu "); if (dn == null) throw new Exception("Failed to parse DN: " + dn); System.out.println(dn); -- output -- CN=Bob Carter,OU=Students,DC=openbook,DC=edu
dn
- the DN or RDN to parse and canonicalizepublic static java.lang.String escapeDnValue(java.lang.String str)
Consider the following example:
String name = "Baker, Alice"; String dn = "CN=" + lsp.escapeDnValue(name) + "," + lsp.getProperty("ldap.base"); System.out.println(dn); -- output -- CN=Baker\, Alice,CN=Users,DC=busicorp,DC=local
This method iterates through each character of the input string and prefixes the following characters with a backslash: , = + < > # ; \ "
public void dispose() throws SecurityProviderException
dispose
in class SecurityProvider
SecurityProviderException
protected void encodeObject(ByteBuffer bb, java.lang.Object obj) throws EncodingException
exportState()
to convert this object into a byte[] array.
encodeObject
in class jespa.security.Properties
bb
- the buffer into which to serialize objobj
- the object to be serializedEncodingException
- if an error occurs trying to serialize an objectprotected java.lang.Object decodeObject(ByteBuffer bb) throws EncodingException
importState(java.lang.Object)
to convert a byte[] array into an Object of the appropriate type.
Developers should not need to use this method unless they extend this class and require that custom properties be imported by importState.decodeObject
in class jespa.security.Properties
bb
- the buffer from which to deserialize an ObjectEncodingException
- if an error occurs trying to deserialize an objectpublic java.lang.Object exportState() throws SecurityProviderException
exportState
in class SecurityProvider
SecurityProviderException
- if the state could not or cannot be
exported.public void importState(java.lang.Object ostate) throws SecurityProviderException
exportState()
with another instance of this security provider.
The security provider instance must be initialized with the same properties as the exported instance.
importState
in class SecurityProvider
ostate
- the byte[] array represeting the state
to be imported.SecurityProviderException
- if the state could not be imported such as
because the state being imported has been transferred beyond it's acceptable
boundries.public java.lang.Object put(java.lang.Object name, java.lang.Object value)
put
in interface java.util.Map
put
in class java.util.HashMap
public java.lang.Object getProperty(java.lang.String name, java.lang.Object def) throws SecurityProviderException
This method overrides the parent method to perform work specific to this provider including computing default values.
getProperty
in class SecurityProvider
name
- the name of the property to retrieve.def
- the default value to return of the named property is not set.SecurityProviderException
- if the name is invalid or it's value cannot
be retrieved.public java.util.List search(java.lang.String[] attrs, java.lang.String filter) throws SecurityProviderException
LdapEntry
objects for the supplied search parameters.
The behavior of an LDAP search is controlled primarily with four parameters - a base, a scope, a list of attribute names and a filter expression.
The scope and base of the search are defined in the LDAP URL supplied using the bindstr property whereas the attribute names and filter may be supplied using the attrs and filter parameters of this method.
RFC2251 is the definitive reference for LDAP search parameters but the below table summarizes the four parameters and how they must be supplied by the developer.
LdapSearch
utility.
LDAP Search Parameter | Description |
---|---|
Base |
The LDAP search base specifies the base DN of the search.
The significance of the base DN depends greatly on the "scope" described next.
This base DN is specified by the developer using the bindstr property.
For example, if the bindstr value is If no base is specified, the default is the RootDSE (an empty string). |
Scope |
The LDAP search scope is a string with a value of "sub", "base" or "one" which limits the depth of the search.
The default search scope is "sub" and indicates that all entries contained within the base DN or the base DN itself are eligible to be selected.
A scope of "base" indicates that only the entry identified by the base DN itself should be queried.
A scope of "one" indicates that one level of entries contained immediately within the entry identified by the base DN should be searched.
The bindstr property is used to specify the LDAP search scope.
For example, if the bindstr value is If no scope is specified, the default is "sub". |
Attributes |
The LDAP search attributes are a list of attribute names that indicate which attributes should be retrieved.
LDAP search attributes may be specified either with the attrs parameter of this method or with the bindstr property. See The bindstr Property section above for details about LDAP URLs and search parameters. If the attrs parameter is null, attributes in the LDAP URL specified with the bindstr will be used. If the bindstr also does not specify any attributes, "all attributes" will be retrieved. |
Filter |
The LDAP search filter is a search expression used by the LDAP server to select matching entries.
For example, a filter of (&(objectCategory=Person)(sAMAccountName=a*)) would select only entries with an objectCategory value of Person and a sAMAccountName value that begins with the letter a .
LDAP filter syntax and behavior is defined in RFC2254 but there are many Internet resources that informally cover the topic of LDAP filter expressions.
The LDAP search filter may be specified either with the filter parameter of this method or with the bindstr property. See The bindstr Property section above for details about LDAP URLs and search parameters.
If the filter property is null, the filter of the LDAP URL specified with the bindstr will be used. If the bindstr also does not specify a filter, the default filter is |
Consider the following example:
HashMap props = new HashMap(); props.put("bindstr", "ldap://busicorp.local/OU=Tést,DC=busicorp,DC=local??one"); props.put("service.acctname", "jas2stag@busicorp.local"); props.put("service.password", "moonbike69"); props.put("dns.servers", "192.168.44.110,192.168.22.115"); props.put("dns.site", "Paris"); LdapSecurityProvider lsp = new LdapSecurityProvider(props); try { String attrs = new String[] { "sAMAccountName", "objectSid" }; String filter = "(&(objectCategory=Person)(postalCode=" + lsp.escapeFilterValue(postalCode) + "))"; List results = lsp.search(attrs, filter); Iterator eiter = results.iterator(); while (eiter.hasNext()) { LdapEntry entry = (LdapEntry)eiter.next(); System.out.println(entry); } } finally { lsp.dispose(); }
The above example specifies a base of OU=Tést,DC=busicorp,DC=local
and a scope of "one" to indicate that only entries immediately under the base are eligible to be selected.
The attrs parameter indicates that only the sAMAccountName and objectSid of each entry should be retrieved.
The filter parameter is constructed so that only Person entries with a postalCode value that matches the externally supplied value should be selected.
This example also illustrates that it is important to use the escapeFilterValue(java.lang.String)
method to escape externally supplied text as it may contain characters reserved for filter expressions.
See the Mixing JNDI and Jespa LDAP APIs section above for another search example that uses the PagedResultsControl.
LdapSearch
utility like ldap://busicorp.local/CN=Bob Carter,CN=Users,DC=busicorp,DC=local?modifyTimeStamp?base
.
attrs
- the list of attributes to retrieve for the selected entriesfilter
- the RFC2254 style search expression that describes which entries should be selectedLdapEntry
objects representing the selected entiresSecurityProviderException
- if a catostrophic error has occured such as if "base" scope was specified but the corresponding entry does not exist or communication with a suitable LDAP server could not be establishedpublic java.util.Map getEntry(java.lang.Object key, java.lang.String[] attrs) throws SecurityProviderException
The attrs parameter is an array of names of attributes that should be retrieved.
If the attrs parameter is null, a default subset of attributes will be returned that is convenient or efficent for the implementation and server disposition.
If the attrs parameter is the special LdapEntry.ALL_ATTRS constant, all attributes of the entry will be retrieved.
The attrs parameter may be the expression new String[0]
to clearly indicate that the caller has no interest in the actual data (such as because the intent is only to delete the entry).
The implementation may retrieve additional attributes other than those requested.
If a requested attribute does not exist in the directory, it will simply not be present in the returned LdapEntry object.
Regardless of what attributes are requested and returned, LdapEntry objects will always have at least a distinguishedName attribute that is the full DN of the entry.
If the named account cannot be found, a SecurityProviderException will be thrown with an error code of SecurityProviderException.STATUS_ACCOUNT_NOT_FOUND.
jespa.security.SecurityProviderException: Entry key must be DN or RDN relative to base supplied in bindstr at jespa.ldap.LdapSecurityProvider.getLdapEntry(LdapSecurityProvider.java:1874) at jespa.ldap.LdapSecurityProvider.getEntry(LdapSecurityProvider.java:1903)
For example, this error would occur if a comma in an RDN was not escaped like CN=Baker\, Alice
.
LdapAttrDef
documentation for details.
To retrieve an account, use the getAccount(java.lang.String, java.lang.String[])
method instead.
key
- the DN of the entry to retrieve or the RDN relative to the base supplied in the bindstr propertyattrs
- a list of names of attributes to retrieveSecurityProviderException
public Account getAccount(java.lang.String acctname, java.lang.String[] attrs) throws SecurityProviderException
The accepted format of the acctname parameter depends greatly on the ldap.disposition, ldap.canonicalForm, domain.dns.name, domain.netbios.name, ldap.account.filter and ldap.account.attrname properties. If the acctname is in DN form (or RDN form and a base DN was supplied in the bindstr, the account will be queried directly. Otherwise, a search will retrieve the account data and return an LdapAccount object.
The attrs parameter is an array of names of attribute that should be retrieved. If the attrs parameter is null, a default subset of attributes will be returned that is convenient or efficent for the implementation and server disposition. If the attrs parameter is the special Account.ALL_ATTRS constant, all attributes of the account will be retrieved. All LdapAccount (and LdapEntry) objects will always have at least a distinguishedName attribute that is the full DN of the directory entry. The implementation may retrieve additional attributes other than those requested. If a requested attribute does not exist in the directory, it's value will simply not be present in the returned LdapAccount object.
If the named account cannot be found, a SecurityProviderException will be thrown with an error code of SecurityProviderException.STATUS_ACCOUNT_NOT_FOUND.
LdapAttrDef
documentation for details.
getAccount
in class SecurityProvider
acctname
- the name of the account to retrieveattrs
- an array of attribute names to retrieveSecurityProviderException
- if the named account could not be found or if a catostrophic error occurs.public void authenticate(java.lang.Object credential) throws SecurityProviderException
PasswordCredential
objects are supported.
The account name of the PasswordCredential may be in any form supported by the SecurityPrincipal
class provided that domain.dns.name and domain.netbios.name properties are supplied.
After calling authenticate, you should call PasswordCredential.destroy()
like the following example:
PasswordCredential pc = PasswordCredential(acctname, password.toCharArray()); LdapSecurityProvider lsp = new LdapSecurityProvider(properties); try { lsp.authenticate(pc); ... use lsp } finally { lsp.dispose(); pc.destroy(); }
This method can only be called once on each LdapSecurityProvider instance.
If this method successfully authenticates the supplied credentials, the identity will be set to the canonicalized account name and if getAccount(java.lang.String, java.lang.String[])
is called with a null account name, the authenticated user's LdapAccount
entry will be returned.
If the credentials are successfully authenticated and the ldap.authenticate.setcredential property is set to true, this method will also install the credentials in place of service.acctname and service.password (the password will be encrypted using the runtime key) so that the credentials may be used to impersonate the user when performing subsequent operations with the LdapSecurityProvider instance.
If the LdapSecurityProvider is not disposed, the impersonated credentials will persist across SecurityProvider.exportState()
/ SecurityProvider.importState(java.lang.Object)
calls (such as across HTTP calls through the HttpSecurityService).
getProperty("service.password")
.
The password is only encrypted so that it is not easily recovered from a log file or HTTP session store.
To minimize the chance of leaking credentials, call dispose()
as soon as the impersonating credentials are no longer required.
If impersonation is not required, do not set the ldap.authenticate.setcredential property at all.
authenticate
in class SecurityProvider
credential
- the credential to be validated.SecurityProviderException
- if the target LDAP service could not be bound.
If the supplied account name or password are incorrect, SecurityProviderException.getCode()
will return SecurityProviderException.STATUS_INVALID_CREDENTIALS
to indicate that the supplied account name was not found or that the password was incorrect.
An instance of LdapException will be thrown if an LDAP protocol error occurs.
For all other types of errors (such as a failure to locate a suitable LDAP server), a generic SecurityProviderException will be thrown.