我正在编写一个要求用户登录的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 =上下文;
}
}
code> pre>
您会注意到其中可能有一些不需要的位。
好。
例如,我的应用程序需要保留成功登录的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
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身份验证。