如何使用Spring Security对Active Directory服务器进行身份validation?

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

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

如果我将LDAP URL更改为错误的IP地址,它不会抛出exception或任何东西,所以我想知道它是否甚至尝试连接到服务器开始。

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

这是我与安全相关的bean:

      CN={0},OU=SBSUsers,OU=Users,OU=MyBusiness,DC=Acme,DC=com            

我做了同样的敲击 – 我的头撞墙体验,最后编写了一个自定义身份validation提供程序,对Active Directory服务器执行LDAP查询。

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

 /** * 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 (eg, 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类:

 /** * 

* Authentication token to use when an app needs further access to the LDAP context used to * authenticate the user. *

* *

* When this is the Authentication object stored in the Spring Security context, an application * can retrieve the current LDAP context thusly: *

* *
 * LdapAuthenticationToken ldapAuth = (LdapAuthenticationToken) SecurityContextHolder * .getContext().getAuthentication(); * InitialLdapContext ldapContext = ldapAuth.getContext(); * 

* * @author Jason * */ public class LdapAuthenticationToken extends AbstractAuthenticationToken { private static final long serialVersionUID = -5040340622950665401L; private Authentication auth; transient private InitialLdapContext context; private List authorities = new ArrayList(); /** * 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相关的function。 因此,我有一个自定义身份validation令牌LdapAuthenticationToken,我传递(而不是Spring的默认身份validation令牌),它允许我附加LDAP上下文。 在LdapAuthenticationProvider.authenticate()中,我创建了该令牌并将其传回去; 在LdapAuthenticatorImpl.authenticate()中,我将登录的上下文附加到返回对象,以便可以将其添加到用户的Spring身份validation对象中。

此外,在LdapAuthenticationProvider.authenticate()中,我为所有登录用户分配了ROLE_USER角色 – 这就是让我在intercept-url元素中测试该角色的原因。 您希望使此匹配符合您要测试的任何角色,甚至根据Active Directory组或其他任何角色分配角色。

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

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

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

我能够使用spring security 2.0.4对Active Directory进行身份validation。

我记录了设置

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

正如卢克在上面的回答:

Spring Security 3.1具有专门用于Active Directory的身份validation提供程序。

以下是使用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

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

从卢克的答案来看:

作为参考,Spring Security 3.1有一个身份validation提供程序[专门用于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组中显然可能存在案例问题,只有2个单独的组仅按案例区分(即帐户和帐户)。

此外,语法还需要手动指定域控制器名称和端口,这使得冗余有点可怕。 当然有一种方法可以在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("", ""); authenticationProvider.setConvertSubErrorCodesToExceptions(true); authenticationProvider.setUseAuthenticationRequestCredentials(true); return authenticationProvider; } }