Selection based AuthProvider Spring Security

Viewed 884

I have configured Spring security to work with both LDAP and DB based login. First it tries to login via LDAP and if required permissions is not there then it takes to username/password entry page.

<security:http auto-config="false" entry-point-ref="loginUrlAuthenticationEntryPoint">
    <security:custom-filter ref="customPreAuthFilter" position="PRE_AUTH_FILTER"/> // This is for LDAP
    <security:custom-filter ref="customAuthFilter" position="FORM_LOGIN_FILTER"/> // This is for DB Based

    /** intercept urls 

    **/

</security:http>

I want to add a new screen on the top and user need to select between the two button LDAP or username/pass. How do I proceed?

The data to be accessed is the same url i.e. /home but both ldap and DB users should be able to access.

1 Answers

If you look at code in UsernamePasswordAuthenticationFilter there is setDetails method.

from docs:

Provided so that subclasses may configure what is put into the authentication request's details property.

Idea from here Provision to change ldap/Ad provider url at run time

You can set the details like authtype here and use it authentication provider, But to achieve the things you would lik adds little more work.

Adding details and hope it helps.

CustomUsernamePasswordAuthenticationFilter.java

import javax.servlet.http.HttpServletRequest;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.AuthenticationDetailsSource;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.stereotype.Component;

@Component
public class CustomUsernamePasswordAuthenticationFilter extends UsernamePasswordAuthenticationFilter {

      private static final Logger logger = LoggerFactory.getLogger(CustomUsernamePasswordAuthenticationFilter.class);

      @Autowired
      @Override
    public void setAuthenticationManager(AuthenticationManager authenticationManager) {
        // TODO Auto-generated method stub
        super.setAuthenticationManager(authenticationManager);
    }

     @Autowired 
      @Override
    public void setAuthenticationDetailsSource(
            AuthenticationDetailsSource<HttpServletRequest, ?> authenticationDetailsSource) {
        super.setAuthenticationDetailsSource(authenticationDetailsSource);
    }

      @Override
    protected void setDetails(HttpServletRequest request, UsernamePasswordAuthenticationToken authRequest) {
        String authType = request.getParameter("authType");
        logger.info("authType {} ",authType);
        authRequest.setDetails(authenticationDetailsSource.buildDetails(request));
    }

}

But this is not sufficient you would need to extend WebAuthenticationDetails.

Reason is WebAuthenticationDetails provides only remote IP address and sessionId so, to add authType we need to extend this class.

You have to extend WebAuthenticationDetailsSource to return CustomAuthenticationDetails as shown below.

CustomAuthenticationDetails.java

public class CustomAuthenticationDetails extends WebAuthenticationDetails{


    private  final String authType;

    public CustomAuthenticationDetails(HttpServletRequest request) {
        super(request);
        authType = request.getParameter("authType");
    }

    public String getAuthType() {
        return authType;
    }
}

CustomWebAuthenticationDetailsSource.java

public class CustomWebAuthenticationDetailsSource extends WebAuthenticationDetailsSource {

    @Override
    public WebAuthenticationDetails buildDetails(HttpServletRequest context) {
        return new CustomAuthenticationDetails(context);
    }

}

Please note these classes for demo purpose only.

Need to autowire actual authentication providers in these classes.

import java.util.Arrays;
import java.util.List;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.authentication.dao.DaoAuthenticationProvider;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.ldap.authentication.LdapAuthenticationProvider;
import org.springframework.stereotype.Component;

@Component
public class AuthenicationProviderJdbcLdapImpl implements AuthenticationProvider{

    // you need to autowire jdbc auth provider
    @Autowired(required = false)
    DaoAuthenticationProvider authenticationProvider;

    //you need to autowire ldap auth provider
    @Autowired(required = false)
    LdapAuthenticationProvider ldapAuthenticationProvider;



    protected static class User{
        private final String userId;
        private final String password;
        public User(String userId,String password) {
            this.userId = userId;
            this.password = password;
        }
        public String getUserId() {
            return userId;
        }
        public String getPassword() {
            return password;
        }
        @Override
        public String toString() {
            return "User [userId=" + userId + ", password=" + password + "]";
        }
    }

    private final static Logger logger = LoggerFactory.getLogger(AuthenicationProviderJdbcLdapImpl.class);
    private static final List<User> users1 = Arrays.asList(new User("admin1", "password"),new User("admin2", "password"));
    private static final List<User> users2 = Arrays.asList(new User("admin3", "password"),new User("admin4", "password"));

    @Override
    public Authentication authenticate(Authentication authentication) throws AuthenticationException {

        CustomAuthenticationDetails details = (CustomAuthenticationDetails) authentication.getDetails();

        String authType = details.getAuthType();
        logger.info("authType {}",authType);
        if("jdbc".equalsIgnoreCase(authType)) {
            logger.info("perfrom jdbc authentication");

            //perform your authentication using jdbc
            //the below is just for explaination

            return performAuthentication(authentication, users1);

        }else if("ldap".equalsIgnoreCase(authType)) {
            logger.info("perfrom ldap authentication");

            //perform your authentication using ldap
            //the below is just for explaination

            return performAuthentication(authentication, users2);

        }
        return null;
    }

    private Authentication performAuthentication(Authentication authentication,List<User> users) {
        String credential =  (String) authentication.getCredentials();
        String userId = authentication.getName();
        for(User user: users) {
            if(user.getUserId().equals(userId)&& user.getPassword().equals(credential)) {
                authentication = new UsernamePasswordAuthenticationToken(authentication.getPrincipal(), authentication.getCredentials(),authentication.getAuthorities());

                return authentication;
            }
        }
        return null;
    }
    @Override
    public boolean supports(Class<?> authentication) {
        return authentication.equals(UsernamePasswordAuthenticationToken.class);
    }



}

If you would need to redirect different login page (not sure, if you have the requirement) you register AuthenticationFailureHandler shown in security config. Here it is redirected to login and login1 based on condition.

http.failureHandler(new AuthenticationFailureHandler() {

                        @Override
                        public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response,
                                AuthenticationException exception) throws IOException, ServletException {
                            String whichPage = request.getParameter("whichPage");
                            System.out.println("inside login failure handler "+whichPage);
                            if("login1".equals(whichPage)) {
                                response.sendRedirect(request.getContextPath() +"/login1");
                            }else {
                                response.sendRedirect(request.getContextPath() +"/login");
                            }
                        }
                    })

WebSecurityConfig.java

import java.io.IOException;


import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.springframework.beans.factory.annotation.Autowired;

import org.springframework.context.annotation.Bean;

import org.springframework.context.annotation.Configuration;

import org.springframework.security.authentication.AuthenticationManager;

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.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.core.AuthenticationException;

import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.web.authentication.AuthenticationFailureHandler;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;

@Configuration
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

    @Autowired
    private UserDetailsService userDetailsService;

    @Bean
    public BCryptPasswordEncoder bCryptPasswordEncoder() {
        return new BCryptPasswordEncoder();
    }

    @Bean
    public AuthenticationManager getAuthenticationManager() throws Exception {
        return super.authenticationManagerBean();
    }


    @Autowired
    AuthenticationSuccessHandler authenticationSuccessHandler;

    @Autowired()
    AuthenicationProviderJdbcImpl authenicationProviderJdbcImpl;

    @Autowired()
    AuthenicationProviderLdapImpl authenicationProviderLdapImpl;


    @Autowired
    CustomUsernamePasswordAuthenticationFilter customUsernamePasswordAuthenticationFilter;


    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.addFilterAt(customUsernamePasswordAuthenticationFilter, UsernamePasswordAuthenticationFilter.class);

        http
                .authorizeRequests()
                    .antMatchers("/resources/**", "/registration","/login1").permitAll()
                    .anyRequest().authenticated()
                    .and()
                .formLogin()
                    .loginPage("/login")
                    .permitAll()//.successHandler(authenticationSuccessHandler)
                    .failureHandler(new AuthenticationFailureHandler() {

                        @Override
                        public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response,
                                AuthenticationException exception) throws IOException, ServletException {
                            String whichPage = request.getParameter("whichPage");
                            System.out.println("inside login failure handler "+whichPage);
                            if("login1".equals(whichPage)) {
                                response.sendRedirect(request.getContextPath() +"/login1");
                            }else {
                                response.sendRedirect(request.getContextPath() +"/login");
                            }
                        }
                    })
                    .and()
                .logout()
                    .permitAll();
    }

   @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
       auth.authenticationProvider(authenicationProviderLdapImpl).authenticationProvider(authenicationProviderJdbcImpl);

    }



    @Autowired
    public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
        /*auth.userDetailsService(userDetailsService)
        .passwordEncoder(bCryptPasswordEncoder());*/

    }
}

The below is from logs when authType = jdbc or authType=ldap

login called
2018-11-23 17:45:25.606  INFO 7232 --- [nio-8080-exec-6] stomUsernamePasswordAuthenticationFilter : authType jdbc 
2018-11-23 17:45:25.606  INFO 7232 --- [nio-8080-exec-6] c.t.a.AuthenicationProviderJdbcLdapImpl  : authType jdbc
2018-11-23 17:45:25.606  INFO 7232 --- [nio-8080-exec-6] c.t.a.AuthenicationProviderJdbcLdapImpl  : perfrom jdbc authentication
login called
login1 called
login1 called
2018-11-23 17:45:42.435  INFO 7232 --- [nio-8080-exec-5] stomUsernamePasswordAuthenticationFilter : authType ldap 
2018-11-23 17:45:42.435  INFO 7232 --- [nio-8080-exec-5] c.t.a.AuthenicationProviderJdbcLdapImpl  : authType ldap
2018-11-23 17:45:42.435  INFO 7232 --- [nio-8080-exec-5] c.t.a.AuthenicationProviderJdbcLdapImpl  : perfrom ldap authentication   returning true in ldap
Related