javaspringspring-securityldapspring-security-ldap

Spring Security LDAP Authentication and gather user details from local database


In summary, user is being authenticated, but I do appear to actually have logged into the users account.

I'm currently working on implementing LDAP authentication on a project. It appears that the authentication portion of things are working in the sense that my application does accept the correct credentials. The issue I'm having is that I cant seem to access 'principal' in my jsp views. (I was able to access all of this before making the switch to LDAP). When running a trace my CustomUserDetails service is querying and pulling the correct account information. Any assistance is appreciated

This will display the proper username:

<sec:authorize access="isAuthenticated()">
   <h2><sec:authentication property="name"/></h2>
</sec:authorize>

This does not (it did work before LDAP)

<sec:authorize access="isAuthenticated()">
   <h2><sec:authentication property="principal.firstName"/></h2>
</sec:authorize>

Relevant Code SecurityConfig.java

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.builders.WebSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.ldap.authentication.UserDetailsServiceLdapAuthoritiesPopulator;
import org.springframework.security.web.access.expression.DefaultWebSecurityExpressionHandler;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;

@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter{
    @Autowired
    private CustomUserDetailsService userDetailsService;

    @Bean
    public CustomSaltSource customSaltSource(){ return new CustomSaltSource();}

    @Bean
    public AuthenticationSuccessHandler myAuthenticationSuccessHandler(){
        return new AuthenticationSuccessHandler();
    }



    @Autowired
    void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
        auth.ldapAuthentication().contextSource()
                .url("ldap://bar.foo.com")
                .port(####)
                .and()
                .userDnPatterns("cn={0},cn=users,dc=ms,dc=ds,dc=foo,dc=com")
                .ldapAuthoritiesPopulator(new UserDetailsServiceLdapAuthoritiesPopulator(userDetailsService));
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {

        http.authorizeRequests()
                .antMatchers("/skins/**", "/css/**", "/**/laggingComponents", "/assets/**").permitAll().and()
                .formLogin().loginPage("/login").permitAll().defaultSuccessUrl("/", true).successHandler(myAuthenticationSuccessHandler())
                .and().logout().logoutRequestMatcher(new AntPathRequestMatcher("/logout")).deleteCookies("JSESSIONID").permitAll()
                .and().authorizeRequests().antMatchers("/api/**").anonymous()
                .and().authorizeRequests().anyRequest().authenticated().and().rememberMe().key("KEY").userDetailsService(userDetailsService);
    }

    @Override
    public void configure(WebSecurity web) throws Exception {
        DefaultWebSecurityExpressionHandler handler = new DefaultWebSecurityExpressionHandler();
        handler.setPermissionEvaluator(new PermissionEvaluator());
        web.expressionHandler(handler);
        web.ignoring().antMatchers( "/skins/**", "/css/**", "/api/**", "/assets/**", "/health"); //"/**/test/**"
    }
}

CustomUserDetaulsService.java

import org.hibernate.Session;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service;

import java.util.Set;

@Service
public class CustomUserDetailsService implements UserDetailsService{

    @Override
    public CustomUserDetails loadUserByUsername(String username) throws UsernameNotFoundException{
        Session session = DBFactory.factory.openSession();
        User user = (User) session.createQuery("from User where userName =:userName")
                .setParameter("userName", username).uniqueResult();
        if(user == null){
            throw new UsernameNotFoundException("User Not Found");
        }
        //Needed to initialize permissions
        Set<Role> roles = user.getRoles();
        int i = roles.size();
        for(Role role: roles){
            int j = role.getPermissions().size();
        }
        CustomUserDetails userDetails = new CustomUserDetails(user);

        session.close();
        return userDetails;
    }


}

Solution

  • If I'm not wrong, You switched to Ldap Authorization, set url and DN patterns but still provide userDetailsService which search user in database. You need to set UserDetailsContextMapper by implementing the interface and creating your custom one. This will map data from ldap directory context to your custom UserDetails and return it through mapUserFromContext method.

    Here is an example CustomUserDetailsContextMapper:

    public class CustomUserDetailsContextMapper implements UserDetailsContextMapper {
    
    
        private LdapUser ldapUser = null;
        private String commonName;
    
        @Override
        public UserDetails mapUserFromContext(DirContextOperations ctx, String username, Collection<? extends GrantedAuthority> authorities) {
            Attributes attributes = ctx.getAttributes();
            UserDetails ldapUserDetails = (UserDetails) super.mapUserFromContext(ctx,username,authorities);
            try {
                commonName = attributes.get("cn").get().toString();
            } catch (NamingException e) {
                e.printStackTrace();
            }
            ldapUser = new LdapUser(ldapUserDetails);
            ldapUser.setCommonName(commonName);
            return ldapUser;
        }
    
        @Override
        public void mapUserToContext(UserDetails user, DirContextAdapter ctx) {
    
        }
    }
    

    My custom LdapUser:

    public class LdapUser implements UserDetails
    {
        private String commonName;
        private UserDetails ldapUserDetails;
    
        public LdapUser(LdapUserDetails ldapUserDetails) {
            this.ldapUserDetails = ldapUserDetails;
        }
    
        @Override
        public String getDn() {
            return ldapUserDetails.getDn();
        }
    
        @Override
        public void eraseCredentials() {
    
        }
    
        @Override
        public Collection<? extends GrantedAuthority> getAuthorities() {
            return ldapUserDetails.getAuthorities();
        }
    
        @Override
        public String getPassword() {
            return ldapUserDetails.getPassword();
        }
    
        @Override
        public String getUsername() {
            return ldapUserDetails.getUsername();
        }
    
        @Override
        public boolean isAccountNonExpired() {
            return ldapUserDetails.isAccountNonExpired();
        }
    
        @Override
        public boolean isAccountNonLocked() {
            return ldapUserDetails.isAccountNonLocked();
        }
    
        @Override
        public boolean isCredentialsNonExpired() {
            return ldapUserDetails.isCredentialsNonExpired();
        }
    
        @Override
        public boolean isEnabled() {
            return ldapUserDetails.isEnabled();
        }
    }
    

    Then set CustomUserDetailsContextMapper in auth configuration. This is how you will be able to get your user from authentication.getPrincipal(). I hope I correctly understand your problem and answered.