Spring Security + LDAP + CustomLdapAuthoritiesPopulator + RememberMe

Posted

技术标签:

【中文标题】Spring Security + LDAP + CustomLdapAuthoritiesPopulator + RememberMe【英文标题】: 【发布时间】:2015-07-29 12:36:15 【问题描述】:

我对 Spring Security 有一点问题 :)

我的目标是什么: 使用自定义角色配置 LDAP 身份验证,从数据库中获取,并记住我的功能。

做了什么:

LDAP 身份验证:确定 数据库中 AD 用户的自定义角色:确定 记住我:失败

我的问题是: “记住我”工作正常,persistent_logins 表创建成功,它可以很好地存储令牌。但是当用户返回网站时,spring 显示“未授权”页面。

我认为这是因为“记住我”对我的自定义角色以及从 LDAP 获取角色一无所知。

问题是:如何告诉“记住我”以通过我的 CustomLdapAuthoritiesPopulator 获得角色?

我的 applicationContext.xml

<bean id="contextSource" class="org.springframework.security.ldap.DefaultSpringSecurityContextSource">
    <constructor-arg value="ldap://ldap.forumsys.com:389"/>
    <property name="userDn" value="cn=read-only-admin,dc=example,dc=com"/>
    <property name="password" value="password"/>
</bean>

<bean name="myDataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
    <property name="driverClassName" value="com.mysql.jdbc.Driver"/>
    <property name="url" value="jdbc:mysql://localhost:3306/db"/>
    <property name="username" value="username"/>
    <property name="password" value="password"/>
</bean>

<bean id="ldapAuthProvider" class="org.springframework.security.ldap.authentication.LdapAuthenticationProvider">
    <constructor-arg>
        <bean class="org.springframework.security.ldap.authentication.BindAuthenticator">
            <constructor-arg ref="contextSource"/>
            <property name="userDnPatterns">
                <list>
                    <value>uid=0,dc=example,dc=com</value>
                </list>
            </property>
        </bean>
    </constructor-arg>
    <constructor-arg>
        <bean class="my.extra.CustomLdapAuthoritiesPopulator"/>
    </constructor-arg>
</bean>

<bean id="tokenRepository"
      class="org.springframework.security.web.authentication.rememberme.JdbcTokenRepositoryImpl">
    <property name="createTableOnStartup" value="false"/>
    <property name="dataSource" ref="myDataSource"/>
</bean>

<security:authentication-manager>
    <security:authentication-provider ref="ldapAuthProvider"/>
</security:authentication-manager>

<security:http auto-config="true" use-expressions="true">
    <security:access-denied-handler error-page="/403"/>
    <security:intercept-url pattern="/login*" access="permitAll()"/>
    <security:intercept-url pattern="/favicon.ico" access="permitAll()"/>
    <security:intercept-url pattern="/resources/**" access="permitAll()"/>
    <security:intercept-url pattern="/**" access="hasRole('ROLE_USER')"/>
    <security:form-login login-page='/login' login-processing-url="/j_spring_security_check"
                         default-target-url="/" authentication-failure-url="/login?fail"/>
    <security:remember-me key="_spring_security_remember_me" token-validity-seconds="14400"
                          token-repository-ref="tokenRepository"
                          user-service-ref="ldapUserService"/>

</security:http>

<security:ldap-user-service id="ldapUserService" server-ref="contextSource"
                            group-search-base="dc=example,dc=com"
                            group-search-filter="ou=0)"
                            user-search-base="dc=example,dc=com"
                            user-search-filter="uid=0"/>

在调试期间,当用户返回时,未调用 CustomLdapAuthoritiesPopulator。 我添加了代码来检查用户的角色(在欢迎页面和自定义 403 页面上)。

Collection<? extends GrantedAuthority> roles = SecurityContextHolder.getContext().getAuthentication().getAuthorities();
roles.forEach(System.out::println);

用户登录后,欢迎页面显示“ROLE_USER”、“ROLE_ADMIN”

用户返回后,403页面显示“”; (无)

【问题讨论】:

你得到上述问题的答案了吗?如果没有,请尝试我的回答并访问我提供的链接以获取有关 Remember-Me 身份验证 的更多信息。 在我的答案中尝试使用新链接。 我解决了这个问题,稍后会发布答案。 【参考方案1】:

来自Spring Docs Remember-Me

如果您使用的身份验证提供程序不使用 UserDetailsService(例如 LDAP 提供程序),那么 除非您还有 UserDetailsS​​ervice bean,否则它将无法工作在您的应用程序上下文中

尝试在下面添加在您的applicationContext.xml:-

添加RoleVoter。来自 Spring 文档

如果有ConfigAttribute.getAttribute() 以表明它是一个角色的前缀开头,则投票。默认前缀字符串是ROLE_,但它可以被覆盖为任何值。它也可以设置为空,这意味着基本上任何属性都将被投票。如下所述,空前缀的效果可能不太理想。

如果没有配置属性以角色前缀开头,则弃权。如果 GrantedAuthority 与以角色前缀开头的 ConfigAttribute 完全匹配,则投票授予访问权限。如果没有与以角色前缀开头的 ConfigAttribute 完全匹配的 GrantedAuthority,则投票拒绝访问。

所有比较和前缀都区分大小写。

<bean id="roleVoter" 
    class="org.springframework.security.access.vote.RoleVoter" p:rolePrefix="" />

添加AuthenticatedVoter。来自Soring Docs

如果存在IS_AUTHENTICATED_FULLYIS_AUTHENTICATED_REMEMBEREDIS_AUTHENTICATED_ANONYMOUSLY 中的ConfigAttribute.getAttribute(),则投票。此列表按最严格检查到最不严格检查的顺序排列。

将检查当前身份验证以确定主体是否具有特定级别的身份验证。 “FULLY”认证选项表示用户已完全认证(即AuthenticationTrustResolver.isAnonymous(Authentication) 为假,AuthenticationTrustResolver.isRememberMe(Authentication) 为假)。 "REMEMBERED" 将授予访问权限,如果主体通过记住我进行身份验证或完全通过身份验证。 "ANONYMOUSLY" 将授予访问权限,如果委托人通过记住我、或匿名或通过完全身份验证进行身份验证。

所有比较和前缀都区分大小写。

<bean id="authVoter" 
    class="org.springframework.security.access.vote.AuthenticatedVoter">               
</bean>

现在,配置一个 Spring AccessDecisionManager,它利用上面的两个投票者,以便确定用户是否被授予访问资源的正确权限

<bean id="accessDecisionManager" class="org.springframework.security.access.vote.ConsensusBased">
    <property name="allowIfAllAbstainDecisions" value="false" />
    <property name="decisionVoters">
        <list>
            <ref bean="roleVoter"  />
            <ref bean="authVoter" />
        </list>
    </property>
</bean>

更多详情:请访问Spring security 3 remember-me with LDAP authentication


编辑 1:

    Spring Security with LDAP and Database roles Spring Security 3.1 - Implement UserDetailsService with Spring Data JPA

编辑 2:

以下来源最初发布于Configuring Spring Security Form Login with Remember-Me Enabled。

创建自定义 RememberMeProcessingFilter:

public class MyRememberMeProcessingFilter extends RememberMeProcessingFilter  
    private myService; 

    @Override 
    protected void onSuccessfulAuthentication(HttpServletRequest request, HttpServletResponse response, Authentication authResult)  
        // perform some custom logic when the user has been 'remembered' & authenticated - e.g. update a login count etc 
        this.myService.doSomeCustomBusinessLogic(authResult.getName()); 

        super.onSuccessfulAuthentication(request, response, authResult); 
     
 

RememberMeProcessingFilter 包含您希望在用户返回您的站点并被应用程序“记住”时运行的任何自定义业务逻辑。

别忘了添加:

<custom-authentication-provider /> 

这确保记住我实际上被用作身份验证提供程序 - 即当您的用户返回之前要求被记住时,这会将记住我添加到检查用户是否存在的提供程序列表中已通过身份验证。

【讨论】:

提供的链接不包含任何与“LDAP Remember-me”实施相关的信息。而且我不知道如何将其应用于我的配置。此外,很多标记为“已弃用”的东西我正在使用 Spring Security 3.2.4.RELEASE 我已经更新了我的答案。请尝试此更新。 RoleVoter 将替换我的'use-expressions=true',我不想要它,我的控制器的所有方法都用@PreAuthorize('ExpressionHere') 注释。而且我不想删除前缀“ROLE_”。我的主要问题是:我正在使用 ,登录后 - 它会从我的数据库中为用户分配角色。但请记住,我对 CustomLdapAuthoritiesPopulator 一无所知,当用户返回时,我认为它无法正确分配角色。 你试过调试你的代码吗?当用户返回时您实际得到什么以及预期什么?您是否为CustomLdapAuthoritiesPopulator - getGrantedAuthorities(DirContextOperations userData, String username) 方法提供了自定义定义? 是的,试过了,我更新了我的问题。是的,我提供了 getGrantedAuthorities 的自定义定义。在该方法中,代码为角色调用数据库。【参考方案2】:

使用错误的解决方案

<security:ldap-user-service/>

如果您想为“记住我”功能应用数据库中的自定义角色。

仍然需要实现自定义的 UserDetailsS​​ervice 并从 remember-me 部分中引用它。

<security:http>
....
<security:remember-me key="_spring_security_remember_me" token-validity-seconds="14400"
                          token-repository-ref="tokenRepository"
                          user-service-ref="rememberMeUserDetailsService"/>
</security:http>

<bean id="rememberMeUserDetailsService" class="gpb.extra.RememberMeUserDetailsService"/>

这有点棘手,但有效,我的数据库存储每个用户名,因为它存储角色。所以,我可以检查任何没有 LDAP 的用户。

package gpb.extra;

import gpb.database.models.User;
import gpb.database.utils.HibernateUtils;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;

import java.util.Arrays;
import java.util.Collection;
import java.util.HashSet;
import java.util.List;

public class RememberMeUserDetailsService implements UserDetailsService 
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException 

    Collection<GrantedAuthority> authorities = new HashSet<>();
    List<User> users = HibernateUtils.get(User.class, username, "username");
    if (users.size() == 0) 
        throw new UsernameNotFoundException("User not found");
     else 
        User existingUser = users.get(0);
        if (!existingUser.getRoles().isEmpty()) 
            List<String> roles = Arrays.asList(existingUser.getRoles().split(","));
            roles.forEach(t -> authorities.add(new SimpleGrantedAuthority(t.trim())));
        
    

    boolean enabled = true;
    boolean accountNonExpired = true;
    boolean credentialsNonExpired = true;
    boolean accountNonLocked = true;

    return new org.springframework.security.core.userdetails.User(
            username, "password", enabled, accountNonExpired,
            credentialsNonExpired, accountNonLocked, authorities);


代码演示了主要思想。

非常感谢@OO7 为我指明了正确的道路并提供了惊人的帮助:)

【讨论】:

我的荣幸。我很高兴知道问题已经解决。 +1 提出这么好的问题。

以上是关于Spring Security + LDAP + CustomLdapAuthoritiesPopulator + RememberMe的主要内容,如果未能解决你的问题,请参考以下文章

如何使用 spring-security-core-ldap 插件在 grails 中实现 LDAP 身份验证?

Grails,Spring Security LDAP 插件

Spring Security和LDAP身份验证

如何将 Spring Security 从 ldap 更改为 ldap starttls

使用 Spring Security 的 ldap 身份验证

Spring Security 中的 LDAP + HttpBasicAuth 问题