turgos
turgos

Reputation: 1614

Custom authorities with LDAP authentication

I’ve found few Spring XML Configuration examples for logging in with LDAP and configuring the authorities of the logged in user with the help of a custom method and not through LDAP. Unfortunately, I could not find any Spring Boot example with annotations.

In our case, there is a central LDAP repository in which the usernames and passwords of the users are stored, but the groups of the users are not stored there.

I appreciate any example or reference. Thank you in advance.

Upvotes: 4

Views: 6947

Answers (2)

turgos
turgos

Reputation: 1614

Below you can find our final implementation at the project. Basic flow is:

a)Check the user id and roles during the authentication. If user is not defined or does not have the roles for the application, do not authenticate the user.

b)if user pass the database check, continue with ldap authentication.

c)Merge the roles coming from database with ldap to be used during the application.

public class WebSecurityConfiguration extends WebSecurityConfigurerAdapter implements InitializingBean {
...
@Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
auth
  .authenticationProvider(this.ldapAndDatabaseAuthenticationProvider());
}

@Bean(name="ldapAuthenticationProvider")
public AuthenticationProvider ldapAndDatabaseAuthenticationProvider(){
  LdapUserDetailsMapper userDetailsMapper = new LdapUserDetailsMapper();
  userDetailsMapper.setRoleAttributes(new String[]{"groupMembership"});

  LdapAndDatabaseAuthenticationProvider provider = 
      new LdapAndDatabaseAuthenticationProvider(this.ldapAuthenticator(), this.ldapAuthoritiesPopulator());
  provider.setUserDetailsContextMapper(userDetailsMapper);

  return provider;
}

@Bean( name = "ldapAuthoritiesPopulator" )
public LdapAndDatabaseAuthoritiesPopulator ldapAuthoritiesPopulator(){
  return new LdapAndDatabaseAuthoritiesPopulator(this.contextSource(), "");
}

@Bean( name = "ldapAuthenticator" )
public LdapAuthenticator ldapAuthenticator() {

    BindAuthenticator authenticator = new BindAuthenticator(   this.contextSource() );
  authenticator.setUserDnPatterns(new String[]{"cn={0},ou=prod,o=COMP"});

  return authenticator;
}

@Bean( name = "contextSource" )
public DefaultSpringSecurityContextSource contextSource() {

    DefaultSpringSecurityContextSource contextSource =
          new DefaultSpringSecurityContextSource( ldapUrl );
    return contextSource;
}

Here is how additional roles populator (LdapAndDatabaseAuthoritiesPopulator ) implemented.

public class LdapAndDatabaseAuthoritiesPopulator extends DefaultLdapAuthoritiesPopulator{

  public LdapAndDatabaseAuthoritiesPopulator(ContextSource contextSource, String groupSearchBase) {
    super(contextSource, groupSearchBase);
  }

  protected Set<GrantedAuthority> getAdditionalRoles(DirContextOperations user,
      String username) {
    Set<GrantedAuthority> mappedAuthorities = new HashSet<GrantedAuthority>();

    /* Add additional roles from other sources for this user*/
    /* below add is just an example of how to add a role */
    mappedAuthorities.add(
        new GrantedAuthority() { 
          private static final long serialVersionUID = 3618700057662135367L;

          @Override 
          public String getAuthority() { 
            return "ROLE_MYAPP_USER"; //this is just a temporary role we are adding as example. get the roles from database.
          } 

         @Override
         public String toString(){
          return this.getAuthority();
         }
        });


    for (GrantedAuthority granted : mappedAuthorities) {
      log.debug("Authority : {}", granted.getAuthority().toString());
    }

    return mappedAuthorities;
  }

}

Below is how the Custom Ldap authentication provider (LdapAndDatabaseAuthenticationProvider) implemented to check if user has required roles defined in the database to access the application. If user is not in the database or roles are missing, authentication will throw account DisabledException.

franDays also suggested to use "Custom Authentication Provider". I would like to give him a credit.

public class LdapAndDatabaseAuthenticationProvider extends LdapAuthenticationProvider{

  public LdapAndDatabaseAuthenticationProvider(LdapAuthenticator authenticator, LdapAuthoritiesPopulator authoritiesPopulator) {
    super(authenticator, authoritiesPopulator);
  }

  @Override
  protected DirContextOperations doAuthentication(
      UsernamePasswordAuthenticationToken authentication) {

    log.debug("Checking if user <{}> is defined at database to use this application.", authentication.getName());

    // Here is the part we need to check in the database if user has required role to log into the application.
    // After check if user has the role, do nothing, otherwise throw exception like below example.    
    boolean canUserAuthenticate = isActiveUserExist(authentication.getName());
    log.debug("canUserAuthenticate: {}", canUserAuthenticate);

    if (!canUserAuthenticate)
      throw new DisabledException("User does not have access to Application!");

    return super.doAuthentication(authentication);
  }


  private boolean isActiveUserExist(String userId) {

  // Do your logic here are return boolean value...

  }

Upvotes: 3

franDayz
franDayz

Reputation: 943

You can implement your own AuthenticationProvider. The authenticate method would query an LdapTemplate and upon successful attempt then query the groups from wherever they are stored. It could look like below:

public class CustomAuthenticationProvider implements AuthenticationProvider {

  private LdapTemplate ldapTemplate;
  private UserRepository userRepository;

  @Override
  public Authentication authenticate(Authentication authentication) throws AuthenticationException {
      String username = (String) authentication.getPrincipal();
      boolean success = ldapTemplate.authenticate(...);
      if (!success) {
          throw new BadCredentialsException("Wrong username or password");
      }
      User user = userRepository.findByUsername(username); 
      if (user == null) {
          throw new BadCredentialsException("Username not known by the application");
      }
      return new CustomAuthentication(username, user.getRoles());
  }
}

I omitted the initialization of the LdapTemplate because it depends on the specifics of your case. Same thing for the Authentication object you return for which you would need to implement a class and allow a way to build an instance by passing the username and password.

If you need guidance on how to register your auth provider using java config, this post might help: Custom Authentication provider with Spring Security and Java Config

Upvotes: 1

Related Questions