【问题标题】:How do you authenticate against an Active Directory server using Spring Security?如何使用 Spring Security 对 Active Directory 服务器进行身份验证?
【发布时间】:2010-09-10 05:24:36
【问题描述】:

我正在编写一个需要用户登录的 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>

【问题讨论】:

  • 这与其说是一个真正的答案,不如说是一个明确的问题——您是否已经为 spring 安全包完全打开了日志记录?
  • 我已为所有内容开启了登录功能。没有看到任何消息被记录...我已经用我的 Log4J 配置更新了我的问题。
  • 告诉我这需要的 jar 文件。请

标签: java spring active-directory ldap


【解决方案1】:

我有与你一样的撞墙体验,并最终编写了一个自定义身份验证提供程序,对 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 角色——这让我可以在我的拦截 url 元素中测试该角色。您需要匹配您想要测试的任何角色,甚至根据 Active Directory 组或其他方式分配角色。

最后,由此推论,我实现 LdapAuthenticationProvider.authenticate() 的方式为所有拥有有效 AD 帐户的用户提供了相同的 ROLE_USER 角色。显然,在这种方法中,您可以对用户执行进一步的测试(即,用户是否在特定的 AD 组中?)并以这种方式分配角色,甚至在授予用户访问权限之前测试某些条件 all .

【讨论】:

  • 告诉我这需要的 jar 文件。请。
  • 我遇到了同样的问题。但是由于 Spring 新版本中不推荐使用这些方法中的许多方法,我不能再像这样做同样的工作了。我在这里问了我的问题stackoverflow.com/questions/32070142/…。我想知道你是否可以在这个@delfuego 中帮助我
【解决方案2】:

作为参考,Spring Security 3.1 有一个身份验证提供程序specifically for Active Directory

【讨论】:

  • +1 这应该是最佳答案。根据经验,我可以说,如果您使用 LdapAuthenticationProvider 对 AD 进行身份验证,那么您自己的生活就会变得非常艰难。在 Spring 的现代版本中,您可以用不到 5 行代码完成您想做的事情。请参阅卢克在上面提供的链接。我在下面的回答中也给出了详细信息。
【解决方案3】:

只是为了将其更新到最新状态。 Spring Security 3.0 有一个complete package,默认实现专门用于 ldap-bind 以及查询和比较身份验证。

【讨论】:

    【解决方案4】:

    我能够使用 spring security 2.0.4 对活动目录进行身份验证。

    我记录了设置

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

    【讨论】:

      【解决方案5】:

      正如上面卢克的回答:

      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

      【讨论】:

      • 我有类似的问题。我是否需要更改视图以使安全控制器自动找到主体?
      【解决方案6】:

      如果您使用的是 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;
      }
      }
      

      【讨论】:

        【解决方案7】:

        没有 SSL 的 LDAP 身份验证是不安全的,任何人都可以在将用户凭据传输到 LDAP 服务器时看到这些凭据。我建议使用 LDAPS:\ 协议进行身份验证。它不需要对弹簧部分进行任何重大更改,但您可能会遇到一些与证书相关的问题。详情请见LDAP Active Directory authentication in Spring with SSL

        【讨论】:

          【解决方案8】:

          来自卢克上面的回答:

          作为参考,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_”为前缀,这使得在项目中通过文本搜索很容易找到它们,但如果出于某种奇怪的原因只有 2 个单独的组进行区分,显然可能会在 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 环境。

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 2015-04-17
            • 2020-02-16
            • 2019-01-23
            • 2020-04-08
            • 2014-12-01
            • 1970-01-01
            • 2012-05-25
            • 2012-07-14
            相关资源
            最近更新 更多