Spring 安全性 + LDAP + CustomLdapAuthoritiesPopulator + RememberMe

Spring Security + LDAP + CustomLdapAuthoritiesPopulator + RememberMe

我在 spring 安全方面有点问题 :)

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

做了什么:

我的问题是: 'Remember me' 工作正常,persistent_logins table 创建成功,它可以很好地存储令牌。但是当用户 returns 访问网站时,spring 显示 'not authorized' 页面。

我认为这是因为 'Remember me' 对我的自定义角色以及从 LDAP 获取角色一无所知。

问题是:如何告诉'Remember me'通过我的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}"/>

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

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

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

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

来自Spring Docs Remember-Me

If you are using an authentication provider which doesn't use a UserDetailsService (for example, the LDAP provider) then it won't work unless you also have a UserDetailsService bean in your application context.

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

添加RoleVoter。来自 Spring 文档

Votes if any ConfigAttribute.getAttribute() starts with a prefix indicating that it is a role. The default prefix string is ROLE_, but this may be overridden to any value. It may also be set to empty, which means that essentially any attribute will be voted on. As described further below, the effect of an empty prefix may not be quite desirable.

Abstains from voting if no configuration attribute commences with the role prefix. Votes to grant access if there is an exact matching GrantedAuthority to a ConfigAttribute starting with the role prefix. Votes to deny access if there is no exact matching GrantedAuthority to a ConfigAttribute starting with the role prefix.

All comparisons and prefixes are case sensitive.

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

添加AuthenticatedVoter。来自 Soring 文档

Votes if a ConfigAttribute.getAttribute() of IS_AUTHENTICATED_FULLY or IS_AUTHENTICATED_REMEMBERED or IS_AUTHENTICATED_ANONYMOUSLY is present. This list is in order of most strict checking to least strict checking.

The current Authentication will be inspected to determine if the principal has a particular level of authentication. The "FULLY" authenticated option means the user is authenticated fully (i.e. AuthenticationTrustResolver.isAnonymous(Authentication) is false and AuthenticationTrustResolver.isRememberMe(Authentication) is false). The "REMEMBERED" will grant access if the principal was either authenticated via remember-me OR is fully authenticated. The "ANONYMOUSLY" will grant access if the principal was authenticated via remember-me, OR anonymously, OR via full authentication.

All comparisons and prefixes are case sensitive.

<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:

  1. Spring Security with LDAP and Database roles
  2. 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 to include any custom business logic you wish to run when a user returns to your site and is ‘remembered’ by the application.

别忘了加上:

<custom-authentication-provider /> 

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

使用错误的解决方案

<security:ldap-user-service/>

如果您想为 'Remember me' 功能应用数据库中的自定义角色。

仍然需要实现自定义 UserDetailsS​​ervice 并从“记住我”部分引用它。

<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 为我指明了正确的道路并提供了惊人的帮助:)