javaspring-securityspring-security-ldap

multiple authentication mechanisms in a single app using java config


Currently I have a single authentication mechanism in my application which is to use LDAP for authentication and authorization. My security configuration looks like this

@Configuration
@EnableWebMvcSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {

@Override
protected void configure(HttpSecurity http) throws Exception {
    http
        .csrf().disable()
        .authorizeRequests()
            .anyRequest().fullyAuthenticated()
            .and()
            .httpBasic();
}

@Configuration
protected static class AuthenticationConfiguration extends GlobalAuthenticationConfigurerAdapter {

    @Value("${ldap-${env}.manager.dn}")
    private String managerDn;

    @Value("${ldap-${env}.manager.pass}")
    private String managerPass;

    @Value("${ldap-${env}.server.url}")
    private String url;

    @Value("${ldap.password.attribute:userPassword}")
    private String passwordAttr;

    @Override
    public void init(AuthenticationManagerBuilder auth) throws Exception {
        auth.ldapAuthentication().userDnPatterns("uid={0},ou=people").groupSearchBase("ou=groups")
                .groupSearchFilter("(member={0})").userSearchBase("ou=people").userSearchFilter("(uid={0})")
                .userDetailsContextMapper(new CustomLdapPersonContextMapper())
                // .passwordCompare()
                // .passwordAttribute(passwordAttr)
                // .passwordEncoder(new PlaintextPasswordEncoder())
                // .and()
                .contextSource().managerDn(managerDn).managerPassword(managerPass).url(url);
    }
}
}

There are situations though where users might come in with a session token which can authentication from a session key server and valid token returns a username which can then be used to load authrization information from LDAP for that user. So my second authentication mechanism should happen first where if a session token is present in http headers it should perform the token authentication and then ldap lookup and if no session token is present it should just fall to current authentication mechanism. How can I add this second layer of authentication.


Solution

  • I spent quite some time wrapping my head around spring-security when using pure java configuration. There are a few steps involved in getting this to work. It should be something along these lines. The basic process is as follows:

    I have been using this setup for a while supporting various authentication methods (signed request, username/password, oauth etc) and it works quite well.

    You can also pass AuthenticationSuccessHandler's and AuthenticationFailuersHandler's to the custom security filters to provide custom redirection strategies and failure handling.

    Also be sure to setup the ant matchers in the filter's constructors to control what url patterns the filters apply too. For example, an ldap request filter would probably need to be check with any request "/*" whereas a username/password filter can just be checked on POST's to /login or something similar.

    Example Code:

    1. Create custom AuthenticationToken's for each type of authentication you want to support

      public class LDAPAuthorizationToken extends AbstractAuthenticationToken { private String token;

       public LDAPAuthorizationToken( String token ) {
           super( null );
           this.token = token;
       }
      
       public Object getCredentials() {
           return token;
       }
      
       public Object getPrincipal() {
           return null;
       }
      

      }

      public class OTPAuthorizationToken extends UsernamePasswordAuthenticationToken { private String otp;

       public OTPAuthorizationToken( String username, String password, String otp ) {
           super( username, password );
           this.otp = otp;
       }
      
       public String getOTP() {
           return otp;
       }
      

      }

    2. Create custom security filters for each type

      public class LDAPAuthorizationFilter extends AbstractAuthenticationProcessingFilter { @Autowired private UserDetailsService userDetailsService;

       public LDAPAuthorizationFilter() {
           super( "/*" ); // allow any request to contain an authorization header
       }
      
       public Authentication attemptAuthentication( HttpServletRequest request, HttpServletResponse response ) throws AuthenticationException
       {
      
           if ( request.getHeader( "Authorization" ) == null ) {
               return null; // no header found, continue on to other security filters
           }
      
           // return a new authentication token to be processed by the authentication provider
           return new LDAPAuthorizationToken( request.getHeader( "Authorization" ) );
       }
      

      }

      public class OTPAuthorizationFilter extends AbstractAuthenticationProcessingFilter { @Autowired private UserDetailsService userDetailsService;

       public OTPAuthorizationFilter() {
           super( "/otp_login" );
       }
      
       public Authentication attemptAuthentication( HttpServletRequest request, HttpServletResponse response ) throws AuthenticationException
       {
      
           if ( request.getParameter( "username" ) == null || request.getParameter( "password" ) == null || request.getParameter( "otp" ) == null ) {
               return null;
           }
      
           // return a new authentication token to be processed by the authentication provider
           return new OTPAuthorizationToken( request.getParameter( "username" ), request.getParameter( "password" ), request.getParameter( "otp" ) );
       }
      

      }

    3. Create custom AuthenticationProviders

      public class LDAPAuthenticationProvider implements AuthenticationProvider {

       @Autowired
       private MyAuthenticationService sampleService;
      
       @Override
       public Authentication authenticate( Authentication authentication ) throws AuthenticationException {
           LDAPAuthorizationToken auth = (LDAPAuthorizationToken)authentication;
      
           String username = sampleService.verifyToken( auth.getCredentials() );
           if ( username == null ) {
               throw new LoginException( "Invalid Token" );
           }
      
           auth.setAuthenticated( true );
      
           return auth;
       }
      
       @Override
       public boolean supports( Class<?> authentication ) {
           if ( authentication.isAssignableFrom( LDAPAuthorizationToken.class ) ) {
               return true;
           }
           return false;
       }
      

      }

      public class OTPAuthenticationProvider implements AuthenticationProvider {

       @Autowired
       private MyAuthenticationService sampleService;
      
       @Override
       public Authentication authenticate( Authentication authentication ) throws AuthenticationException {
           OTPAuthorizationToken auth = (OTPAuthorizationToken)authentication;
      
           String error = sampleService.loginWithOTP( auth.getPrincipal(), auth.getCredentials(), auth.getOTP() );
           if ( error != null ) {
               throw new LoginException( error );
           }
      
           auth.setAuthenticated( true );
      
           return auth;
       }
      
       @Override
       public boolean supports( Class<?> authentication ) {
           if ( authentication.isAssignableFrom( OTPAuthorizationToken.class ) ) {
               return true;
           }
           return false;
       }
      

      }

    4. Configure spring security

      public class SecurityConfiguration extends WebSecurityConfigurerAdapter { @Override protected void configure( HttpSecurity http ) throws Exception { // configure filters http.addFilterBefore( new LDAPAuthorizationFilter(), UsernamePasswordAuthenticationFilter.class ); http.addFilterBefore( new OTPAuthorizationFilter(), UsernamePasswordAuthenticationFilter.class );

           // configure authentication providers
           http.authenticationProvider( new LDAPAuthenticationProvider() );
           http.authenticationProvider( new OTPAuthenticationProvider() );
      
           // disable csrf
           http.csrf().disable();
      
           // setup security
           http.authorizeRequests()
               .anyRequest()
                   .fullyAuthenticated()
                   .and().httpBasic();
       }
      

      }