Can I chain multiple instances of AuthenticationEntryPoint in Spring Security 3.2.4?
I attempting to create the following scenario:
- A certain URL is secured with Spring Security
- The
AuthenticationEntryPointused isLoginUrlAuthenticationEntryPoint - An admin interface can spawn services under this URL
- The admin can choose to secure these services with
CLIENT-CERT
When a user attempts to access the secure URL:
- If the path has been secured with
CLIENT-CERTthen authentication fails unless they have provided a valid certificate the corresponds to a user in theUserService. Standard Spring Securityx509authentication. - Once the user has been authentication as per the first point, or if the URL is not secured with
CLIENT-CERT, they are directed to aFORMbased authentication page. - Once they successfully authenticate with a username and password, they are directed to a landing page.
I am running on Tomcat 7.0.54 with clientAuth="want". This works perfectly in a "simple" Spring Security set up - i.e. with one WebSecurityConfigurerAdapter set to x509() and another set to formLogin() as per this example
So, I want a process flow something like the following:

I have had some success with dynamically changing the used authentication method by using a DelegatingAuthenticationEntryPoint but:
- When using an
AntPathRequestMatcherto map, say,/form/**to aLoginUrlAuthenticationEntryPointthe authentication servlet (/j_spring_security_check) gives aHTTP404error. - When using an
AntPathRequestMatcherto map, say,/cert/**to aHttp403ForbiddenEntryPointthe user's details are not extracted from the presented client certificate so this gives aHTTP403error.
I also cannot see how to force a user to authenticate twice.
I am using the java-config and not XML.
My code:
I have a DelegatingAuthenticationEntryPoint:
@Bean
public AuthenticationEntryPoint delegatingEntryPoint() {
final LinkedHashMap<RequestMatcher, AuthenticationEntryPoint> map = Maps.newLinkedHashMap();
map.put(new AntPathRequestMatcher("/basic/**"), new BasicAuthenticationEntryPoint());
map.put(new AntPathRequestMatcher("/cert/**"), new Http403ForbiddenEntryPoint());
final DelegatingAuthenticationEntryPoint entryPoint = new DelegatingAuthenticationEntryPoint(map);
entryPoint.setDefaultEntryPoint(new LoginUrlAuthenticationEntryPoint("/login"));
return entryPoint;
}
And my configure
@Override
protected void configure(final HttpSecurity http) throws Exception {
defaultConfig(http)
.headers()
.contentTypeOptions()
.xssProtection()
.cacheControl()
.httpStrictTransportSecurity()
.addHeaderWriter(new XFrameOptionsHeaderWriter(SAMEORIGIN))
.and()
.authorizeRequests()
.accessDecisionManager(decisionManager())
.anyRequest()
.authenticated()
.and()
.httpBasic()
.authenticationEntryPoint(delegatingEntryPoint())
.and()
.sessionManagement()
.maximumSessions(1)
.sessionRegistry(sessionRegistry())
.maxSessionsPreventsLogin(true);
}
Where decisionManager() returns a UnanimousBased instance. sessionRegistry() returns a SessionRegistryImpl instance. Both methods are @Bean.
I add a custom UserDetailsService using:
@Autowired
public void configureAuthManager(
final AuthenticationManagerBuilder authBuilder,
final InMemoryUserDetailsService authService) throws Exception {
authBuilder.userDetailsService(authService);
}
And I have a custom FilterInvocationSecurityMetadataSource mapped using a BeanPostProcessor as in this example.