簡體   English   中英

如何使用Spring Security對Active Directory服務器進行身份驗證?

[英]How do you authenticate against an Active Directory server using Spring Security?

我正在編寫一個需要用戶登錄的Spring Web應用程序。 我的公司有一個Active Directory服務器,我想為此目的使用它。 但是,我在使用Spring Security連接服務器時遇到問題。

我正在使用Spring 2.5.5和Spring Security 2.0.3以及Java 1.6。

如果我將LDAP URL更改為錯誤的IP地址,它不會拋出異常或任何東西,所以我想知道它是否甚至嘗試連接到服務器開始。

盡管Web應用程序啟動得很好,但我在登錄頁面中輸入的任何信息都會被拒絕。 我以前使用過InMemoryDaoImpl,它工作正常,所以我的應用程序的其余部分似乎都配置正確。

這是我與安全相關的bean:

  <beans:bean id="ldapAuthProvider" class="org.springframework.security.providers.ldap.LdapAuthenticationProvider">
    <beans:constructor-arg>
      <beans:bean class="org.springframework.security.providers.ldap.authenticator.BindAuthenticator">
        <beans:constructor-arg ref="initialDirContextFactory" />
        <beans:property name="userDnPatterns">
          <beans:list>
            <beans:value>CN={0},OU=SBSUsers,OU=Users,OU=MyBusiness,DC=Acme,DC=com</beans:value>
          </beans:list>
        </beans:property>
      </beans:bean>
    </beans:constructor-arg>
  </beans:bean>

  <beans:bean id="userDetailsService" class="org.springframework.security.userdetails.ldap.LdapUserDetailsManager">
    <beans:constructor-arg ref="initialDirContextFactory" />
  </beans:bean>

  <beans:bean id="initialDirContextFactory" class="org.springframework.security.ldap.DefaultInitialDirContextFactory">
    <beans:constructor-arg value="ldap://192.168.123.456:389/DC=Acme,DC=com" />
  </beans:bean>

我做了同樣的敲擊 - 我的頭撞牆體驗,最后編寫了一個自定義身份驗證提供程序,對Active Directory服務器執行LDAP查詢。

所以我的安全相關的bean是:

<beans:bean id="contextSource"
    class="org.springframework.security.ldap.DefaultSpringSecurityContextSource">
    <beans:constructor-arg value="ldap://hostname.queso.com:389/" />
</beans:bean>

<beans:bean id="ldapAuthenticationProvider"
    class="org.queso.ad.service.authentication.LdapAuthenticationProvider">
    <beans:property name="authenticator" ref="ldapAuthenticator" />
    <custom-authentication-provider />
</beans:bean>

<beans:bean id="ldapAuthenticator"
    class="org.queso.ad.service.authentication.LdapAuthenticatorImpl">
    <beans:property name="contextFactory" ref="contextSource" />
    <beans:property name="principalPrefix" value="QUESO\" />
</beans:bean>

然后是LdapAuthenticationProvider類:

/**
 * Custom Spring Security authentication provider which tries to bind to an LDAP server with
 * the passed-in credentials; of note, when used with the custom {@link LdapAuthenticatorImpl},
 * does <strong>not</strong> require an LDAP username and password for initial binding.
 * 
 * @author Jason
 */
public class LdapAuthenticationProvider implements AuthenticationProvider {

    private LdapAuthenticator authenticator;

    public Authentication authenticate(Authentication auth) throws AuthenticationException {

        // Authenticate, using the passed-in credentials.
        DirContextOperations authAdapter = authenticator.authenticate(auth);

        // Creating an LdapAuthenticationToken (rather than using the existing Authentication
        // object) allows us to add the already-created LDAP context for our app to use later.
        LdapAuthenticationToken ldapAuth = new LdapAuthenticationToken(auth, "ROLE_USER");
        InitialLdapContext ldapContext = (InitialLdapContext) authAdapter
                .getObjectAttribute("ldapContext");
        if (ldapContext != null) {
            ldapAuth.setContext(ldapContext);
        }

        return ldapAuth;
    }

    public boolean supports(Class clazz) {
        return (UsernamePasswordAuthenticationToken.class.isAssignableFrom(clazz));
    }

    public LdapAuthenticator getAuthenticator() {
        return authenticator;
    }

    public void setAuthenticator(LdapAuthenticator authenticator) {
        this.authenticator = authenticator;
    }

}

然后是LdapAuthenticatorImpl類:

/**
 * Custom Spring Security LDAP authenticator which tries to bind to an LDAP server using the
 * passed-in credentials; does <strong>not</strong> require "master" credentials for an
 * initial bind prior to searching for the passed-in username.
 * 
 * @author Jason
 */
public class LdapAuthenticatorImpl implements LdapAuthenticator {

    private DefaultSpringSecurityContextSource contextFactory;
    private String principalPrefix = "";

    public DirContextOperations authenticate(Authentication authentication) {

        // Grab the username and password out of the authentication object.
        String principal = principalPrefix + authentication.getName();
        String password = "";
        if (authentication.getCredentials() != null) {
            password = authentication.getCredentials().toString();
        }

        // If we have a valid username and password, try to authenticate.
        if (!("".equals(principal.trim())) && !("".equals(password.trim()))) {
            InitialLdapContext ldapContext = (InitialLdapContext) contextFactory
                    .getReadWriteContext(principal, password);

            // We need to pass the context back out, so that the auth provider can add it to the
            // Authentication object.
            DirContextOperations authAdapter = new DirContextAdapter();
            authAdapter.addAttributeValue("ldapContext", ldapContext);

            return authAdapter;
        } else {
            throw new BadCredentialsException("Blank username and/or password!");
        }
    }

    /**
     * Since the InitialLdapContext that's stored as a property of an LdapAuthenticationToken is
     * transient (because it isn't Serializable), we need some way to recreate the
     * InitialLdapContext if it's null (e.g., if the LdapAuthenticationToken has been serialized
     * and deserialized). This is that mechanism.
     * 
     * @param authenticator
     *          the LdapAuthenticator instance from your application's context
     * @param auth
     *          the LdapAuthenticationToken in which to recreate the InitialLdapContext
     * @return
     */
    static public InitialLdapContext recreateLdapContext(LdapAuthenticator authenticator,
            LdapAuthenticationToken auth) {
        DirContextOperations authAdapter = authenticator.authenticate(auth);
        InitialLdapContext context = (InitialLdapContext) authAdapter
                .getObjectAttribute("ldapContext");
        auth.setContext(context);
        return context;
    }

    public DefaultSpringSecurityContextSource getContextFactory() {
        return contextFactory;
    }

    /**
     * Set the context factory to use for generating a new LDAP context.
     * 
     * @param contextFactory
     */
    public void setContextFactory(DefaultSpringSecurityContextSource contextFactory) {
        this.contextFactory = contextFactory;
    }

    public String getPrincipalPrefix() {
        return principalPrefix;
    }

    /**
     * Set the string to be prepended to all principal names prior to attempting authentication
     * against the LDAP server.  (For example, if the Active Directory wants the domain-name-plus
     * backslash prepended, use this.)
     * 
     * @param principalPrefix
     */
    public void setPrincipalPrefix(String principalPrefix) {
        if (principalPrefix != null) {
            this.principalPrefix = principalPrefix;
        } else {
            this.principalPrefix = "";
        }
    }

}

最后,LdapAuthenticationToken類:

/**
 * <p>
 * Authentication token to use when an app needs further access to the LDAP context used to
 * authenticate the user.
 * </p>
 * 
 * <p>
 * When this is the Authentication object stored in the Spring Security context, an application
 * can retrieve the current LDAP context thusly:
 * </p>
 * 
 * <pre>
 * LdapAuthenticationToken ldapAuth = (LdapAuthenticationToken) SecurityContextHolder
 *      .getContext().getAuthentication();
 * InitialLdapContext ldapContext = ldapAuth.getContext();
 * </pre>
 * 
 * @author Jason
 * 
 */
public class LdapAuthenticationToken extends AbstractAuthenticationToken {

    private static final long serialVersionUID = -5040340622950665401L;

    private Authentication auth;
    transient private InitialLdapContext context;
    private List<GrantedAuthority> authorities = new ArrayList<GrantedAuthority>();

    /**
     * Construct a new LdapAuthenticationToken, using an existing Authentication object and
     * granting all users a default authority.
     * 
     * @param auth
     * @param defaultAuthority
     */
    public LdapAuthenticationToken(Authentication auth, GrantedAuthority defaultAuthority) {
        this.auth = auth;
        if (auth.getAuthorities() != null) {
            this.authorities.addAll(Arrays.asList(auth.getAuthorities()));
        }
        if (defaultAuthority != null) {
            this.authorities.add(defaultAuthority);
        }
        super.setAuthenticated(true);
    }

    /**
     * Construct a new LdapAuthenticationToken, using an existing Authentication object and
     * granting all users a default authority.
     * 
     * @param auth
     * @param defaultAuthority
     */
    public LdapAuthenticationToken(Authentication auth, String defaultAuthority) {
        this(auth, new GrantedAuthorityImpl(defaultAuthority));
    }

    public GrantedAuthority[] getAuthorities() {
        GrantedAuthority[] authoritiesArray = this.authorities.toArray(new GrantedAuthority[0]);
        return authoritiesArray;
    }

    public void addAuthority(GrantedAuthority authority) {
        this.authorities.add(authority);
    }

    public Object getCredentials() {
        return auth.getCredentials();
    }

    public Object getPrincipal() {
        return auth.getPrincipal();
    }

    /**
     * Retrieve the LDAP context attached to this user's authentication object.
     * 
     * @return the LDAP context
     */
    public InitialLdapContext getContext() {
        return context;
    }

    /**
     * Attach an LDAP context to this user's authentication object.
     * 
     * @param context
     *          the LDAP context
     */
    public void setContext(InitialLdapContext context) {
        this.context = context;
    }

}

您會注意到那里有一些您可能不需要的位。

例如,我的應用程序需要保留已成功登錄的LDAP上下文,以供用戶在登錄后進一步使用 - 該應用程序的目的是讓用戶通過其AD憑據登錄,然后執行其他與AD相關的功能。 因此,我有一個自定義身份驗證令牌LdapAuthenticationToken,我傳遞(而不是Spring的默認身份驗證令牌),它允許我附加LDAP上下文。 在LdapAuthenticationProvider.authenticate()中,我創建了該令牌並將其傳回去; 在LdapAuthenticatorImpl.authenticate()中,我將登錄的上下文附加到返回對象,以便可以將其添加到用戶的Spring身份驗證對象中。

此外,在LdapAuthenticationProvider.authenticate()中,我為所有登錄用戶分配了ROLE_USER角色 - 這就是讓我在intercept-url元素中測試該角色的原因。 您希望將此匹配設置為您要測試的任何角色,甚至根據Active Directory組或其他任何角色分配角色。

最后,還有一個推論,我實現LdapAuthenticationProvider.authenticate()的方式為所有用戶提供了有效的AD帳戶ROLE_USER角色。 顯然,在這種方法中,可以對用戶進行進一步的測試(即是在一個特定的AD組的用戶?),甚至授予用戶訪問所有之前分配角色對於一些條件的方式,甚至測試。

作為參考,Spring Security 3.1具有專門用於Active Directory的身份驗證提供程序。

只是為了使其達到最新狀態。 Spring Security 3.0有一個完整的包 ,其默認實現專門用於ldap-bind以及查詢和比較身份驗證。

我能夠使用spring security 2.0.4對Active Directory進行身份驗證。

我記錄了設置

http://maniezhilan.blogspot.com/2008/10/spring-security-204-with-active.html

正如盧克在上面的回答:

Spring Security 3.1具有專門用於Active Directory的身份驗證提供程序。

以下是使用ActiveDirectoryLdapAuthenticationProvider如何輕松完成此操作的詳細信息。

在resources.groovy:

ldapAuthProvider1(ActiveDirectoryLdapAuthenticationProvider,
        "mydomain.com",
        "ldap://mydomain.com/"
)

在Config.groovy中:

grails.plugin.springsecurity.providerNames = ['ldapAuthProvider1']

這是您需要的所有代碼。 您幾乎可以刪除Config.groovy中的所有其他grails.plugin.springsecurity.ldap。*設置,因為它們不適用於此AD設置。

有關文檔,請參閱: http//docs.spring.io/spring-security/site/docs/3.1.x/reference/springsecurity-single.html#ldap-active-directory

來自盧克的答案:

作為參考,Spring Security 3.1有一個身份驗證提供程序[專門用於Active Directory] ​​[1]。

[1]: http//static.springsource.org/spring-security/site/docs/3.1.x/reference/springsecurity-single.html#ldap-active-directory

我使用Spring Security 3.1.1嘗試了上述內容:ldap有一些細微的變化 - 用戶所屬的活動目錄組是原始案例。

以前在ldap下,這些組被大寫並且以“ROLE_”為前綴,這使得在項目中通過文本搜索很容易找到它們,但是如果出於某些奇怪的原因,有兩個獨立的組僅根據情況進行區分,那么unix組中的情況顯然可能會出現問題(即帳戶和帳戶)。

此外,語法還需要手動指定域控制器名稱和端口,這使得冗余有點可怕。 當然有一種方法可以在java中查找域的SRV DNS記錄,即相當於(來自Samba 4 howto):

$ host -t SRV _ldap._tcp.samdom.example.com.
_ldap._tcp.samdom.example.com has SRV record 0 100 389 samba.samdom.example.com.

然后定期進行A查找:

$ host -t A samba.samdom.example.com.
samba.samdom.example.com has address 10.0.0.1

(實際上可能還需要查找_kerberos SRV記錄......)

以上是Samba4.0rc1,我們正逐步從Samba 3.x LDAP環境升級到Samba AD環境。

如果您使用的是Spring security 4,您也可以使用給定的類實現它

  • SecurityConfig.java
@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {


static final Logger LOGGER = LoggerFactory.getLogger(SecurityConfig.class);

@Autowired
protected void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
    auth.authenticationProvider(activeDirectoryLdapAuthenticationProvider());
}

@Override
protected void configure(HttpSecurity http) throws Exception {
    http
            .authorizeRequests()
              .antMatchers("/").permitAll()
              .anyRequest().authenticated();
            .and()
              .formLogin()
            .and()
              .logout();
}

@Bean
public AuthenticationProvider activeDirectoryLdapAuthenticationProvider() {
    ActiveDirectoryLdapAuthenticationProvider authenticationProvider = 
        new ActiveDirectoryLdapAuthenticationProvider("<domain>", "<url>");

    authenticationProvider.setConvertSubErrorCodesToExceptions(true);
    authenticationProvider.setUseAuthenticationRequestCredentials(true);

    return authenticationProvider;
}
}

沒有SSL的LDAP身份驗證不安全,任何人都可以在將這些憑據轉移到LDAP服務器時看到用戶憑據。 我建議使用LDAPS:\\ protocol進行身份驗證。 它不需要對彈簧部件進行任何重大更改,但您可能會遇到與證書相關的一些問題。 有關詳細信息,請參閱Spring with SSL中的LDAP Active Directory身份驗證

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM