Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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.

like image 353
adeelmahmood Avatar asked Sep 11 '14 18:09

adeelmahmood


People also ask

Can we have multiple WebSecurityConfigurerAdapter?

When using Java configuration, the way to define multiple security realms is to have multiple @Configuration classes that extend the WebSecurityConfigurerAdapter base class – each with its own security configuration. These classes can be static and placed inside the main config.

What is difference between AuthenticationManager and AuthenticationProvider?

Authentication Provider calls User Details service loads the User Details and returns the Authenticated Principal. Authentication Manager returns the Authenticated Object to Authentication Filter and Authentication Filter sets the Authentication object in Security Context .

What is UsernamePasswordAuthenticationToken?

The UsernamePasswordAuthenticationToken is an implementation of interface Authentication which extends the interface Principal . Principal is defined in the JSE java. security . UsernamePasswordAuthenticationToken is a concept in Spring Security which implements the Principal interface.


2 Answers

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:

  • Create custom filters to check requests for specific authorization information

  • Each filter returns null (if no authorization of that type is found), or a custom AbstractAuthenticationToken

  • If a filter returns a token, each AuthenticationProvider's supports(class) method will be invoked with that token returning true|false if it should try authentication

  • attemptAuthentication will then be called on the AuthenticationProvider which supports the token. Here you do any service calls to authenticate the user. You can then throw LoginException's or call authentication.setAuthenticated(true) and return the token for a successful authentication.

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();     } } 

Hope that helps!

like image 188
Matt MacLean Avatar answered Sep 30 '22 07:09

Matt MacLean


Another, option to add a second authentication provider: Simply specify another one on the AuthenticationManagerBuilder. Because the @EnableWebSecurity annotation is itself annotated with EnableGlobalAuthentication you can configure the global instance of AuthenticationManagerBuilder. (See the javadocs for more details.)

For example, here we have an LDAP authentication provider as well as an in memory (hard-coded) authentication provider (this is something we do in development to have local users to test with):

    @Configuration     @EnableWebSecurity     public class SecurityConfig extends WebSecurityConfigurerAdapter {        @Value("${user.role}")       private String userRole; // i.e. ROLE_APP_USER        @Value("${include.test.users}")       private boolean includeTestUsers;        @Override       protected void configure(HttpSecurity http) throws Exception {         http.authorizeRequests()           .antMatchers("/**/js/**").permitAll()           .antMatchers("/**/images/**").permitAll()           .antMatchers("/**/favicon.ico").permitAll()           .antMatchers("/**/css/**").permitAll()           .antMatchers("/**/fonts/**").permitAll()           .antMatchers("/**").hasAnyRole(userRole)           .and().formLogin().loginPage("/login").permitAll().and().logout().permitAll();          http.logout().logoutRequestMatcher(new AntPathRequestMatcher("/logout"));       }        @Autowired       public void configureGlobal(AuthenticationManagerBuilder auth, LdapContextSource contextSource) throws Exception {         auth.ldapAuthentication()           .userSearchBase("OU=Users OU")           .userSearchFilter("sAMAccountName={0}")           .groupSearchBase("OU=Groups OU")           .groupSearchFilter("member={0}")           .contextSource(contextSource);          if (includeTestUsers) {           auth.inMemoryAuthentication().withUser("user").password("u").authorities(userRole);         }       }     } 
like image 32
Brice Roncace Avatar answered Sep 30 '22 07:09

Brice Roncace