2008-09-17 71 views
27

我在写一个需要用户登录的Spring Web应用程序。我的公司有一个Active Directory服务器,我想利用这个目的。但是,我无法使用Spring Security连接到服务器。如何使用Spring Security对Active Directory服务器进行身份验证?

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

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

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

这里是我的与安全相关的豆类:

<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> 
+0

这不是一个真正的答案,而是一个澄清的问题 - 您是否已经将日志记录转换为Spring安全包的所有内容? – 2008-09-17 17:52:40

+0

我有日志记录开启了一切。没有看到任何正在记录的消息......我用我的Log4J配置更新了我的问题。 – Michael 2008-09-17 18:35:26

+0

告诉我这个所需的jar文件。请 – addy 2013-09-06 10:28:14

回答

35

我拥有了你做同样的敲打,我的头抵墙式体验,并最终写入,做一个LDAP自定义身份验证提供者针对Active Directory服务器进行查询。

所以我的安全相关的豆类:

<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可疑类:

/** 
* 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; 
    } 

} 

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

} 

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

} 

你会注意到,有一些你可能不需要的位。

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

此外,在LdapAuthenticationProvider.authenticate()中,我为所有登录用户分配了ROLE_USER角色 - 这就是让我在我的拦截url元素中测试该角色的原因。无论您想要测试哪种角色,您都希望进行匹配,甚至可以根据Active Directory组或其他角色分配角色。

最后,由此推论,我实现LdapAuthenticationProvider.authenticate()的方式为所有用户提供了有效的AD帐户,它们具有相同的ROLE_USER角色。显然,在该方法中,您可以对用户(即特定AD组中的用户?)执行进一步测试,并且甚至在准许用户访问全部之前分配角色,甚至测试某些条件。

2

只是把这个给了最新的状态。 Spring Security 3.0有一个complete package,默认实现专用于ldap-bind以及查询和比较认证。

0

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

0

卢克的回答以上:

仅供参考使用,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

我试图上述与春季安全3.1.1:有从LDAP一些轻微的变化 - 活性目录组用户是作为原始情况来通过的成员。

以前,在ldap下,这些组被大写,并带有“ROLE_”前缀,这使得他们很容易在项目中使用文本搜索来查找,但是如果出于某种奇怪的原因只有2个独立的组,按情况区分(即账户和账户)。

此外,语法还需要手动指定域控制器名称和端口,这使冗余有点可怕。当然有一种仰视的SRV DNS记录在Java领域中,即相当于一种方式(从桑巴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. 

其次是定期的查找:

$ 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之一。

0

作为卢克的回答以上:

的Spring Security 3.1对Active Directory中的身份验证专门供应商。

下面是使用ActiveDirectoryLdapAuthenticationProvider可以轻松完成的细节。

在资源。常规:

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

0

如果您在使用Spring 安全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("<domain>", "<url>"); 

    authenticationProvider.setConvertSubErrorCodesToExceptions(true); 
    authenticationProvider.setUseAuthenticationRequestCredentials(true); 

    return authenticationProvider; 
} 
} 
相关问题