Spring Security + LDAP + CustomLdapAuthoritiesPopulator + RememberMe

I have a little spring security issue :)

What is my goal: Configure LDAP authentication with custom roles, fetch from database and memorize functionality to me.

What is done:

  • LDAP Auth: OK
  • Custom Roles for AD Users from Database: OK
  • Remember me: FAIL

My problem: "Remember me" works great, persistent_logins table created successfully, it stores tokens fine. But when the user goes back to the website, spring shows the "not authorized" page.

I think this is happening because "Remember me" knows nothing about my custom roles and is pulling roles from LDAP.

Question: How do I say "Remember me" to get roles through my CustomLdapAuthoritiesPopulator ?

my 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}"/>

      

During debugging, when the user returns, the CustomLdapAuthoritiesPopulator is not called. I added code to check roles for a user (on the welcome page and on the 403 page).

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

      

After the user logs in, the welcome page displays "ROLE_USER", "ROLE_ADMIN"

After the user returns to the 403 page, "" is displayed; (Nothing)

+3


source to share


2 answers


From Spring Docs Remember-Me

If you are using an authentication provider that does not use UserDetailsService

(like an LDAP provider ) then it won't work unless you also have a UserDetailsService bean in the context of your application .

Try adding below to applicationContext.xml

: -

Add RoleVoter . From Spring Docs

Voices, if any, ConfigAttribute.getAttribute()

start with a prefix indicating that this is a role. The default prefix string ROLE_

, but this can be overridden to any value. It can also be set to empty, which means that essentially any attribute will vote. As described below, the effect of an empty prefix may not be very desirable.

Abstains from voting if the configuration attribute does not start with a role prefix. Votes to grant access if there is an exact match GrantedAuthority

and ConfigAttribute

starting with the role prefix. Votes deny access unless GrantedAuthority matches the ConfigAttribute exactly, starting with the role prefix.

All comparisons and prefixes are case sensitive.

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

      

Add AuthenticatedVoter . From Soring Docs

Voices if a ConfigAttribute.getAttribute()

of IS_AUTHENTICATED_FULLY

or IS_AUTHENTICATED_REMEMBERED

or is present IS_AUTHENTICATED_ANONYMOUSLY

. This list is in order of most stringent verification for minimum stringent verification.

The current authentication will be checked to determine if the principal has a specific level of authentication. The "FULLY" option is authenticated because the user is fully authenticated (ie, AuthenticationTrustResolver.isAnonymous(Authentication)

false, but AuthenticationTrustResolver.isRememberMe(Authentication)

false). "REMEMBERED"

will grant access if the supervisor was either authenticated using the mem-me function OR, or fully authenticated. "ANONYMOUSLY"

will grant access if the principal was authenticated through mem-mem, OR anonymously, OR through full authentication.

All comparisons and prefixes are case sensitive.

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

      

Now, configure Spring AccessDecisionManager

, which uses the above two voters, to determine if the user has been given the correct access to the resource.

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

      

For more information: Visit Spring security 3 remember me with LDAP authentication




EDIT 1:


EDIT 2:

Below is the source originally posted on Configuring Spring Security Form Login with Remember Me enabled .

Create custom 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

enable any custom business logic that you want to run when the user returns to your site and is "remembered by the application".

Don't forget to add:

<custom-authentication-provider /> 

      

This ensures that remember that I am indeed being used as an authentication provider - that is, when your user returns a message previously requested to remember, it adds, remember me, to the list of providers that validate authenticates.

+1


source


Wrong solution to use

<security:ldap-user-service/>

      

If you want to apply custom roles from the database for the "Remember me" functionality.

You still need to implement a custom UserDetailsService and access it from the mem-me section.

<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"/>

      



A bit tricky but works, my database stores every username because it stores roles. This way I can check any user without 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);
}
}

      

The code demonstrates the main idea.

Thanks a lot @ OO7 for pointing me to the right path and awesome help :)

0


source







All Articles