Spring Security + LDAP + CustomLdapAuthoritiesPopulator + RememberMe

时间:2015-05-18 05:46:12

标签: java spring spring-security ldap remember-me

我对弹簧安全性有点问题:)

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

做了什么:

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

我的问题是: '记住我'工作正常,persistent_logins表创建成功,它存储令牌很好。但是当用户返回网站时,春季节目未被授权'页。

我认为这是因为'记住我'对我的自定义角色一无所知,并从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);

用户登录后,欢迎页面显示&#34; ROLE_USER&#34;,&#34; ROLE_ADMIN&#34;

用户返回后,403页显示&#34;&#34 ;; (无)

2 个答案:

答案 0 :(得分:1)

来自Spring Docs Remember-Me

  

如果您使用的身份验证提供程序不使用UserDetailsService(例如, LDAP提供程序),那么除非您还拥有应用程序上下文中的UserDetailsS​​ervice bean

applicationContext.xml中尝试添加: -

添加RoleVoter。来自Spring Docs

  

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

     

如果没有配置属性以角色前缀开头,则弃权。如果从角色前缀开始与GrantedAuthority完全匹配ConfigAttribute,则表示授予访问权限。如果与角色前缀不一致的ConfigAttribute没有完全匹配的GrantedAuthority,则表示拒绝访问。

     

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

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

添加AuthenticatedVoter。来自Soring Docs

  

如果ConfigAttribute.getAttribute()IS_AUTHENTICATED_FULLYIS_AUTHENTICATED_REMEMBERED存在IS_AUTHENTICATED_ANONYMOUSLY则投票。此列表按照最严格的检查顺序进行最严格的检查。

     

将检查当前的身份验证以确定主体是否具有特定级别的身份验证。 “FULLY”身份验证选项意味着用户已完全通过身份验证(即AuthenticationTrustResolver.isAnonymous(Authentication)为false且AuthenticationTrustResolver.isRememberMe(Authentication)为false)。如果通过remember-me对身份验证进行身份验证,则"REMEMBERED"将授予访问权限。如果通过remember-me,匿名或通过完全身份验证对主体进行身份验证,则"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:

  1. Spring Security with LDAP and Database roles
  2. Spring Security 3.1 - Implement UserDetailsService with Spring Data JPA
  3. 编辑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 /> 
    

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

答案 1 :(得分:0)

使用错误的解决方案

<security:ldap-user-service/>

如果您想从数据库应用自定义角色,请记住我&#39;功能。

仍然需要实现自定义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指出我正确的方式和惊人的帮助:)