web.ignoring() is not working when addFilterBefore(customFilter(), AbstractPreAuthenticatedProcessingFilter.class) is present - spring-boot

Using Spring boot 2.2.4.RELEASE, spring-security-oauth2-2.3.3, spring-security-web-5.2.1.
I have set up successfully my oauth2 server and secured my endpoints using WebSecurityConfigurerAdapter and ResourceServerConfigurer.
The problem I'm having is that when I use addFilterBefore(customFilter(), AbstractPreAuthenticatedProcessingFilter.class) in my ResourceServerConfigurer. Calling unsecured paths still try to authenticate instead of being ignored, the request tries to pass through my customFilter().
I did set up all my custom filters manually and not as beans so they won't be added automatically by spring to the filter chain, but I still get this behavior.
I also used ("/rest/**", "/api/**") ant matchers so customFilter() applies only when encountering these paths, but I also still get this behavior.
On server startup I do see this, which is intended:
org.springframework.security.web.DefaultSecurityFilterChain - Creating filter chain: Ant [pattern='/usecured*'], []
org.springframework.security.web.DefaultSecurityFilterChain - Creating filter chain: Ant [pattern='/unsecured2*'], []
org.springframework.security.web.DefaultSecurityFilterChain - Creating filter chain: Ant [pattern='/usecured3*'], []
My WebSecurityConfigurerAdapter
#EnableWebSecurity
#Configuration
#Order(1) // order 1 so it applies before ResourceServerConfigurer paths
#EnableGlobalMethodSecurity(prePostEnabled = true, proxyTargetClass = true)
public class ApiSecurityRestLoginConfig extends WebSecurityConfigurerAdapter {
//...
#Override
public void configure(WebSecurity web) throws Exception {
web.ignoring().antMatchers("/usecured*","/unsecured2*","/usecured3*");
}
}
My ResourceServerConfigurer
#EnableResourceServer
#Configuration
#EnableGlobalMethodSecurity(prePostEnabled = true, proxyTargetClass = true)
public class ApiSecurityResourceServerConfig implements ResourceServerConfigurer {
//...
#Override
public void configure(HttpSecurity http) throws Exception {
http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
http.authorizeRequests().antMatchers("/rest/**", "/api/**").authenticated()
.and()
//..
.addFilterBefore(customFilter(), AbstractPreAuthenticatedProcessingFilter.class) // <-- when I remove this line, web.ignoring() works, otherwise it doesn't.
//..
}
}
Is this a bug or I'm approaching it the wrong way?

For reference
I updated my web.ignoring() code to this
#Override
public void configure(WebSecurity web) throws Exception {
web.ignoring().antMatchers("/usecured*/**","/unsecured2*/**","/usecured3*/**");
}
and it worked.

Related

Why do unregistered filters get called anyway? [duplicate]

I implemented a customFilter that adds something from the request`s cookies to its headers :
#Component
#Slf4j
public class MyCustomFilter implements Filter {
#Override
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws ServletException, IOException {
.... some logic...
log.info("Sending request to next chain for validation..");
chain.doFilter(request, response);
log.info("Authentication completed sucessfully");
}
#Bean
// This method is needed to replace the default cookieFilter.json processor of tomcat that ignores the jwt cookieFilter.json
public WebServerFactoryCustomizer<TomcatServletWebServerFactory> cookieProcessorCustomizer() {
return tomcatServletWebServerFactory -> tomcatServletWebServerFactory.addContextCustomizers((TomcatContextCustomizer) context -> {
context.setCookieProcessor(new LegacyCookieProcessor());
});
}
}
My WebSecurityConfigurerAdapter class :
#Configuration
public class AuthSecurityConfig extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
//configuring strategy
http.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS).and()
.authorizeRequests()
.anyRequest().authenticated().and()
.oauth2ResourceServer().jwt().and();
http.csrf().disable();
http.addFilterBefore(new MyCustomFilter (), UsernamePasswordAuthenticationFilter.class);
http.exceptionHandling().authenticationEntryPoint(new AuthExceptionEntryPoint());
}
}
When I run the code and send a request via postman/curl I see that the filter triggered twice in the
Sending request to next chain for validation..
Sending request to next chain for validation..
Authentication completed sucessfully
Authentication completed sucessfully
I found a few posts about issue and I tried the following solutions :
It happens because spring registers the beans automatically and I add the filter manually in the configure method. Therefore, I removed the manually addition of the filter in the configure() method. The result was that the filter wasnt called at all.
Instead of implementing the filter interface, try to extend the OncePerRequestFilter class. Done that, but the filter still triggered twice.
Tried also to remove the #Component annotation and add the filter manually. In addition I had to move the CookieProcessor bean to the Configuration class. The problem that raised afterwards is that the app fails to start because of the following error :
Caused by: org.springframework.beans.BeanInstantiationException: Failed to instantiate [org.springframework.web.servlet.HandlerMapping]: Factory method 'resourceHandlerMapping' threw exception; nested exception is java.lang.IllegalStateException: No ServletContext set
I am using spring-security version 5.3.3.
As a rule of thumb, don't add #Bean methods to #Component classes as those are handled differently than those in #Configuration classes. (See this).
The your code in the #Bean is too complex. Create and return a TomcatContextCustomizer to do the modification. Your code will lead to circulair references which will lead to initializing errors.
Add the following #Bean method to your #SpringBootApplication annotated class
#Bean
public TomactContextCustomizer cookieProcessorCustomizer() {
return (context) -> context.setCookieProcessor(new LegacyCookieProcessor());
}
Now in your Filter either remove the #Component or add an accompying FilterRegistrationBean to prevent it from being added to the regular chain of filters. (Spring Boot automatically registers all detected Filter instances to the regular filter chain).
#Bean
public FilterRegistrationBean<MyFilter> myFilterRegistrationBean(MyFilter myFilter) {
FilterRegistrationBean<MyFilter> frb = new FilterRegistrationBean<>(myFilter);
frb.setEnabled(false);
return frb;
}
If you remove #Component the above snippet isn't needed if you don't then you should reuse the scanned MyFilter instance in your security configuration.
#Configuration
public class AuthSecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
private MyFilter myFilter;
#Override
protected void configure(HttpSecurity http) throws Exception {
//configuring strategy
http.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS).and()
.authorizeRequests()
.anyRequest().authenticated().and()
.oauth2ResourceServer().jwt().and();
http.csrf().disable();
http.addFilterBefore(myFilter, UsernamePasswordAuthenticationFilter.class);
http.exceptionHandling().authenticationEntryPoint(new AuthExceptionEntryPoint());
}
}

Spring Boot REST API/Spring Security: Return custom message when authentication fails

I have a Spring Boot app using Jersey as the JAX-RS implementation. This is my security configuration:
#Configuration
#EnableGlobalMethodSecurity(prePostEnabled = true)
#EnableWebSecurity
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
#Autowired TokenAuthenticationProvider tokenAuthenticationProvider;
#Override
public void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.authenticationProvider(tokenAuthenticationProvider);
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http.addFilterBefore(new AuthenticationTokenFilter(), BasicAuthenticationFilter.class)
.csrf().disable()
.authorizeRequests()
.antMatchers("/dataHub/**")
.authenticated();
}
}
What I want to be able to do is to have a way to catch the Exceptions thrown by my TokenAuthenticationProvider and convert them into a standardized JSON format that we have agreed upon. Is there a way to do this? I tried messing around with adding a custom AuthenticationFailureHandler, but couldn't get that to work.
WebSecurityConfigurerAdapter appraoch
The HttpSecurity class has a method called exceptionHandling which can be used to override the default behavior. The following sample presents how the response message can be customized.
#Override
protected void configure(HttpSecurity http) throws Exception {
http
// your custom configuration goes here
.exceptionHandling()
.authenticationEntryPoint((request, response, e) -> {
String json = String.format("{\"message\": \"%s\"}", e.getMessage());
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
response.setContentType("application/json");
response.setCharacterEncoding("UTF-8");
response.getWriter().write(json);
});
}
#ControllerAdvice appraoch - Why it doesn't work in this case
At first I thought about #ControllerAdvice that catches authentication exceptions for the entire application.
import org.springframework.http.HttpStatus;
import org.springframework.security.core.AuthenticationException;
#ControllerAdvice
public class AuthExceptionHandler {
#ResponseStatus(HttpStatus.UNAUTHORIZED)
#ExceptionHandler(AuthenticationException.class)
#ResponseBody
public String handleAuthenticationException(AuthenticationException e) {
return String.format("{\"message\": \"%s\"}", e.getMessage());
}
}
In the example above, the JSON is built manually, but you can simply return a POJO which will be mapped into JSON just like from a regular REST controller. Since Spring 4.3 you can also use #RestControllerAdvice, which is a combination of #ControllerAdvice and #ResponseBody.
However, this approach doesn't work because the exception is thrown by the AbstractSecurityInterceptor and handled by ExceptionTranslationFilter before any controller is reached.

Customize LdapAuthoritiesPopulator in configuration

The DefaultLdapAuthoritiesPopulator sets a search scope of "ONE_LEVEL", but I need to search "SUBSCOPE" to get the list of groups a user is a member of.
I've been following the "configuration" style Spring setup (code, not XML). While there's tons of examples of how to configure a custom LdapAuthoritiesPopulator in XML, I'm kind of stuck on how to do it in code.
Here's what I have so far:
#EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
#EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
public void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.ldapAuthentication()
.contextSource().url("ldap://ldap.company.org/")
.and()
.userSearchBase("o=company.org,c=us")
.userSearchFilter("(uid={0})")
.groupSearchBase("o=company.org,c=us")
.groupSearchFilter("(&(objectClass=groupOfUniqueNames)(uniqueMember={0}))");
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http.formLogin().and().authorizeRequests()
.antMatchers("/api/**").authenticated()
.anyRequest().permitAll();
}
}
What's missing is that I need to be able to set the search scope on the DefaultLdapAuthoritiesPopulator. The class itself exposes a "setSearchSubtree" method, but the LdapAuthenticationProviderConfigurer does not provide a way of configuring it.
Any suggestions?
Solution is to set this property in LdapAuthoritiesPopulator and pass it to LdapAuthenticationProvider
Refer Example 1 in : https://www.programcreek.com/java-api-examples/?api=org.springframework.security.ldap.userdetails.DefaultLdapAuthoritiesPopulator
#Bean
public LdapAuthoritiesPopulator authoritiesPopulator(){
DefaultLdapAuthoritiesPopulator populator = new DefaultLdapAuthoritiesPopulator(
contextSource(),
groupSearchBase);
populator.setGroupSearchFilter("(uniqueMember={0})");
populator.setGroupRoleAttribute("cn");
**populator.setSearchSubtree(true);**
populator.setRolePrefix("");
return populator;
}
You need to add something like:
final SearchControls searchControls = new SearchControls();
searchControls.setSearchScope(SearchControls.SUBTREE_SCOPE);
To before you begin your search.
Why it is called a "control" is beyond me (an LDAP guy), but that is what Spring does.
-jim

Spring Boot setup with multiple authentication providers (API+Browser)

My application serves both API and browser. I've implemented API Token authentication with all custom providers and filter. The configuration now seems to interfere with the browser version.
I have two questions that I need advice on how to solve, as I'm not getting anywhere after digging through the documentation and other examples.
1) My StatelessAuthenticationFilter is being called despite a request
coming from the browser. I have e.g. specified the request matcher to "/api/**". Why is that?
2) The AuthenticationManager have not registered two AuthenticationProviders. This is my conclusion after debugging my StatelessAuthenticationFilter that's being called wrongly.
Here's the configuration classes that I have
#Configuration
#EnableWebSecurity
public class WebSecurityConfig {
#Order(1)
#Configuration
public static class A extends WebSecurityConfigurerAdapter {
#Autowired
TokenAuthenticationProvider tokenAuthenticationProvider;
#Autowired
ApiEntryPoint apiEntryPoint;
#Override
protected void configure(HttpSecurity http) throws Exception {
StatelessAuthenticationFilter filter = new StatelessAuthenticationFilter();
AntPathRequestMatcher requestMatcher = new AntPathRequestMatcher("/api/**");
filter.setRequiresAuthenticationRequestMatcher(requestMatcher);
filter.setAuthenticationManager(super.authenticationManager());
http.csrf().disable()
.exceptionHandling().authenticationEntryPoint(apiEntryPoint)
.and()
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.addFilterBefore(filter, UsernamePasswordAuthenticationFilter.class);
}
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.authenticationProvider(tokenAuthenticationProvider);
}
#Override
public void configure(WebSecurity web) throws Exception {
web.ignoring().antMatchers("/api/user/register");
}
}
#Configuration
public static class B extends WebSecurityConfigurerAdapter {
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.authenticationProvider(new DaoAuthenticationProvider());
}
}
}
As you can see, B class doesn't specify anything, yet when I access localhost:8080 the StatelessAuthenticationFilter is called. What is going on here?
In class A you are configuring the StatelessAuthenticationFilter to use a requestMatcher. Whatever you do with that, spring does not know or care about that.
You must also restrict your security configuration using
http.antMatcher("/api/**")
otherwise its configured for every URI and the StatelessAuthenticationFilter will be invoked for every request, exactly as you described.
You should also annotate class A and B with #Order as shown in the example at multiple-httpsecurity

can't get Spring Security to work

I'm new to Spring Security so I probably miss out on something. I have a Spring Application that starts a Jetty with a WebApplication I want to secure using Spring Security. The webapp is running and reachable, but not restricted. I've tried a lot of stuff but nothing worked so I broke it down to a minimal setup, but still no chance.
the webapp is configured by the following java configuration:
#EnableWebMvc
#Configuration
#Import(SecurityConfiguration.class)
#ComponentScan(useDefaultFilters = false, basePackages = { "myapp.web" }, includeFilters = { #ComponentScan.Filter(Controller.class) })
public class SpringMvcConfiguration extends WebMvcConfigurerAdapter {
/**
* Allow the default servlet to serve static files from the webapp root.
*/
#Override
public void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer) {
configurer.enable();
}
}
and Spring Security configured here:
#Configuration
#EnableWebSecurity
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
#Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
auth
.inMemoryAuthentication()
.withUser("user")
.password("password")
.roles("ADMIN")
.authorities("ADMIN");
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.anyRequest()
.hasAuthority("ADMIN");
}
}
and some controller like this:
#Controller
public class SecuredController {
#RequestMapping(value = "/secure", method = RequestMethod.GET)
#ResponseBody
public String secured() {
return "you should not see this unless you provide authentication";
}
}
Everything starts up all right, the log tells me, that the controller is mapped...
[2014-10-01 20:21:29,538, INFO ] [main] mvc.method.annotation.RequestMappingHandlerMapping:197 - Mapped "{[/secure],methods=[GET],params=[],headers=[],consumes=[],produces=[],custom=[]}" onto public java.lang.String myapp.web.SecuredController.secured()
...and that security is in place as well...
[2014-10-01 20:21:30,298, INFO ] [main] gframework.security.web.DefaultSecurityFilterChain:28 - Creating filter chain: org.springframework.security.web.util.matcher.AnyRequestMatcher#1, [org.springframework.security.web.context.request.async.WebAsyncManagerIntegrationFilter#352c308, org.springframework.security.web.context.SecurityContextPersistenceFilter#2af616d3, org.springframework.security.web.header.HeaderWriterFilter#1a2e2935, org.springframework.security.web.csrf.CsrfFilter#64f857e7, org.springframework.security.web.authentication.logout.LogoutFilter#bc57b40, org.springframework.security.web.savedrequest.RequestCacheAwareFilter#3deb2326, org.springframework.security.web.servletapi.SecurityContextHolderAwareRequestFilter#7889a1ac, org.springframework.security.web.authentication.AnonymousAuthenticationFilter#7d373bcf, org.springframework.security.web.session.SessionManagementFilter#5922ae77, org.springframework.security.web.access.ExceptionTranslationFilter#7e1a1da6, org.springframework.security.web.access.intercept.FilterSecurityInterceptor#1051817b]
... but the /secure url of my controller is unconditionally reachable. What am I doing wrong?
ps. I want to avoid xml config
In order to integrate Spring Security with Spring MVC you have to use #EnableWebMvcSecurity annotation instead of #EnableWebSecurity in SecurityConfiguration class.
I figured, I had to move the initialization of the Spring Security configuration to the root context, not the dispatcher-servlet context, and add the following line where i configure the context of my embedded Jetty:
context.addFilter(new FilterHolder(new DelegatingFilterProxy("springSecurityFilterChain")), "/*", EnumSet.allOf(DispatcherType.class));

Resources