I am looking for a non invasive way to add a captcha filter for certain api calls.
My setup consists of two WebSecurityConfigurerAdapters
with one filter each (not the captcha filter):
How can I add a filter before the Spring Security stuff, on public, internal api or external api calls? I don't need the SecurityContext, just need to check for a Captcha in the request headers, forward to filterChain (normal filters) or manually deny access. I tried declaring a filter in web.xml, but that breaks the ability to use dependency injection.
Here is my Spring Security Configuration:
@EnableWebSecurity
public class SpringSecurityConfig {
@Configuration
@Order(1)
@EnableGlobalMethodSecurity(securedEnabled = true)
public static class InternalApiConfigurerAdapter extends WebSecurityConfigurerAdapter {
@Autowired
private Filter filterA;
public InternalApiConfigurerAdapter() {
super(true);
}
@Override
public void configure(WebSecurity web) throws Exception {
web
.ignoring()
.antMatchers("/public/**");
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.antMatcher("/iapi/**")
.exceptionHandling().and()
.anonymous().and()
.servletApi().and()
.authorizeRequests()
.anyRequest().authenticated().and()
.addFilterBefore(filterA, (Class<? extends Filter>) UsernamePasswordAuthenticationFilter.class);
}
@Override
@Bean
public AuthenticationManager authenticationManagerBean() throws Exception {
return authenticationManager();
}
}
@Configuration
@Order(2)
@EnableGlobalMethodSecurity(securedEnabled = true)
public static class ExternalApiConfigurerAdapter extends WebSecurityConfigurerAdapter {
@Autowired
private FilterB filterB;
public ExternalApiConfigurerAdapter() {
super(true);
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.antMatcher("/external/**")
.exceptionHandling().and()
.anonymous().and()
.servletApi().and()
.authorizeRequests()
.anyRequest().authenticated().and()
.addFilterBefore(filterB, (Class<? extends Filter>) UsernamePasswordAuthenticationFilter.class);
}
@Override
@Bean
public AuthenticationManager authenticationManagerBean() throws Exception {
return authenticationManager();
}
}
Update: At the moment I have a working configuration with a filter declared in web.xml. However, it has the drawback of being seperated from the Spring Context (e.g. no autowiring of beans), so I am looking for a better solution leveraging Spring.
Summary: There are two remaining problems:
antMatchers("/api/v1/signup"). permitAll().
There are a couple of possible methods: addFilterBefore(filter, class) adds a filter before the position of the specified filter class. addFilterAfter(filter, class) adds a filter after the position of the specified filter class. addFilterAt(filter, class) adds a filter at the location of the specified filter class.
You already have a working configuration with filters A and B inserted before UsernamePasswordAuthenticationFilter
so should be easy to add another custom filter.
First, you create the filter, and declare it as a bean, either annotating the class with @Component
, or as a @Bean
inside a @Configuration
class, so it is ready to be injected with @Autowired
.
Now you are able to inject it, as filter A and B, and use it. According to the Filter Ordering section in the Spring Security reference documentation, the very first Filter in the chain is ChannelProcessingFilter
, so in order to insert the filter before anything else in the Spring Security filter chain, you'd do this:
@Autowired
private CaptchaFilter captchaFilter;
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.antMatcher("/iapi/**")
.addFilterBefore(captchaFilter, (Class<? extends Filter>) ChannelProcessingFilter.class)
.addFilterBefore(filterA, (Class<? extends Filter>) UsernamePasswordAuthenticationFilter.class)
.authorizeRequests()
.anyRequest().authenticated();
}
By the way, exceptionHandling()
anonymous()
and servletApi()
aren't needed because when extending WebSecurityConfigurerAdapter
, these are already included, except for anonymous()
when you actually specify more configuration details, as it states HttpSecurity
javadoc
Keep in mind that the Spring Security "entrypoint", the DelegatingFilterProxy
still will be executed before your filter, but this component only delegates the request to the first filter in the chain, which in this case would be the CaptchaFilter, so you really would execute your filter before anything else from Spring Security.
But if you still want the captcha filter be executed before the DelegatingFilterProxy
, there is no way to do so in the Spring Security configuration, and you need to declare it in the web.xml
file.
Update: If you do not desire to include the captcha filter in the other configurations, you can always add a third configuration, and the configurations class would be as follows:
@EnableWebSecurity
@EnableGlobalMethodSecurity(securedEnabled = true)
public class SpringSecurityConfig {
@Configuration
@Order(1)
public static class CaptchaApiConfigurerAdatper extends WebSecurityConfigurerAdapter {
@Autowired
private CaptchaFilter captchaFilter;
public CaptchaApiConfigurerAdatper() {
super(true);
}
@Override
public void configure(WebSecurity web) throws Exception {
web
.ignoring()
.antMatchers("/public/**");
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.requestMatchers()
.antMatcher("/iapi/captcha**")
.antMatcher("/external/captcha**")
.and()
.addFilterBefore(captchaFilter, (Class<? extends Filter>) ChannelProcessingFilter.class)
.authorizeRequests()
.anyRequest().authenticated();
}
}
@Configuration
@Order(2)
public static class InternalApiConfigurerAdapter extends WebSecurityConfigurerAdapter {
// ommiting code for the sake of clarity
}
@Configuration
@Order(3)
public static class ExternalApiConfigurerAdapter extends WebSecurityConfigurerAdapter {
// ommiting code for the sake of clarity
}
By the way, another tip, you can refactor all the common configuration outside the specific configurations, into the main class, like @EnableGlobalMethodSecurity(securedEnabled = true)
the AuthenticationManager, the WebSecurity
to skip security for the public, but for those since the main class is not extending anything you should @Autowire
the method declarations.
Although there would be one problem with the WebSecurity
, if you are ignoring /public/**
the matcher for the HttpSecurity
with /public/captcha**
would be ignored, so i guess, you shouldnt refactor out the WebSecurity
and have a different pattern in the CaptchaConfig class so it doesnt overlap.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With