如何使用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; } }
- 带有Active Directory的JNDI PartialResultException
- 通过ssl作为匿名用户进行Active Directory身份validation
- 在Java中使用LDAP的最简单方法(Eclipse)
- Java LDAP – 将组添加到用户问题 – 错误代码53 – WILL_NOT_PERFORM
- 使用JAAS LdapLoginModule通过ActiveDirectory进行身份validation时遇到FailedLoginException
- 安全Java SOAP Web服务 – Active Directory身份validation信任
- 为什么JDK1.8.0u121无法找到kerberos default_tkt_enctypes类型? (KrbException:default_tkt_enctypes没有支持的默认etypes)
- 使用Java查找简单的Active Directory信息
- 密码的SPNEGO身份validation问题