1 /* 2 * Licensed to the Apache Software Foundation (ASF) under one 3 * or more contributor license agreements. See the NOTICE file 4 * distributed with this work for additional information 5 * regarding copyright ownership. The ASF licenses this file 6 * to you under the Apache License, Version 2.0 (the 7 * "License"); you may not use this file except in compliance 8 * with the License. You may obtain a copy of the License at 9 * 10 * http://www.apache.org/licenses/LICENSE-2.0 11 * 12 * Unless required by applicable law or agreed to in writing, 13 * software distributed under the License is distributed on an 14 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 15 * KIND, either express or implied. See the License for the 16 * specific language governing permissions and limitations 17 * under the License. 18 */ 19 package org.apache.shiro.realm.ldap; 20 21 import org.apache.shiro.util.StringUtils; 22 import org.slf4j.Logger; 23 import org.slf4j.LoggerFactory; 24 25 import javax.naming.AuthenticationException; 26 import javax.naming.Context; 27 import javax.naming.NamingException; 28 import javax.naming.ldap.InitialLdapContext; 29 import javax.naming.ldap.LdapContext; 30 import java.util.HashMap; 31 import java.util.Hashtable; 32 import java.util.Map; 33 34 /** 35 * {@link LdapContextFactory} implementation using the default Sun/Oracle JNDI Ldap API, utilizing JNDI 36 * environment properties and an {@link javax.naming.InitialContext}. 37 * <h2>Configuration</h2> 38 * This class basically wraps a default template JNDI environment properties Map. This properties map is the base 39 * configuration template used to acquire JNDI {@link LdapContext} connections at runtime. The 40 * {@link #getLdapContext(Object, Object)} method implementation merges this default template with other properties 41 * accessible at runtime only (for example per-method principals and credentials). The constructed runtime map is the 42 * one used to acquire the {@link LdapContext}. 43 * <p/> 44 * The template can be configured directly via the {@link #getEnvironment()}/{@link #setEnvironment(java.util.Map)} 45 * properties directly if necessary, but it is usually more convenient to use the supporting wrapper get/set methods 46 * for various environment properties. These wrapper methods interact with the environment 47 * template on your behalf, leaving your configuration cleaner and easier to understand. 48 * <p/> 49 * For example, consider the following two identical configurations: 50 * <pre> 51 * [main] 52 * ldapRealm = org.apache.shiro.realm.ldap.DefaultLdapRealm 53 * ldapRealm.contextFactory.url = ldap://localhost:389 54 * ldapRealm.contextFactory.authenticationMechanism = DIGEST-MD5 55 * </pre> 56 * and 57 * <pre> 58 * [main] 59 * ldapRealm = org.apache.shiro.realm.ldap.DefaultLdapRealm 60 * ldapRealm.contextFactory.environment[java.naming.provider.url] = ldap://localhost:389 61 * ldapRealm.contextFactory.environment[java.naming.security.authentication] = DIGEST-MD5 62 * </pre> 63 * As you can see, the 2nd configuration block is a little more difficult to read and also requires knowledge 64 * of the underlying JNDI Context property keys. The first is easier to read and understand. 65 * <p/> 66 * Note that occasionally it will be necessary to use the latter configuration style to set environment properties 67 * where no corresponding wrapper method exists. In this case, the hybrid approach is still a little easier to read. 68 * For example: 69 * <pre> 70 * [main] 71 * ldapRealm = org.apache.shiro.realm.ldap.DefaultLdapRealm 72 * ldapRealm.contextFactory.url = ldap://localhost:389 73 * ldapRealm.contextFactory.authenticationMechanism = DIGEST-MD5 74 * ldapRealm.contextFactory.environment[some.other.obscure.jndi.key] = some value 75 * </pre> 76 * 77 * @since 1.1 78 */ 79 public class JndiLdapContextFactory implements LdapContextFactory { 80 81 /*------------------------------------------- 82 | C O N S T A N T S | 83 ===========================================*/ 84 /** 85 * The Sun LDAP property used to enable connection pooling. This is used in the default implementation 86 * to enable LDAP connection pooling. 87 */ 88 protected static final String SUN_CONNECTION_POOLING_PROPERTY = "com.sun.jndi.ldap.connect.pool"; 89 protected static final String DEFAULT_CONTEXT_FACTORY_CLASS_NAME = "com.sun.jndi.ldap.LdapCtxFactory"; 90 protected static final String SIMPLE_AUTHENTICATION_MECHANISM_NAME = "simple"; 91 protected static final String DEFAULT_REFERRAL = "follow"; 92 93 private static final Logger log = LoggerFactory.getLogger(JndiLdapContextFactory.class); 94 95 /*------------------------------------------- 96 | I N S T A N C E V A R I A B L E S | 97 ============================================*/ 98 private Map<String, Object> environment; 99 private boolean poolingEnabled; 100 private String systemPassword; 101 private String systemUsername; 102 103 /*------------------------------------------- 104 | C O N S T R U C T O R S | 105 ===========================================*/ 106 107 /** 108 * Default no-argument constructor that initializes the backing {@link #getEnvironment() environment template} with 109 * the {@link #setContextFactoryClassName(String) contextFactoryClassName} equal to 110 * {@code com.sun.jndi.ldap.LdapCtxFactory} (the Sun/Oracle default) and the default 111 * {@link #setReferral(String) referral} behavior to {@code follow}. 112 */ 113 public JndiLdapContextFactory() { 114 this.environment = new HashMap<String, Object>(); 115 setContextFactoryClassName(DEFAULT_CONTEXT_FACTORY_CLASS_NAME); 116 setReferral(DEFAULT_REFERRAL); 117 poolingEnabled = true; 118 } 119 120 /*------------------------------------------- 121 | A C C E S S O R S / M O D I F I E R S | 122 ===========================================*/ 123 124 /** 125 * Sets the type of LDAP authentication mechanism to use when connecting to the LDAP server. 126 * This is a wrapper method for setting the JNDI {@link #getEnvironment() environment template}'s 127 * {@link Context#SECURITY_AUTHENTICATION} property. 128 * <p/> 129 * "none" (i.e. anonymous) and "simple" authentications are supported automatically and don't need to be configured 130 * via this property. However, if you require a different mechanism, such as a SASL or External mechanism, you 131 * must configure that explicitly via this property. See the 132 * <a href="http://download-llnw.oracle.com/javase/tutorial/jndi/ldap/auth_mechs.html">JNDI LDAP 133 * Authentication Mechanisms</a> for more information. 134 * 135 * @param authenticationMechanism the type of LDAP authentication to perform. 136 * @see <a href="http://download-llnw.oracle.com/javase/tutorial/jndi/ldap/auth_mechs.html"> 137 * http://download-llnw.oracle.com/javase/tutorial/jndi/ldap/auth_mechs.html</a> 138 */ 139 public void setAuthenticationMechanism(String authenticationMechanism) { 140 setEnvironmentProperty(Context.SECURITY_AUTHENTICATION, authenticationMechanism); 141 } 142 143 /** 144 * Returns the type of LDAP authentication mechanism to use when connecting to the LDAP server. 145 * This is a wrapper method for getting the JNDI {@link #getEnvironment() environment template}'s 146 * {@link Context#SECURITY_AUTHENTICATION} property. 147 * <p/> 148 * If this property remains un-configured (i.e. {@code null} indicating the 149 * {@link #setAuthenticationMechanism(String)} method wasn't used), this indicates that the default JNDI 150 * "none" (anonymous) and "simple" authentications are supported automatically. Any non-null value returned 151 * represents an explicitly configured mechanism (e.g. a SASL or external mechanism). See the 152 * <a href="http://download-llnw.oracle.com/javase/tutorial/jndi/ldap/auth_mechs.html">JNDI LDAP 153 * Authentication Mechanisms</a> for more information. 154 * 155 * @return the type of LDAP authentication mechanism to use when connecting to the LDAP server. 156 * @see <a href="http://download-llnw.oracle.com/javase/tutorial/jndi/ldap/auth_mechs.html"> 157 * http://download-llnw.oracle.com/javase/tutorial/jndi/ldap/auth_mechs.html</a> 158 */ 159 public String getAuthenticationMechanism() { 160 return (String) getEnvironmentProperty(Context.SECURITY_AUTHENTICATION); 161 } 162 163 /** 164 * The name of the ContextFactory class to use. This defaults to the SUN LDAP JNDI implementation 165 * but can be overridden to use custom LDAP factories. 166 * <p/> 167 * This is a wrapper method for setting the JNDI environment's {@link Context#INITIAL_CONTEXT_FACTORY} property. 168 * 169 * @param contextFactoryClassName the context factory that should be used. 170 */ 171 public void setContextFactoryClassName(String contextFactoryClassName) { 172 setEnvironmentProperty(Context.INITIAL_CONTEXT_FACTORY, contextFactoryClassName); 173 } 174 175 /** 176 * Sets the name of the ContextFactory class to use. This defaults to the SUN LDAP JNDI implementation 177 * but can be overridden to use custom LDAP factories. 178 * <p/> 179 * This is a wrapper method for getting the JNDI environment's {@link Context#INITIAL_CONTEXT_FACTORY} property. 180 * 181 * @return the name of the ContextFactory class to use. 182 */ 183 public String getContextFactoryClassName() { 184 return (String) getEnvironmentProperty(Context.INITIAL_CONTEXT_FACTORY); 185 } 186 187 /** 188 * Returns the base JNDI environment template to use when acquiring an LDAP connection (an {@link LdapContext}). 189 * This property is the base configuration template to use for all connections. This template is then 190 * merged with appropriate runtime values as necessary in the 191 * {@link #getLdapContext(Object, Object)} implementation. The merged environment instance is what is used to 192 * acquire the {@link LdapContext} at runtime. 193 * <p/> 194 * Most other get/set methods in this class act as thin proxy wrappers that interact with this property. The 195 * benefit of using them is you have an easier-to-use configuration mechanism compared to setting map properties 196 * based on JNDI context keys. 197 * 198 * @return the base JNDI environment template to use when acquiring an LDAP connection (an {@link LdapContext}) 199 */ 200 public Map getEnvironment() { 201 return this.environment; 202 } 203 204 /** 205 * Sets the base JNDI environment template to use when acquiring LDAP connections. It is typically more common 206 * to use the other get/set methods in this class to set individual environment settings rather than use 207 * this method, but it is available for advanced users that want full control over the base JNDI environment 208 * settings. 209 * <p/> 210 * Note that this template only represents the base/default environment settings. It is then merged with 211 * appropriate runtime values as necessary in the {@link #getLdapContext(Object, Object)} implementation. 212 * The merged environment instance is what is used to acquire the connection ({@link LdapContext}) at runtime. 213 * 214 * @param env the base JNDI environment template to use when acquiring LDAP connections. 215 */ 216 @SuppressWarnings({"unchecked"}) 217 public void setEnvironment(Map env) { 218 this.environment = env; 219 } 220 221 /** 222 * Returns the environment property value bound under the specified key. 223 * 224 * @param name the name of the environment property 225 * @return the property value or {@code null} if the value has not been set. 226 */ 227 private Object getEnvironmentProperty(String name) { 228 return this.environment.get(name); 229 } 230 231 /** 232 * Will apply the value to the environment attribute if and only if the value is not null or empty. If it is 233 * null or empty, the corresponding environment attribute will be removed. 234 * 235 * @param name the environment property key 236 * @param value the environment property value. A null/empty value will trigger removal. 237 */ 238 private void setEnvironmentProperty(String name, String value) { 239 if (StringUtils.hasText(value)) { 240 this.environment.put(name, value); 241 } else { 242 this.environment.remove(name); 243 } 244 } 245 246 /** 247 * Returns whether or not connection pooling should be used when possible and appropriate. This property is NOT 248 * backed by the {@link #getEnvironment() environment template} like most other properties in this class. It 249 * is a flag to indicate that pooling is preferred. The default value is {@code true}. 250 * <p/> 251 * However, pooling will only actually be enabled if this property is {@code true} <em>and</em> the connection 252 * being created is for the {@link #getSystemUsername() systemUsername} user. Connection pooling is not used for 253 * general authentication attempts by application end-users because the probability of re-use for that same 254 * user-specific connection after an authentication attempt is extremely low. 255 * <p/> 256 * If this attribute is {@code true} and it has been determined that the connection is being made with the 257 * {@link #getSystemUsername() systemUsername}, the 258 * {@link #getLdapContext(Object, Object)} implementation will set the Sun/Oracle-specific 259 * {@code com.sun.jndi.ldap.connect.pool} environment property to "{@code true}". This means setting 260 * this property is only likely to work if using the Sun/Oracle default context factory class (i.e. not using 261 * a custom {@link #getContextFactoryClassName() contextFactoryClassName}). 262 * 263 * @return whether or not connection pooling should be used when possible and appropriate 264 */ 265 public boolean isPoolingEnabled() { 266 return poolingEnabled; 267 } 268 269 /** 270 * Sets whether or not connection pooling should be used when possible and appropriate. This property is NOT 271 * a wrapper to the {@link #getEnvironment() environment template} like most other properties in this class. It 272 * is a flag to indicate that pooling is preferred. The default value is {@code true}. 273 * <p/> 274 * However, pooling will only actually be enabled if this property is {@code true} <em>and</em> the connection 275 * being created is for the {@link #getSystemUsername() systemUsername} user. Connection pooling is not used for 276 * general authentication attempts by application end-users because the probability of re-use for that same 277 * user-specific connection after an authentication attempt is extremely low. 278 * <p/> 279 * If this attribute is {@code true} and it has been determined that the connection is being made with the 280 * {@link #getSystemUsername() systemUsername}, the 281 * {@link #getLdapContext(Object, Object)} implementation will set the Sun/Oracle-specific 282 * {@code com.sun.jndi.ldap.connect.pool} environment property to "{@code true}". This means setting 283 * this property is only likely to work if using the Sun/Oracle default context factory class (i.e. not using 284 * a custom {@link #getContextFactoryClassName() contextFactoryClassName}). 285 * 286 * @param poolingEnabled whether or not connection pooling should be used when possible and appropriate 287 */ 288 public void setPoolingEnabled(boolean poolingEnabled) { 289 this.poolingEnabled = poolingEnabled; 290 } 291 292 /** 293 * Sets the LDAP referral behavior when creating a connection. Defaults to {@code follow}. See the Sun/Oracle LDAP 294 * <a href="http://java.sun.com/products/jndi/tutorial/ldap/referral/jndi.html">referral documentation</a> for more. 295 * 296 * @param referral the referral property. 297 * @see <a href="http://java.sun.com/products/jndi/tutorial/ldap/referral/jndi.html">Referrals in JNDI</a> 298 */ 299 public void setReferral(String referral) { 300 setEnvironmentProperty(Context.REFERRAL, referral); 301 } 302 303 /** 304 * Returns the LDAP referral behavior when creating a connection. Defaults to {@code follow}. 305 * See the Sun/Oracle LDAP 306 * <a href="http://java.sun.com/products/jndi/tutorial/ldap/referral/jndi.html">referral documentation</a> for more. 307 * 308 * @return the LDAP referral behavior when creating a connection. 309 * @see <a href="http://java.sun.com/products/jndi/tutorial/ldap/referral/jndi.html">Referrals in JNDI</a> 310 */ 311 public String getReferral() { 312 return (String) getEnvironmentProperty(Context.REFERRAL); 313 } 314 315 /** 316 * The LDAP url to connect to. (e.g. ldap://<ldapDirectoryHostname>:<port>). This must be configured. 317 * 318 * @param url the LDAP url to connect to. (e.g. ldap://<ldapDirectoryHostname>:<port>) 319 */ 320 public void setUrl(String url) { 321 setEnvironmentProperty(Context.PROVIDER_URL, url); 322 } 323 324 /** 325 * Returns the LDAP url to connect to. (e.g. ldap://<ldapDirectoryHostname>:<port>). 326 * This must be configured. 327 * 328 * @return the LDAP url to connect to. (e.g. ldap://<ldapDirectoryHostname>:<port>) 329 */ 330 public String getUrl() { 331 return (String) getEnvironmentProperty(Context.PROVIDER_URL); 332 } 333 334 /** 335 * Sets the password of the {@link #setSystemUsername(String) systemUsername} that will be used when creating an 336 * LDAP connection used for authorization queries. 337 * <p/> 338 * Note that setting this property is not required if the calling LDAP Realm does not perform authorization 339 * checks. 340 * 341 * @param systemPassword the password of the {@link #setSystemUsername(String) systemUsername} that will be used 342 * when creating an LDAP connection used for authorization queries. 343 */ 344 public void setSystemPassword(String systemPassword) { 345 this.systemPassword = systemPassword; 346 } 347 348 /** 349 * Returns the password of the {@link #setSystemUsername(String) systemUsername} that will be used when creating an 350 * LDAP connection used for authorization queries. 351 * <p/> 352 * Note that setting this property is not required if the calling LDAP Realm does not perform authorization 353 * checks. 354 * 355 * @return the password of the {@link #setSystemUsername(String) systemUsername} that will be used when creating an 356 * LDAP connection used for authorization queries. 357 */ 358 public String getSystemPassword() { 359 return this.systemPassword; 360 } 361 362 /** 363 * Sets the system username that will be used when creating an LDAP connection used for authorization queries. 364 * The user must have the ability to query for authorization data for any application user. 365 * <p/> 366 * Note that setting this property is not required if the calling LDAP Realm does not perform authorization 367 * checks. 368 * 369 * @param systemUsername the system username that will be used when creating an LDAP connection used for 370 * authorization queries. 371 */ 372 public void setSystemUsername(String systemUsername) { 373 this.systemUsername = systemUsername; 374 } 375 376 /** 377 * Returns the system username that will be used when creating an LDAP connection used for authorization queries. 378 * The user must have the ability to query for authorization data for any application user. 379 * <p/> 380 * Note that setting this property is not required if the calling LDAP Realm does not perform authorization 381 * checks. 382 * 383 * @return the system username that will be used when creating an LDAP connection used for authorization queries. 384 */ 385 public String getSystemUsername() { 386 return systemUsername; 387 } 388 389 /*-------------------------------------------- 390 | M E T H O D S | 391 ============================================*/ 392 393 /** 394 * This implementation delegates to {@link #getLdapContext(Object, Object)} using the 395 * {@link #getSystemUsername() systemUsername} and {@link #getSystemPassword() systemPassword} properties as 396 * arguments. 397 * 398 * @return the system LdapContext 399 * @throws NamingException if there is a problem connecting to the LDAP directory 400 */ 401 public LdapContext getSystemLdapContext() throws NamingException { 402 return getLdapContext((Object)getSystemUsername(), getSystemPassword()); 403 } 404 405 /** 406 * Deprecated - use {@link #getLdapContext(Object, Object)} instead. This will be removed before Apache Shiro 2.0. 407 * 408 * @param username the username to use when creating the connection. 409 * @param password the password to use when creating the connection. 410 * @return a {@code LdapContext} bound using the given username and password. 411 * @throws javax.naming.NamingException if there is an error creating the context. 412 * @deprecated the {@link #getLdapContext(Object, Object)} method should be used in all cases to ensure more than 413 * String principals and credentials can be used. Shiro no longer calls this method - it will be 414 * removed before the 2.0 release. 415 */ 416 @Deprecated 417 public LdapContext getLdapContext(String username, String password) throws NamingException { 418 return getLdapContext((Object) username, password); 419 } 420 421 /** 422 * Returns {@code true} if LDAP connection pooling should be used when acquiring a connection based on the specified 423 * account principal, {@code false} otherwise. 424 * <p/> 425 * This implementation returns {@code true} only if {@link #isPoolingEnabled()} and the principal equals the 426 * {@link #getSystemUsername()}. The reasoning behind this is that connection pooling is not desirable for 427 * general authentication attempts by application end-users because the probability of re-use for that same 428 * user-specific connection after an authentication attempt is extremely low. 429 * 430 * @param principal the principal under which the connection will be made 431 * @return {@code true} if LDAP connection pooling should be used when acquiring a connection based on the specified 432 * account principal, {@code false} otherwise. 433 */ 434 protected boolean isPoolingConnections(Object principal) { 435 return isPoolingEnabled() && principal != null && principal.equals(getSystemUsername()); 436 } 437 438 /** 439 * This implementation returns an LdapContext based on the configured JNDI/LDAP environment configuration. 440 * The environnmet (Map) used at runtime is created by merging the default/configured 441 * {@link #getEnvironment() environment template} with some runtime values as necessary (e.g. a principal and 442 * credential available at runtime only). 443 * <p/> 444 * After the merged Map instance is created, the LdapContext connection is 445 * {@link #createLdapContext(java.util.Hashtable) created} and returned. 446 * 447 * @param principal the principal to use when acquiring a connection to the LDAP directory 448 * @param credentials the credentials (password, X.509 certificate, etc) to use when acquiring a connection to the 449 * LDAP directory 450 * @return the acquired {@code LdapContext} connection bound using the specified principal and credentials. 451 * @throws NamingException 452 * @throws IllegalStateException 453 */ 454 public LdapContext getLdapContext(Object principal, Object credentials) throws NamingException, 455 IllegalStateException { 456 457 String url = getUrl(); 458 if (url == null) { 459 throw new IllegalStateException("An LDAP URL must be specified of the form ldap://<hostname>:<port>"); 460 } 461 462 //copy the environment template into the runtime instance that will be further edited based on 463 //the method arguments and other class attributes. 464 Hashtable<String, Object> env = new Hashtable<String, Object>(this.environment); 465 466 Object authcMech = getAuthenticationMechanism(); 467 if (authcMech == null && (principal != null || credentials != null)) { 468 //authenticationMechanism has not been set, but either a principal and/or credentials were 469 //supplied, indicating that at least a 'simple' authentication attempt is indeed occurring - the Shiro 470 //end-user just didn't configure it explicitly. So we set it to be 'simple' here as a convenience; 471 //the Sun provider implementation already does this same logic, but by repeating that logic here, we ensure 472 //this convenience exists regardless of provider implementation): 473 env.put(Context.SECURITY_AUTHENTICATION, SIMPLE_AUTHENTICATION_MECHANISM_NAME); 474 } 475 if (principal != null) { 476 env.put(Context.SECURITY_PRINCIPAL, principal); 477 } 478 if (credentials != null) { 479 env.put(Context.SECURITY_CREDENTIALS, credentials); 480 } 481 482 boolean pooling = isPoolingConnections(principal); 483 if (pooling) { 484 env.put(SUN_CONNECTION_POOLING_PROPERTY, "true"); 485 } 486 487 if (log.isDebugEnabled()) { 488 log.debug("Initializing LDAP context using URL [{}] and principal [{}] with pooling {}", 489 new Object[]{url, principal, (pooling ? "enabled" : "disabled")}); 490 } 491 492 // validate the config before creating the context 493 validateAuthenticationInfo(env); 494 495 return createLdapContext(env); 496 } 497 498 /** 499 * Creates and returns a new {@link javax.naming.ldap.InitialLdapContext} instance. This method exists primarily 500 * to support testing where a mock LdapContext can be returned instead of actually creating a connection, but 501 * subclasses are free to provide a different implementation if necessary. 502 * 503 * @param env the JNDI environment settings used to create the LDAP connection 504 * @return an LdapConnection 505 * @throws NamingException if a problem occurs creating the connection 506 */ 507 protected LdapContext createLdapContext(Hashtable env) throws NamingException { 508 return new InitialLdapContext(env, null); 509 } 510 511 512 /** 513 * Validates the configuration in the JNDI <code>environment</code> settings and throws an exception if a problem 514 * exists. 515 * <p/> 516 * This implementation will throw a {@link AuthenticationException} if the authentication mechanism is set to 517 * 'simple', the principal is non-empty, and the credentials are empty (as per 518 * <a href="http://tools.ietf.org/html/rfc4513#section-5.1.2">rfc4513 section-5.1.2</a>). 519 * 520 * @param environment the JNDI environment settings to be validated 521 * @throws AuthenticationException if a configuration problem is detected 522 */ 523 protected void validateAuthenticationInfo(Hashtable<String, Object> environment) 524 throws AuthenticationException 525 { 526 // validate when using Simple auth both principal and credentials are set 527 if(SIMPLE_AUTHENTICATION_MECHANISM_NAME.equals(environment.get(Context.SECURITY_AUTHENTICATION))) { 528 529 // only validate credentials if we have a non-empty principal 530 if( environment.get(Context.SECURITY_PRINCIPAL) != null && 531 StringUtils.hasText( String.valueOf( environment.get(Context.SECURITY_PRINCIPAL) ))) { 532 533 Object credentials = environment.get(Context.SECURITY_CREDENTIALS); 534 535 // from the FAQ, we need to check for empty credentials: 536 // http://docs.oracle.com/javase/tutorial/jndi/ldap/faq.html 537 if( credentials == null || 538 (credentials instanceof byte[] && ((byte[])credentials).length <= 0) || // empty byte[] 539 (credentials instanceof char[] && ((char[])credentials).length <= 0) || // empty char[] 540 (String.class.isInstance(credentials) && !StringUtils.hasText(String.valueOf(credentials)))) { 541 542 throw new javax.naming.AuthenticationException("LDAP Simple authentication requires both a " 543 + "principal and credentials."); 544 } 545 } 546 } 547 } 548 549 }