Java 您如何使用 Spring Security 对 Active Directory 服务器进行身份验证?

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/84680/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-11 08:06:56  来源:igfitidea点击:

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

javaspringactive-directoryldap

提问by Michael

I'm writing a Spring web application that requires users to login. My company has an Active Directory server that I'd like to make use of for this purpose. However, I'm having trouble using Spring Security to connect to the server.

我正在编写一个需要用户登录的 Spring Web 应用程序。我的公司有一台 Active Directory 服务器,我想将其用于此目的。但是,我在使用 Spring Security 连接到服务器时遇到了问题。

I'm using Spring 2.5.5 and Spring Security 2.0.3, along with Java 1.6.

我使用的是 Spring 2.5.5 和 Spring Security 2.0.3,以及 Java 1.6。

If I change the LDAP URL to the wrong IP address, it doesn't throw an exception or anything, so I'm wondering if it's even tryingto connect to the server to begin with.

如果我将 LDAP URL 更改为错误的 IP 地址,它不会引发异常或任何事情,所以我想知道它是否甚至尝试连接到服务器开始。

Although the web application starts up just fine, any information I enter into the login page is rejected. I had previously used an InMemoryDaoImpl, which worked fine, so the rest of my application seems to be configured correctly.

尽管 Web 应用程序启动得很好,但我在登录页面中输入的任何信息都会被拒绝。我以前使用过一个 InMemoryDaoImpl,它工作得很好,所以我的应用程序的其余部分似乎配置正确。

Here are my security-related beans:

这是我的安全相关 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>

采纳答案by delfuego

I had the same banging-my-head-against-the-wall experience you did, and ended up writing a custom authentication provider that does an LDAP query against the Active Directory server.

我有与您一样的头撞墙的经历,最终编写了一个自定义身份验证提供程序,该提供程序对 Active Directory 服务器执行 LDAP 查询。

So my security-related beans are:

所以我与安全相关的 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>

Then the LdapAuthenticationProvider class:

然后是 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;
    }

}

Then the LdapAuthenticatorImpl class:

然后是 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 = "";
        }
    }

}

And finally, the LdapAuthenticationToken class:

最后,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;
    }

}

You'll notice that there are a few bits in there that you might not need.

您会注意到其中有一些您可能不需要的位。

For example, my app needed to retain the successfully-logged-in LDAP context for further use by the user once logged in -- the app's purpose is to let users log in via their AD credentials and then perform further AD-related functions. So because of that, I have a custom authentication token, LdapAuthenticationToken, that I pass around (rather than Spring's default Authentication token) which allows me to attach the LDAP context. In LdapAuthenticationProvider.authenticate(), I create that token and pass it back out; in LdapAuthenticatorImpl.authenticate(), I attach the logged-in context to the return object so that it can be added to the user's Spring authentication object.

例如,我的应用程序需要保留成功登录的 LDAP 上下文以供用户登录后进一步使用——该应用程序的目的是让用户通过他们的 AD 凭据登录,然后执行进一步的 AD 相关功能。因此,我有一个自定义身份验证令牌 LdapAuthenticationToken,我传递它(而不是 Spring 的默认身份验证令牌),它允许我附加 LDAP 上下文。在 LdapAuthenticationProvider.authenticate() 中,我创建该令牌并​​将其传回;在 LdapAuthenticatorImpl.authenticate() 中,我将登录上下文附加到返回对象,以便将其添加到用户的 Spring 身份验证对象中。

Also, in LdapAuthenticationProvider.authenticate(), I assign all logged-in users the ROLE_USER role -- that's what lets me then test for that role in my intercept-url elements. You'll want to make this match whatever role you want to test for, or even assign roles based on Active Directory groups or whatever.

此外,在 LdapAuthenticationProvider.authenticate() 中,我为所有登录用户分配了 ROLE_USER 角色——这让我可以在我的拦截 url 元素中测试该角色。您将希望使其与您要测试的任何角色相匹配,甚至根据 Active Directory 组或其他任何角色分配角色。

Finally, and a corollary to that, the way I implemented LdapAuthenticationProvider.authenticate() gives all users with valid AD accounts the same ROLE_USER role. Obviously, in that method, you can perform further tests on the user (i.e., is the user in a specific AD group?) and assign roles that way, or even test for some condition before even granting the user access at all.

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

回答by delfuego

I was able to authenticate against active directory using spring security 2.0.4.

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

I documented the settings

我记录了设置

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

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

回答by er4z0r

Just to bring this to an up-to-date status. Spring Security 3.0 has a complete packagewith default implementations devoted to ldap-bind as well as query and compare authentication.

只是为了将其提升到最新状态。Spring Security 3.0 有一个完整的包,其中包含专门用于 ldap-bind 以及查询和比较身份验证的默认实现。

回答by Gaurav

LDAP authentication without SSL is not safe anyone can see the user credential when those are transffered to LDAP server. I suggest using LDAPS:\ protocol for authentication. It doesn't require any major change on spring part but you may ran with some issues related to certificates. See LDAP Active Directory authentication in Spring with SSLfor more details

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

回答by Shaun the Sheep

For reference, Spring Security 3.1 has an authentication provider specifically for Active Directory.

作为参考,Spring Security 3.1 有一个专门用于 Active Directory的身份验证提供程序。

回答by rjc730

From Luke's answer above:

从上面卢克的回答中:

For reference, Spring Security 3.1 has an authentication provider [specifically for 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 有一个身份验证提供程序 [专门用于 Active Directory][1]。

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

I tried the above with Spring Security 3.1.1: there are some slight changes from ldap - the active directory groups the user is a member of come through as original case.

我在 Spring Security 3.1.1 中尝试了上述操作: ldap 有一些细微的变化 - 用户所属的活动目录组作为原始案例通过。

Previously under ldap the groups were capitalized and prefixed with "ROLE_", which made them easy to find with a text search in a project but obviously might case problems in a unix group if for some strange reason had 2 separate groups only differentiated by case(ie accounts and Accounts).

以前在 ldap 下,这些组大写并以“ROLE_”为前缀,这使得在项目中通过文本搜索很容易找到它们,但如果由于某种奇怪的原因有 2 个单独的组仅按大小写(即帐户和帐户)。

Also the syntax requires manual specification of the domain controller name and port, which makes it a bit scary for redundancy. Surely there is a way of looking up the SRV DNS record for the domain in java, ie equivalent of(from Samba 4 howto):

此外,语法需要手动指定域控制器名称和端口,这使得冗余有点可怕。当然有一种在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.

followed by regular A lookup:

其次是常规的 A 查找:

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

(Actually might need to lookup _kerberos SRV record too...)

(实际上可能也需要查找 _kerberos SRV 记录...)

The above was with Samba4.0rc1, we are progressively upgrading from Samba 3.x LDAP environment to Samba AD one.

上面是 Samba4.0rc1,我们正在逐步从 Samba 3.x LDAP 环境升级到 Samba AD 环境。

回答by Cookalino

As in Luke's answer above:

正如上面卢克的回答:

Spring Security 3.1 has an authentication provider specifically for Active Directory.

Spring Security 3.1 有一个专门用于 Active Directory 的身份验证提供程序。

Here is the detail of how this can be easily done using ActiveDirectoryLdapAuthenticationProvider.

以下是如何使用 ActiveDirectoryLdapAuthenticationProvider 轻松完成此操作的详细信息。

In resources.groovy:

在 resources.groovy 中:

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

In Config.groovy:

在 Config.groovy 中:

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

This is all the code you need. You can pretty much remove all other grails.plugin.springsecurity.ldap.* settings in Config.groovy as they don't apply to this AD setup.

这是您需要的所有代码。您几乎可以删除 Config.groovy 中的所有其他 grails.plugin.springsecurity.ldap.* 设置,因为它们不适用于此 AD 设置。

For documentation, see: http://docs.spring.io/spring-security/site/docs/3.1.x/reference/springsecurity-single.html#ldap-active-directory

有关文档,请参阅:http: //docs.spring.io/spring-security/site/docs/3.1.x/reference/springsecurity-single.html#ldap-active-directory

回答by Riddhi Gohil

If you are using Spring security 4you can also implement same using given class

  • SecurityConfig.java

如果您使用的是 Spring security 4,您也可以使用给定的类实现相同的

  • 安全配置文件
@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;
}
}