Wednesday 22 April 2015

Multiple Authentication Schemes in Spring Security

While developing server side applications using spring framework sometimes we encounter situations where need to support web based clients (typically developed in Backbone.js or Angular.js or JSP based multi form applications), Mobile clients (Android, IOS etc). The RESTfull services exposed may have third party clients as well.


If we have a consumer facing web interface typically accessed by web browsers where we need to maintain a user session we have to end up having a form based authentication mechanism. Third party clients for B2B service consumption we can have token based security in place and mobile users security could be supported by OAuth2. Lets see how we could implement all three types of security mechanisms in a web application using Spring Security.


Security Configuration files for REST and Form based security :-

@Configuration
@EnableWebSecurity

public class MultiHttpSecurityConfig {
   
   @Configuration
    @Order(1)                                                        
    public static class RestSecurityConfig extends WebSecurityConfigurerAdapter {

    @Bean
    public RestAuthenticationEntryPoint restAuthenticationEntryPoint() {
    RestAuthenticationEntryPoint entryPoint = new RestAuthenticationEntryPoint();
    entryPoint.setRealmName("<your_realm_name>");
    return entryPoint;
    }

    @Bean
    public RestAuthenticationProvider restAuthenticationProvider() {
    RestAuthenticationProvider authProvider = new RestAuthenticationProvider();
    return authProvider;
    }

    @Bean
    public RestSecurityFilter restSecurityFilter() {

    RestSecurityFilter filter = null;
    try {
    filter = new RestSecurityFilter(authenticationManagerBean());
    } catch (Exception e) {
    e.printStackTrace();
    }

    return filter;
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
    http.csrf().disable();
    http
    .antMatcher("/api/**")    
    .sessionManagement()
    .sessionCreationPolicy(SessionCreationPolicy.STATELESS).and()
    //.addFilterBefore(restSecurityFilter(), BasicAuthenticationFilter.class)
    .exceptionHandling()
    .authenticationEntryPoint(restAuthenticationEntryPoint()).and()
    .authorizeRequests()
    .antMatchers("/api/**")
    .authenticated().and().addFilterBefore(restSecurityFilter(), BasicAuthenticationFilter.class);
    

    }
    
    @Override
        protected void configure(AuthenticationManagerBuilder authManagerBuilder) throws Exception {
    authManagerBuilder.authenticationProvider(restAuthenticationProvider());
        }
    }

    @Configuration
    public static class FormSecurityConfig extends WebSecurityConfigurerAdapter {

    @Autowired
    DataSource dataSource;

    @Autowired
    private <custom_user_detail_service> customUserDetailsService;

    @Autowired
    CustomSecuritySuccessHandler customSecuritySuccessHandler;

    @Autowired
    CustomSecurityFailureHandler customSecurityFailureHandler;

    @Autowired
    private <password_encoder> passwordEncoder;
    
    @Autowired
    private CustomAccessDeniedHandler customAccessDeniedHandler;

    
    @Override
    public void configure(WebSecurity web) throws Exception {
    web.ignoring().antMatchers("/resources/**");
    }

    protected void configure(HttpSecurity http) throws Exception {
    http.csrf().disable();
    http.authorizeRequests()
    .antMatchers("/", "/login.html", "/app/**", "/assets/**", "/login","/failure","/register","/public/**", "/oauth/v1/**").permitAll().anyRequest().authenticated();
    http.formLogin().loginPage("/login").failureUrl("/")
    .successHandler(customSecuritySuccessHandler)
    .failureHandler(customSecurityFailureHandler).permitAll().and()

    .logout().logoutSuccessUrl("/login").permitAll().and()
                .rememberMe().and().exceptionHandling().accessDeniedHandler(customAccessDeniedHandler);
    
    
    return;
    }

    @Override
    protected void configure(AuthenticationManagerBuilder authManagerBuilder)
    throws Exception {
    authManagerBuilder.userDetailsService(customUserDetailsService)
    .passwordEncoder(passwordEncoder);
    }
    
    }

}


OAuth2 Security Configuartion:-

@Configuration
public class GlobalAuthenticationConfig extends GlobalAuthenticationConfigurerAdapter {
    
@Autowired
private <custom_user_detail_service> oAuthUserDetailService;
@Autowired
private <password_encoder> commonPasswordEncoder;
      
    @Override
public void init(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(oAuthUserDetailService).passwordEncoder(commonPasswordEncoder);

}
}


@Configuration
@EnableAuthorizationServer
public class OAuth2AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter  {

@Autowired
DataSource dataSource;
@Autowired
private AuthenticationManager authenticationManager;

@Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
clients.jdbc(dataSource);
}

@Bean
public TokenStore tokenStore() {
return new JdbcTokenStore(dataSource);
}

@Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
endpoints.tokenStore(tokenStore()).authenticationManager(authenticationManager);

}

@Override
public void configure(AuthorizationServerSecurityConfigurer oauthServer) throws Exception {
oauthServer.allowFormAuthenticationForClients();
}

}

@Configuration
@EnableResourceServer
public class OAuth2ResourceServerConfig extends ResourceServerConfigurerAdapter  {

private static final String HU_REST_RESOURCE_ID = "rest_api";

@Autowired
DataSource dataSource;

@Bean
public TokenStore tokenStore() {
return new JdbcTokenStore(dataSource);
}


@Override
public void configure(ResourceServerSecurityConfigurer resources) {
resources.resourceId(HU_REST_RESOURCE_ID).stateless(false);
}

@Override
public void configure(HttpSecurity http) throws Exception {
http.
requestMatchers().antMatchers("/oauth/v1/**").and().
authorizeRequests().antMatchers("/oauth/v1/**").access("#oauth2.hasScope('read') or (!#oauth2.isOAuth() and hasRole('ROLE_USER'))");
}

}

With these configurations the incoming requests with URL pattern -
i. <context>/api/<version>/<some_request>  will be intercepted by RestSecurityConfig
ii. <context>/oauth/v1/<some_request> will be intercepted by OAuth2ResourceServerConfig
iii. All other requests will be intercepted by FormSecurityConfig

[Feel free to clone https://github.com/badalb/multi-security-config-web.git for detail code.]

4 comments:

  1. Hi Badal, In your Git hub project I didn't find any implementation for the com.test.security.rest.filter.SubscriptionService interface. I thing it is missing in the project. Could u please check it and commit the impl file.

    ReplyDelete
    Replies
    1. This comment has been removed by the author.

      Delete
    2. Hi,

      Thx for figuring it out. Added a pseudo method impl sample for your reference.

      Thx,
      Badal

      Delete