2016-12-20 3 views
0

提供された資格情報を使用してDirContextを作成できます。だから、私はLDAPサーバーに接続していると信任状を確認しているようですが、後で私たちはこれらの信任状から得られるコンテキストで.searchを行います。ここでそれは失敗しています。私は私の春のセキュリティ設定に加えて、私はどのように証明書が動作していることを示し、コードは失敗しているように見えるコードを表示しています。 ActiveDirectoryLdapAuthenticationProviderSpringSecurity Active Directoryは認証されていますが、例外をスローしてユーザーデータを取得します

@Override 
    protected DirContextOperations doAuthentication(UsernamePasswordAuthenticationToken auth) { 
    String username = auth.getName(); 
    String password = (String)auth.getCredentials(); 

    DirContext ctx = bindAsUser(username, password); 

    try { 
     return searchForUser(ctx, username); 

    } catch (NamingException e) { 
     logger.error("Failed to locate directory entry for authenticated user: " + username, e); 
     throw badCredentials(); 
    } finally { 
     LdapUtils.closeContext(ctx); 
    } 
    } 

から

春・セキュリティ設定

<?xml version="1.0" encoding="UTF-8"?> 
<beans:beans xmlns="http://www.springframework.org/schema/security" 
    xmlns:beans="http://www.springframework.org/schema/beans" 
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
    xmlns:p="http://www.springframework.org/schema/p" 
    xsi:schemaLocation="http://www.springframework.org/schema/beans 
       http://www.springframework.org/schema/beans/spring-beans-3.0.xsd 
       http://www.springframework.org/schema/security 
       http://www.springframework.org/schema/security/spring-security-3.1.xsd"> 

    <http pattern="/ui/login" security="none"></http> 
    <http pattern="/styles" security="none"/> 


     <http use-expressions="true"> 



      <intercept-url pattern="/views/*" access="isAuthenticated()" /> 
      <intercept-url pattern="/database/upload" access="isAuthenticated()" /> 
      <intercept-url pattern="/database/save" access="isAuthenticated()" /> 
      <intercept-url pattern="/database/list" access="isAuthenticated()" /> 
      <intercept-url pattern="/database/delete" access="isAuthenticated()" /> 

      <intercept-url pattern="/project/*" access="isAuthenticated()" /> 
      <intercept-url pattern="/file/*" access="isAuthenticated()" /> 
      <intercept-url pattern="/amazon/*" access="isAuthenticated()" /> 
      <intercept-url pattern="/python/*" access="isAuthenticated()" /> 
      <intercept-url pattern="/r/*" access="isAuthenticated()" /> 
      <intercept-url pattern="/project/*" access="isAuthenticated()" /> 
      <intercept-url pattern="/image/*" access="isAuthenticated()" /> 
      <intercept-url pattern="/shell/*" access="isAuthenticated()" /> 

      <intercept-url pattern="/register" access="hasRole('ROLE_ADMIN')" /> 
      <intercept-url pattern="/user/save" access="hasRole('ROLE_ADMIN')" /> 
      <intercept-url pattern="/user/userAdministrator" access="hasRole('ROLE_ADMIN')" />   
      <intercept-url pattern="/user/list" access="isAuthenticated()" /> 
      <intercept-url pattern="/user/archive" access="isAuthenticated()" /> 




    <form-login login-page="/login" default-target-url="/views/main" 
      authentication-failure-url="/loginfailed"/> 
     <logout logout-success-url="/login" /> 


    </http> 



<beans:bean id="ldapAuthProvider" 
class="org.springframework.security.ldap.authentication.ad.ActiveDirectoryLdapAuthenticationProvider"> 

<beans:constructor-arg value="simplead.blazingdb.com" /> 
<beans:constructor-arg value="ldap://simplead.blazingdb.com/" /> 

</beans:bean> 

<authentication-manager alias="authenticationManager" erase-credentials="true"> 
<authentication-provider ref="ldapAuthProvider"> 
</authentication-provider> 
</authentication-manager> 




</beans:beans> 

これはあまりにも長い間、私は正しい資格情報を渡すよううまく返し、私は間違った資格情報を送信する場合ので、私は、我々知って失敗しますこれまでこれを作っている。

問題は、私は問題を見ていますどこ具体的に私が考える次の行がある

public static DirContextOperations searchForSingleEntryInternal(DirContext ctx, SearchControls searchControls, 
     String base, String filter, Object[] params) throws NamingException { 
    final DistinguishedName ctxBaseDn = new DistinguishedName(ctx.getNameInNamespace()); 
    final DistinguishedName searchBaseDn = new DistinguishedName(base); 
    final NamingEnumeration<SearchResult> resultsEnum = ctx.search(searchBaseDn, filter, params, searchControls); 

    if (logger.isDebugEnabled()) { 
     logger.debug("Searching for entry under DN '" + ctxBaseDn 
       + "', base = '" + searchBaseDn + "', filter = '" + filter + "'"); 
    } 

    Set<DirContextOperations> results = new HashSet<DirContextOperations>(); 
    try { 
     while (resultsEnum.hasMore()) { 
      SearchResult searchResult = resultsEnum.next(); 
      // Work out the DN of the matched entry 
      DistinguishedName dn = new DistinguishedName(new CompositeName(searchResult.getName())); 

      if (base.length() > 0) { 
       dn.prepend(searchBaseDn); 
      } 

      if (logger.isDebugEnabled()) { 
       logger.debug("Found DN: " + dn); 
      } 
      results.add(new DirContextAdapter(searchResult.getAttributes(), dn, ctxBaseDn)); 
     } 
    } catch (PartialResultException e) { 
     LdapUtils.closeEnumeration(resultsEnum); 
     logger.info("Ignoring PartialResultException"); 
    } 

    if (results.size() == 0) { 
     throw new IncorrectResultSizeDataAccessException(1, 0); 
    } 

    if (results.size() > 1) { 
     throw new IncorrectResultSizeDataAccessException(1, results.size()); 
    } 

    return results.iterator().next(); 
    } 

SpringSecurityLdapTemplate

の内側に来ます。 1を期待しているときにサイズ0の戻り値が返されるので、エラーが発生し、すべてが失敗します。

final NamingEnumeration<SearchResult> resultsEnum = ctx.search(searchBaseDn, filter, params, searchControls); 

私たちはresultsEnum.hasMore()を実行しようとするたびに、PartialResultsExceptionをキャッチします。

私は、なぜこれが当てはまるかを考えようとしています。私はAmazon Simpleディレクトリサービス(MSFTバージョンではなくSambaによってサポートされているもの)を使用しています。私はLDAPとActive Directoryを非常に新しくしています。もし私の質問がうまく形成されていない場合は、追加する必要のある情報を教えてください。

答えて

0

私がApache Directory Studioを使用して、Spring Security Active Directoryのデフォルトから出てくるldapクエリを実行しようとすると、問題はかなり簡単でした。彼らはあなたがsAMAccountNameとドメインの組み合わせであるuserPrincipalNameという属性を持っていると仮定します。

最後に、sAMAccountNameを使用するようにsearchFilterを設定しなければならず、使用されているドメイン内のユーザーのみを検索してsAMAccountNameを比較しているActiveDirectoryLdapAuthenticationProviderの独自のバージョンを作成しなければなりませんでした。私はsearchForUserだけを変更しましたが、これは最終的なクラスだったので、私はそれをコピーしなければなりませんでした。私はこれをする必要はありませんが、私は動かなければなりません。これらはSpring Security 3.2.9の設定可能なオプションではありません。

パッケージorg.springframework.security.ldap.authentication.ad;

import org.springframework.dao.IncorrectResultSizeDataAccessException; 
import org.springframework.ldap.core.DirContextOperations; 
import org.springframework.ldap.core.DistinguishedName; 
import org.springframework.ldap.core.support.DefaultDirObjectFactory; 
import org.springframework.ldap.support.LdapUtils; 
import org.springframework.security.authentication.AccountExpiredException; 
import org.springframework.security.authentication.BadCredentialsException; 
import org.springframework.security.authentication.CredentialsExpiredException; 
import org.springframework.security.authentication.DisabledException; 
import org.springframework.security.authentication.LockedException; 
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; 
import org.springframework.security.core.GrantedAuthority; 
import org.springframework.security.core.authority.AuthorityUtils; 
import org.springframework.security.core.authority.SimpleGrantedAuthority; 
import org.springframework.security.core.userdetails.UsernameNotFoundException; 
import org.springframework.security.ldap.SpringSecurityLdapTemplate; 
import org.springframework.security.ldap.authentication.AbstractLdapAuthenticationProvider; 
import org.springframework.security.ldap.authentication.ad.ActiveDirectoryAuthenticationException; 
import org.springframework.util.Assert; 
import org.springframework.util.StringUtils; 

import javax.naming.AuthenticationException; 
import javax.naming.Context; 
import javax.naming.NamingException; 
import javax.naming.OperationNotSupportedException; 
import javax.naming.directory.DirContext; 
import javax.naming.directory.SearchControls; 
import javax.naming.ldap.InitialLdapContext; 
import java.util.*; 
import java.util.regex.Matcher; 
import java.util.regex.Pattern; 



public final class BlazingActiveDirectory extends AbstractLdapAuthenticationProvider { 
    private static final Pattern SUB_ERROR_CODE = Pattern.compile(".*data\\s([0-9a-f]{3,4}).*"); 

    // Error codes 
    private static final int USERNAME_NOT_FOUND = 0x525; 
    private static final int INVALID_PASSWORD = 0x52e; 
    private static final int NOT_PERMITTED = 0x530; 
    private static final int PASSWORD_EXPIRED = 0x532; 
    private static final int ACCOUNT_DISABLED = 0x533; 
    private static final int ACCOUNT_EXPIRED = 0x701; 
    private static final int PASSWORD_NEEDS_RESET = 0x773; 
    private static final int ACCOUNT_LOCKED = 0x775; 

    private final String domain; 
    private final String rootDn; 
    private final String url; 
    private boolean convertSubErrorCodesToExceptions; 
    private String searchFilter = "(&(objectClass=user)(userPrincipalName={0}))"; 

    // Only used to allow tests to substitute a mock LdapContext 
    ContextFactory contextFactory = new ContextFactory(); 

    /** 
    * @param domain the domain name (may be null or empty) 
    * @param url an LDAP url (or multiple URLs) 
    */ 
    public BlazingActiveDirectory(String domain, String url) { 
     Assert.isTrue(StringUtils.hasText(url), "Url cannot be empty"); 
     this.domain = StringUtils.hasText(domain) ? domain.toLowerCase() : null; 
     this.url = url; 
     rootDn = this.domain == null ? null : rootDnFromDomain(this.domain); 
    } 

    @Override 
    protected DirContextOperations doAuthentication(UsernamePasswordAuthenticationToken auth) { 
     String username = auth.getName(); 
     String password = (String) auth.getCredentials(); 

     DirContext ctx = bindAsUser(username, password); 

     try { 
     return searchForUser(ctx, username); 
     } catch (NamingException e) { 
     logger.error("Failed to locate directory entry for authenticated user: " + username, e); 
     throw badCredentials(e); 
     } finally { 
     LdapUtils.closeContext(ctx); 
     } 
    } 

    /** 
    * Creates the user authority list from the values of the {@code memberOf} attribute obtained from the user's 
    * Active Directory entry. 
    */ 
    @Override 
    protected Collection<? extends GrantedAuthority> loadUserAuthorities(DirContextOperations userData, String username, String password) { 
     String[] groups = userData.getStringAttributes("memberOf"); 

     if (groups == null) { 
     logger.debug("No values for 'memberOf' attribute."); 

     return AuthorityUtils.NO_AUTHORITIES; 
     } 

     if (logger.isDebugEnabled()) { 
     logger.debug("'memberOf' attribute values: " + Arrays.asList(groups)); 
     } 

     ArrayList<GrantedAuthority> authorities = new ArrayList<GrantedAuthority>(groups.length); 

     for (String group : groups) { 
     authorities.add(new SimpleGrantedAuthority(new DistinguishedName(group).removeLast().getValue())); 
     } 

     return authorities; 
    } 

    private DirContext bindAsUser(String username, String password) { 
     // TODO. add DNS lookup based on domain 
     final String bindUrl = url; 

     Hashtable<String, String> env = new Hashtable<String, String>(); 
     env.put(Context.SECURITY_AUTHENTICATION, "simple"); 
     String bindPrincipal = createBindPrincipal(username); 
     env.put(Context.SECURITY_PRINCIPAL, bindPrincipal); 
     env.put(Context.PROVIDER_URL, bindUrl); 
     env.put(Context.SECURITY_CREDENTIALS, password); 
     env.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.ldap.LdapCtxFactory"); 
     env.put(Context.OBJECT_FACTORIES, DefaultDirObjectFactory.class.getName()); 

     try { 
     return contextFactory.createContext(env); 
     } catch (NamingException e) { 
     if ((e instanceof AuthenticationException) || (e instanceof OperationNotSupportedException)) { 
      handleBindException(bindPrincipal, e); 
      throw badCredentials(e); 
     } else { 
      throw LdapUtils.convertLdapException(e); 
     } 
     } 
    } 

    private void handleBindException(String bindPrincipal, NamingException exception) { 
     if (logger.isDebugEnabled()) { 
     logger.debug("Authentication for " + bindPrincipal + " failed:" + exception); 
     } 

     int subErrorCode = parseSubErrorCode(exception.getMessage()); 

     if (subErrorCode <= 0) { 
     logger.debug("Failed to locate AD-specific sub-error code in message"); 
     return; 
     } 

     logger.info("Active Directory authentication failed: " + subCodeToLogMessage(subErrorCode)); 

     if (convertSubErrorCodesToExceptions) { 
     raiseExceptionForErrorCode(subErrorCode, exception); 
     } 
    } 

    private int parseSubErrorCode(String message) { 
     Matcher m = SUB_ERROR_CODE.matcher(message); 

     if (m.matches()) { 
     return Integer.parseInt(m.group(1), 16); 
     } 

     return -1; 
    } 

    private void raiseExceptionForErrorCode(int code, NamingException exception) { 
     String hexString = Integer.toHexString(code); 
     Throwable cause = new ActiveDirectoryAuthenticationException(hexString, exception.getMessage(), exception); 
     switch (code) { 
     case PASSWORD_EXPIRED: 
      throw new CredentialsExpiredException(messages.getMessage("LdapAuthenticationProvider.credentialsExpired", 
        "User credentials have expired"), cause); 
     case ACCOUNT_DISABLED: 
      throw new DisabledException(messages.getMessage("LdapAuthenticationProvider.disabled", 
        "User is disabled"), cause); 
     case ACCOUNT_EXPIRED: 
      throw new AccountExpiredException(messages.getMessage("LdapAuthenticationProvider.expired", 
        "User account has expired"), cause); 
     case ACCOUNT_LOCKED: 
      throw new LockedException(messages.getMessage("LdapAuthenticationProvider.locked", 
        "User account is locked"), cause); 
     default: 
      throw badCredentials(cause); 
     } 
    } 

    private String subCodeToLogMessage(int code) { 
     switch (code) { 
     case USERNAME_NOT_FOUND: 
      return "User was not found in directory"; 
     case INVALID_PASSWORD: 
      return "Supplied password was invalid"; 
     case NOT_PERMITTED: 
      return "User not permitted to logon at this time"; 
     case PASSWORD_EXPIRED: 
      return "Password has expired"; 
     case ACCOUNT_DISABLED: 
      return "Account is disabled"; 
     case ACCOUNT_EXPIRED: 
      return "Account expired"; 
     case PASSWORD_NEEDS_RESET: 
      return "User must reset password"; 
     case ACCOUNT_LOCKED: 
      return "Account locked"; 
     } 

     return "Unknown (error code " + Integer.toHexString(code) +")"; 
    } 

    private BadCredentialsException badCredentials() { 
     return new BadCredentialsException(messages.getMessage(
        "LdapAuthenticationProvider.badCredentials", "Bad credentials")); 
    } 

    private BadCredentialsException badCredentials(Throwable cause) { 
     return (BadCredentialsException) badCredentials().initCause(cause); 
    } 

    private DirContextOperations searchForUser(DirContext context, String username) throws NamingException { 
     SearchControls searchControls = new SearchControls(); 
     searchControls.setSearchScope(SearchControls.SUBTREE_SCOPE); 

     String bindPrincipal = createBindPrincipal(username); 
     String searchRoot = rootDn != null ? rootDn : searchRootFromPrincipal(bindPrincipal); 

     try { 
      String verifyName = username; 
      if(username.indexOf("@") != -1){ 
        verifyName = username.substring(0,username.indexOf("@")); 
      } 
     return SpringSecurityLdapTemplate.searchForSingleEntryInternal(context, searchControls, 
       searchRoot, searchFilter, new Object[]{verifyName}); 
     } catch (IncorrectResultSizeDataAccessException incorrectResults) { 
     // Search should never return multiple results if properly configured - just rethrow 
     if (incorrectResults.getActualSize() != 0) { 
      throw incorrectResults; 
     } 
     // If we found no results, then the username/password did not match 
     UsernameNotFoundException userNameNotFoundException = new UsernameNotFoundException("User " + username 
       + " not found in directory.", incorrectResults); 
     throw badCredentials(userNameNotFoundException); 
     } 
    } 

    private String searchRootFromPrincipal(String bindPrincipal) { 
     int atChar = bindPrincipal.lastIndexOf('@'); 

     if (atChar < 0) { 
     logger.debug("User principal '" + bindPrincipal + "' does not contain the domain, and no domain has been configured"); 
     throw badCredentials(); 
     } 

     return rootDnFromDomain(bindPrincipal.substring(atChar + 1, bindPrincipal.length())); 
    } 

    private String rootDnFromDomain(String domain) { 
     String[] tokens = StringUtils.tokenizeToStringArray(domain, "."); 
     StringBuilder root = new StringBuilder(); 

     for (String token : tokens) { 
     if (root.length() > 0) { 
      root.append(','); 
     } 
     root.append("dc=").append(token); 
     } 

     return root.toString(); 
    } 

    String createBindPrincipal(String username) { 
     if (domain == null || username.toLowerCase().endsWith(domain)) { 
     return username; 
     } 

     return username + "@" + domain; 
    } 

    /** 
    * By default, a failed authentication (LDAP error 49) will result in a {@code BadCredentialsException}. 
    * <p> 
    * If this property is set to {@code true}, the exception message from a failed bind attempt will be parsed 
    * for the AD-specific error code and a {@link CredentialsExpiredException}, {@link DisabledException}, 
    * {@link AccountExpiredException} or {@link LockedException} will be thrown for the corresponding codes. All 
    * other codes will result in the default {@code BadCredentialsException}. 
    * 
    * @param convertSubErrorCodesToExceptions {@code true} to raise an exception based on the AD error code. 
    */ 
    public void setConvertSubErrorCodesToExceptions(boolean convertSubErrorCodesToExceptions) { 
     this.convertSubErrorCodesToExceptions = convertSubErrorCodesToExceptions; 
    } 

    /** 
    * The LDAP filter string to search for the user being authenticated. 
    * Occurrences of {0} are replaced with the {@code [email protected]}. 
    * <p> 
    * Defaults to: {@code (&(objectClass=user)(userPrincipalName={0}))} 
    * </p> 
    * 
    * @param searchFilter the filter string 
    * 
    * @since 3.2.6 
    */ 
    public void setSearchFilter(String searchFilter) { 
     Assert.hasText(searchFilter,"searchFilter must have text"); 
     this.searchFilter = searchFilter; 
    } 

    static class ContextFactory { 
     DirContext createContext(Hashtable<?,?> env) throws NamingException { 
     return new InitialLdapContext(env, null); 
     } 
    } 
} 
関連する問題