关于java:如何使用Spring Security对Active Directory服务器进行身份验证?

关于java:如何使用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:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
  <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是:

好。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<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类:

好。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
/**
 * 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 not 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类:

好。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
/**
 * Custom Spring Security LDAP authenticator which tries to bind to an LDAP server using the
 * passed-in credentials; does not 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类:

好。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
 <wyn> / **
  * <p>

  *验证令牌,当应用需要进一步访问用于以下目的的LDAP上下文时使用
  *验证用户身份。
  *
</p>好。


  *
  * <p>

  *当这是存储在Spring Security上下文中的Authentication对象时,一个应用程序
  *可以这样检索当前的LDAP上下文:
  *
</p>好。

 *
 * [cc lang="java"]
 * LdapAuthenticationToken ldapAuth = (LdapAuthenticationToken) SecurityContextHolder
 *      .getContext().getAuthentication();
 * InitialLdapContext ldapContext = ldapAuth.getContext();
 *

*
* @作者杰森
*
* /
公共类LdapAuthenticationToken扩展AbstractAuthenticationToken {

专用静态最终长serialVersionUID = -5040340622950665401L;

私人身份验证授权;
临时私有InitialLdapContext上下文;
私有List 权限=新的ArrayList ();

/ **
*使用现有的Authentication对象构造一个新的LdapAuthenticationToken,并
*为所有用户授予默认权限。
*
* @参数验证
* @param defaultAuthority
* /
公共LdapAuthenticationToken(身份验证auth,GrantedAuthority defaultAuthority){
this.auth = auth;
如果(auth.getAuthorities()!= null){
this.authorities.addAll(Arrays.asList(auth.getAuthorities()));
}
如果(defaultAuthority!= null){
this.authorities.add(defaultAuthority);
}
super.setAuthenticated(true);
}

/ **
*使用现有的Authentication对象构造一个新的LdapAuthenticationToken,并
*为所有用户授予默认权限。
*
* @参数验证
* @param defaultAuthority
* /
public LdapAuthenticationToken(Authentication auth,String defaultAuthority){
this(auth,new GrantedAuthorityImpl(defaultAuthority));
}

公共GrantedAuthority [] getAuthorities(){
GrantedAuthority [] AuthorityArray = this.authorities.toArray(new GrantedAuthority [0]);
返回authoritiesArray;
}

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

公共对象getCredentials(){
返回auth.getCredentials();
}

公共对象getPrincipal(){
返回auth.getPrincipal();
}

/ **
*检索附加到该用户身份验证对象的LDAP上下文。
*
* @返回LDAP上下文
* /
公共InitialLdapContext getContext(){
返回上下文
}

/ **
*将LDAP上下文附加到该用户的身份验证对象。
*
* @param上下文
* LDAP上下文
* /
公共无效setContext(InitialLdapContext上下文){
this.context =上下文;
}

}

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

好。

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

好。

另外,在LdapAuthenticationProvider.authenticate()中,我为所有登录的用户分配了ROLE_USER角色-然后,我可以在我的拦截URL元素中测试该角色。您将要使其与要测试的角色匹配,甚至根据Active Directory组或其他角色分配角色。

好。

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

好。

好。


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


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


如上述卢克的回答:

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

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

在resources.groovy中:

1
2
3
4
ldapAuthProvider1(ActiveDirectoryLdapAuthenticationProvider,
       "mydomain.com",
       "ldap://mydomain.com/"
)

在Config.groovy中:

1
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 2.0.4针对活动目录进行身份验证。

我记录了设置

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


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

  • SecurityConfig.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
@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;
}
}


根据卢克的上述回答:

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.1中尝试了上述方法:ldap进行了一些细微更改-用户作为原始成员通过的活动目录组。

以前在ldap下,这些组使用大写字母并以" ROLE_"开头,这使它们易于在项目中的文本搜索中找到,但是如果由于某些奇怪的原因而使2个单独的组仅按大小写进行区分,则很可能在unix组中解决问题即帐户和帐户)。

此外,该语法还要求手动指定域控制器名称和端口,这使得冗余显得有些恐怖。当然,有一种方法可以在Java中查找域的SRV DNS记录,即等效于(来自Samba 4 howto):

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

随后进行常规A查找:

1
2
$ 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。


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


推荐阅读