what's the proper way to add Spring Security filter only for specific URL? [duplicate] - filter

This question already has answers here:
Filter invoke twice when register as Spring bean
(3 answers)
Closed 5 months ago.
I am trying to add custom filter to only specific URL, however the filter get applied to every request, regardless of URL and method, does anybody know the proper way to fix this using latest from Spring Security, i.e. not using WebSecurityConfigurerAdapter, because it is going to be deprecated.
There are many similar questions here, but they either do not work for me, or they use the "old" approach (like this and this and many other), or both.
I have number of endpoints exposed that all follow the pattern: /api/** however I need to provide some authentication for a specific endpoint: /api/some/url and a particular method (GET in this case), how do I do this properly?
NOTE: the endpoint URLs are all under /api/* (should they be called nested?)
My security configuration looks like this:
#EnableWebSecurity
public class SecurityConfig {
private MyFilter myFilter;
public SecurityConfig(MyFilter pif) {
myFilter = pif;
}
/**
* Handling AuthZ & AuthN for most APIs. No AuthZ & AuthN.
*/
#Bean
#Order(Ordered.HIGHEST_PRECEDENCE)
public SecurityFilterChain defaultSecurity(HttpSecurity http) throws Exception {
http.requestMatchers((requests) ->
requests.antMatchers("/"))
.authorizeHttpRequests((authorize) -> authorize.anyRequest()
.permitAll());
return http.build();
}
/**
* Handling AuthZ & AuthN for GET /api/some/url.
*/
#Bean
public SecurityFilterChain keyApiSecurity(HttpSecurity http) throws Exception {
http.requestMatchers((requests) -> requests
.antMatchers(HttpMethod.GET, "/api/some/url").and())
.addFilterBefore(myFilter,
BasicAuthenticationFilter.class)
.authorizeHttpRequests((authorize) -> authorize.anyRequest().permitAll());
return http.build();
}
}

When you expose some GenericFilter implementation as a bean in spring-boot, it automatically puts it in a common filter chain for any request, because it doesn't know if it's a security filter or not - it could be a logging filter or anything else.
So this filter bean will be executed regardless of spring-security.
Your defaultSecurity filter chain doesn't have this custom filter, so MyFilter will be executed after spring security filter chain due to the order.
At the same time, keyApiSecurity filter chain sets this custom filter before BasicAuthenticationFilter, so it will be executed there, and will not be executed the second time, because basic doFilter() implementation of OncePerRequestFilters method checks whether the request was already filtered by the filter.
So, if you want your filter to work only as a security filter, you should not expose it as a bean, and you should set it in a security filter chain like this:
.addFilterBefore(new MyFilter(), BasicAuthenticationFilter.class)
Also you should think about setting the lowest priority for a "default" security filter chain, because if it's selected first - other security filter chains will be totally ignored. So I think some specific filter security chains should have higher priority.
EDIT:
If you can't set your security filter with the new operator because you rely on bean injection in this Filter implementation, you can override shouldNotFilter(HttpServletRequest request) method of OncePerRequestFilter, for example like this:
#Component
public class MyFilter extends OncePerRequestFilter {
private final RequestMatcher uriMatcher =
new AntPathRequestMatcher("/api/some/url", HttpMethod.GET.name());
// some bean injection...
#Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response,
FilterChain filterChain) throws ServletException, IOException {
// filter logic here...
}
#Override
protected boolean shouldNotFilter(HttpServletRequest request) {
RequestMatcher matcher = new NegatedRequestMatcher(uriMatcher);
return matcher.matches(request);
}
}
Then it will filter only matched requests, and putting it into the security filter chain will set its order.

Related

How to properly set SecurityContextRepository for a custom UsernamePasswordAuthenticationFilter in Spring Security 6?

Since Spring Security 6.0 an explicit saving of the SecurityContextRepository is required:
https://docs.spring.io/spring-security/reference/migration/servlet/session-management.html#_require_explicit_saving_of_securitycontextrepository
This means that if a custom UsernamePasswordAuthenticationFilter is used, a SecurityContextRepository must be provided to being able to persist the SecurityContext, like this:
#Component
public class InternalUsernamePasswordAuthenticationFilter extends UsernamePasswordAuthenticationFilter {
#PostConstruct
private void setup() {
super.setSecurityContextRepository(
new DelegatingSecurityContextRepository(
new RequestAttributeSecurityContextRepository(),
new HttpSessionSecurityContextRepository()));
}
//...
}
It seems to be sufficient to specify the SecurityContextRepository only once in the AuthenticationFilter, although #Max in the example here sets the repository in the SecurityFilterChain as well.
I have tested both variants and it doesn't seem to matter if a SecurityContextRepository is specified in the FilterChain or not. It also doesn't seem to matter if we use a bean or create new repositories for both AuthenticationFilter and FilterChain like this:
#Configuration
#EnableWebSecurity
#EnableMethodSecurity(securedEnabled = true)
public class WebSecurityConfig {
#Bean
public SecurityFilterChain mvcFilterChain(HttpSecurity http) throws Exception {
return http
.authorizeHttpRequests(authorize -> authorize
.shouldFilterAllDispatcherTypes(true)
.dispatcherTypeMatchers(DispatcherType.FORWARD).permitAll()
.requestMatchers("/", "/login/**").permitAll()
.anyRequest().authenticated())
.addFilterAt(internalUsernamePasswordAuthenticationFilter, UsernamePasswordAuthenticationFilter.class)
.securityContext(securityContext -> securityContext
.securityContextRepository(
new DelegatingSecurityContextRepository(
new RequestAttributeSecurityContextRepository(),
new HttpSessionSecurityContextRepository()
)))
//...
}
}
This would indicate that only the repository of the AuthenticationFilter is used and the repository of the FilterChain is ignored.
My assumption would therefore be that creating the SecurityContextRepository in the AuthenticationFilter would be sufficient.
So my questions are:
Do I see that right?
Or should I rather define a bean?
And is there any reason to set this bean in the SecurityFilterChain as well?
Many thanks for any help for clarification

configuring interceptors to perform "pre-tasks" on hitting endpoints in spring based web app

I am required to perform some pre-task whenever few specific endpoints are hit in my spring based web app. I came across the interceptor component provided by the spring-security framework. I searched various forums but didn't find relevant answers for configuring and adding interceptor.
Consider a situation where I am required to set some key=value in a database by sending POST request in the database whenever the user hits following endpoints.
/endpoint1
/endpoint2
/endpoint3
/endpoint4
After completion of the pre-task user should be redirected to the origin endpoint.
How can this be achieved using an interceptor in the spring-security framework?
Spring Security is for security stuff related to Authentification and Authorization. You can trigger some action if somebody logged in, but if you just need to trigger action for each request than Spring Security is not a good place for that (according to business logic), better add just filter. Anyway answering to your question:
The best way is to add custom filter to Spring Security Filter Chain:
You have to overwrite:
#Configuration
public class CustomWebSecurityConfigurerAdapter
extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http.addFilterAfter(
new CustomFilter(), BasicAuthenticationFilter.class);
}
}
and create your custom filter:
public class CustomFilter extends GenericFilterBean {
#Override
public void doFilter(
ServletRequest request,
ServletResponse response,
FilterChain chain) throws IOException, ServletException {
//your logic here
chain.doFilter(request, response); //it's needed to pass your request father
}
}
Code taken from baeldung.com see for more information

Spring Security OAuth2: '#oauth2.xxx' expressions not evaluted with multiple RequestMatchers mapped to the same FilterChain

In a multi modules app I've defined 5 RequestMatchers mapped to the same FilterChain, like below:
#Configuration
public class Module1SecurityFilterChain extends ResourceServerConfigurerAdapter {
#Override
public void configure( HttpSecurity http ) throws Exception {
http.sessionManagement().sessionCreationPolicy( STATELESS );
http.requestMatchers().antMatchers( "/module1/**")
.and()
.authorizeRequests()
.antMatchers( "/module1/resource").authenticated()
.antMatchers( "/module1/test" ).access( "#oauth2.isClient()")
.anyRequest().access( "#oauth2.hasScope('webclient')" );
}
}
And module2:
#Configuration
public class Module2SecurityFilterChain extends ResourceServerConfigurerAdapter {
#Override
public void configure( HttpSecurity http ) throws Exception {
http.sessionManagement().sessionCreationPolicy( STATELESS );
http.requestMatchers().antMatchers( "/module2/**")
.and()
.authorizeRequests()
.antMatchers( "/module2/resource").authenticated()
.antMatchers( "/module2/test" ).access( "#oauth2.isClient()")
.anyRequest().access( "#oauth2.hasScope('webclient')" );
}
}
And enabled method security:
#Configuration
#EnableGlobalMethodSecurity( prePostEnabled = true, securedEnabled = true )
public class MethodSecurityConfiguration extends GlobalMethodSecurityConfiguration {
#Override
protected MethodSecurityExpressionHandler createExpressionHandler() {
return new OAuth2MethodSecurityExpressionHandler();
}
}
The problem is that all #oauth2.xx expressions are evaluated only for the 1st module requestmatcher /module1/** and ignored in others. When I authenticate a user and try to access to /module1/test the access is denied as expected whereas when accessing to /module2/test access is granted (it should also be denied).
Could someone explains me why and how to solve this? I know Spring Security isn't easy at all...
Thanks again.
EDIT
#Darren Forsythe (thanks for your comment)
The filter chains created are:
INFO | o.s.s.w.DefaultSecurityFilterChain | Creating filter chain: OrRequestMatcher [requestMatchers=[Ant [pattern='/oauth/token'], Ant [pattern='/oauth/token_key'], Ant [pattern='/oauth/check_token']]], [org.springframework.security.web.context.request.async.WebAsyncManagerIntegrationFilter#f55a810b, org.springframework.security.web.context.SecurityContextPersistenceFilter#85021903, org.springframework.security.web.header.HeaderWriterFilter#1d0744d1, org.springframework.security.web.authentication.logout.LogoutFilter#2d15146a, org.springframework.security.web.authentication.www.BasicAuthenticationFilter#c38f3266, org.springframework.security.web.savedrequest.RequestCacheAwareFilter#8f9bf85, org.springframework.security.web.servletapi.SecurityContextHolderAwareRequestFilter#74a71be5, org.springframework.security.web.authentication.AnonymousAuthenticationFilter#e4eb6cc, org.springframework.security.web.session.SessionManagementFilter#22f6b39a, org.springframework.security.web.access.ExceptionTranslationFilter#960c464f, org.springframework.security.web.access.intercept.FilterSecurityInterceptor#f7a19dc5]
INFO | o.s.s.w.DefaultSecurityFilterChain | Creating filter chain: OrRequestMatcher [requestMatchers=[Ant [pattern='/module1/**'], Ant [pattern='/module2/**'], Ant [pattern='/module3/**'], Ant [pattern='/module4/**'], Ant [pattern='/module5/**'], Ant [pattern='/module6/**']]], [org.springframework.security.web.context.request.async.WebAsyncManagerIntegrationFilter#38ef2427, org.springframework.security.web.context.SecurityContextPersistenceFilter#a26ff7af, org.springframework.security.web.header.HeaderWriterFilter#5344e710, org.springframework.security.web.authentication.logout.LogoutFilter#da0534c8, org.springframework.security.oauth2.provider.authentication.OAuth2AuthenticationProcessingFilter#2956c7ab, org.springframework.security.web.savedrequest.RequestCacheAwareFilter#5682f610, org.springframework.security.web.servletapi.SecurityContextHolderAwareRequestFilter#f4cbf7a4, org.springframework.security.web.authentication.AnonymousAuthenticationFilter#d1b1395a, org.springframework.security.web.session.SessionManagementFilter#d352f8ab, org.springframework.security.web.access.ExceptionTranslationFilter#9bb1d86, org.springframework.security.web.access.intercept.FilterSecurityInterceptor#73c7a695]
INFO | o.s.s.w.DefaultSecurityFilterChain | Creating filter chain: any request, [org.springframework.security.web.context.request.async.WebAsyncManagerIntegrationFilter#1cc2056f, org.springframework.security.web.context.SecurityContextPersistenceFilter#259d95db, org.springframework.security.web.header.HeaderWriterFilter#de089e0b, org.springframework.security.web.authentication.logout.LogoutFilter#8b86b4c, org.springframework.security.web.savedrequest.RequestCacheAwareFilter#96304ca8, org.springframework.security.web.servletapi.SecurityContextHolderAwareRequestFilter#1d5b7e4b, org.springframework.security.web.session.SessionManagementFilter#bd586b4d, org.springframework.security.web.access.ExceptionTranslationFilter#7cff2571]
As you can see, all module's urls are mapped to the same filter chain with this list of filters:
Security filter chain: [
WebAsyncManagerIntegrationFilter
SecurityContextPersistenceFilter
HeaderWriterFilter
LogoutFilter
OAuth2AuthenticationProcessingFilter
RequestCacheAwareFilter
SecurityContextHolderAwareRequestFilter
AnonymousAuthenticationFilter
SessionManagementFilter
ExceptionTranslationFilter
FilterSecurityInterceptor
]
What I don't understand is why for the other modules the #oauth2.xx expression is not evaluated since the FilterChain is the same?
Request matchers (specified in antMatchers, anyRequest, etc.) are processed by the filter chain in the order they are specified. Because multiple ResourceServiceConfiguredAdapter instances simply configure off of the same instance of HttpSecurity, the matchers are processed something like this for each one of your requests:
if (uri == "/module1/resource") {
// ...
} else if (uri == "/module1/test") {
// ...
} else if (true) { // anyRequest
// ...
} else if (uri = "/module2/resource") {
// ...
} else if (uri = "/module2/test") {
// ...
}
As you can see, the last two if conditions would never get hit.
Here are two things you can consider:
Replace anyRequest()
anyRequest is usually very handy; however, in this case, you don't actually mean "any request" since you are trying to narrow the scope to certain module paths. You might instead do:
http
.requestMatchers()
.antMatchers("/module2/**")
.and()
.authorizeRequests()
.antMatchers("/module2/resource").authenticated()
.antMatchers("/module2/test").access( "#oauth2.isClient()")
.antMatchers("/module2/**").access( "#oauth2.hasScope('webclient')" );
That way, the module doesn't overreach and try and specify behavior that maybe it doesn't know about.
Truthfully, it is typically harmless to call anyRequest since you are already narrowing the scope of the filter chain already with requestMatchers. But, because you are composing a single HttpSecurity with multiple adapters, there is this hidden complexity.
oauth2ResourceServer() - Spring Security 5.1+
If you are on Spring Security 5.1, then there is actually support built into WebSecurityConfigurerAdapter natively, so you don't need to use ResourceServerConfigurerAdapter anymore, at least for JWT-encoded tokens at this point. This is also nice because two WebSecurityConfigurerAdapters are treated as separate filter chains.
Depending on the OAuth 2.0 features you need, you may be able to do that instead:
#EnableWebSecurity
public class Module1Config extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.requestMatchers()
.antMatchers("/module1/**")
.and()
.authorizeRequests()
.antMatchers("/module1/resource").authenticated()
.anyRequest.hasRole("SCOPE_webclient")
.oauth2ResourceServer()
.jwt();
}
}
This is an active area of development for Spring Security right now - porting features over into WebSecurityConfigurerAdapter; so definitely reach out with your specific use case to make sure that it gets prioritized if it isn't already in place.

How to create servlet filter to authorize the request?

How to create spring servlet filter to authorize the request.
Need to add a filter in the spring security filter chain that updates the user with few details (re-loading permissions or anything) for every request, if they need to be changed.
Need some sample code snippet to follow or understand.
Thanks in advance.
To add a custom filter you should extend the org.springframework.web.filter.GenericFilterBean, like so:
public class MySecurityFilter extends GenericFilterBean {
#Override
public void doFilter(
ServletRequest request,
ServletResponse response,
FilterChain chain) throws IOException, ServletException {
chain.doFilter(request, response);
}
}
The next step is to actually register the filter by overriding the configure method of WebSecurityConfigurerAdapter:
#Configuration
public class CustomWebSecurityConfigurerAdapter
extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http.addFilterAfter(
new MySecurityFilter(), BasicAuthenticationFilter.class);
}
}
As you can see the filter is added by adding the filter to the HttpSecurity object. The method that is used is the addFilterAfter which basically allocates your filter after the one that you provide in the second argument, in this example is the BasicAuthenticationFilter, so your filter will be executed after this one, in the spring secuirty chain of filters.
Update
Refer to this link to personalize the authorization of your servlet. The method gives you an Authentication object, through which you can obtain your User object and perform additional checks.

Spring Boot Security PreAuthenticated Scenario with Anonymous access

I have a Spring Boot (1.5.6) application that is using the "pre-authenticated" authentication scenario (SiteMinder) from Spring Security.
I have a need to expose the actuator "health" endpoint anonymously meaning the requests to that endpoint will not go through SiteMinder and as a result, the SM_USER header will not be present in the HTTP Request Header.
The problem I'm facing is that no matter how I try to configure the "health" endpoint, the framework is throwing an org.springframework.security.web.authentication.preauth.PreAuthenticatedCredentialsNotFoundException because the expected header ("SM_USER") is not present when the request does not go through SiteMinder.
This was my original security config:
#Configuration
#EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.authorizeRequests().antMatchers("/cars/**", "/dealers/**")
.hasAnyRole("CLIENT", "ADMIN")
.and()
.authorizeRequests().antMatchers("/health")
.permitAll()
.and()
.authorizeRequests().anyRequest().denyAll()
.and()
.addFilter(requestHeaderAuthenticationFilter())
.csrf().disable();
}
#Bean
public Filter requestHeaderAuthenticationFilter() throws Exception {
RequestHeaderAuthenticationFilter filter = new RequestHeaderAuthenticationFilter();
filter.setAuthenticationManager(authenticationManager());
return filter;
}
#Override
public void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.authenticationProvider(preAuthProvider());
}
#Bean
public AuthenticationProvider preAuthProvider() {
PreAuthenticatedAuthenticationProvider authManager = new PreAuthenticatedAuthenticationProvider();
authManager.setPreAuthenticatedUserDetailsService(preAuthUserDetailsService());
return authManager;
}
#Bean
public AuthenticationUserDetailsService<PreAuthenticatedAuthenticationToken> preAuthUserDetailsService() {
return new UserDetailsByNameServiceWrapper<>(inMemoryUserDetails());
}
#Bean
public UserDetailsService inMemoryUserDetails() {
return new InMemoryUserDetailsManager(getUserSource().getUsers());
}
#Bean
public UserHolder getUserHolder() {
return new UserHolderSpringSecurityImple();
}
#Bean
#ConfigurationProperties
public UserSource getUserSource() {
return new UserSource();
}
I've tried to exclude the /health endpoint a couple different ways to no avail.
Things I've tried:
Configure health endpoint for anonymous access rather than permitAll:
http
.authorizeRequests().antMatchers("/health")
.anonymous()
Configure WebSecurity to ignore the health endpoint:
#Override
public void configure(WebSecurity web) throws Exception {
web.ignoring().antMatchers("/health");
}
Turn off security for all actuator endpoints (not idea but I was grasping for straws):
management.security.enabled=false
Looking at the logs, the problem seems to be that the RequestHeaderAuthenticationFilter is getting registered as a top level filter rather than a filter in the existing securityFilterChain:
.s.DelegatingFilterProxyRegistrationBean : Mapping filter: 'springSecurityFilterChain' to: [/*]
o.s.b.w.servlet.FilterRegistrationBean : Mapping filter: 'webRequestLoggingFilter' to: [/*]
o.s.b.w.servlet.FilterRegistrationBean : Mapping filter: 'requestHeaderAuthenticationFilter' to: [/*]
Based on my understanding, because the RequestHeaderAuthenticationFilter extends AbstractPreAuthenticatedProcessingFilter, the framework knows where to insert the filter within the chain which is why I'm not tinkering with the addFilterBefore or addFilterAfter variants. Maybe I should be? Does anybody know the correct place to insert the filter explicitly? (I thought the need for explicitly specifying filter order was removed in prior versions of Spring Security)
I know I can configure the RequestHeaderAuthenticationFilter so that it doesn't throw an exception if the header is not present but I'd like to keep that on if at all possible.
I found this SO post that seems to be similar to my problem but unfortunately there's no answer there either.
spring-boot-security-preauthentication-with-permitted-resources-still-authenti
Any help would be greatly appreciated!
Thanks!
The problem was indeed the fact that the RequestHeaderAuthenticationFilter was being registered both as a top level filter (unwanted) and also within the Spring Security FilterChain (desired).
The reason for the "double registration" is because Spring Boot will register any Filter Beans with the Servlet Container automatically.
In order to prevent the "auto-registration" I just had to define a FilterRegistrationBean like so:
#Bean
public FilterRegistrationBean registration(RequestHeaderAuthenticationFilter filter) {
FilterRegistrationBean registration = new FilterRegistrationBean(filter);
registration.setEnabled(false);
return registration;
}
Docs:
https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#howto-disable-registration-of-a-servlet-or-filter
An alternate/simpler solution:
Just don't mark the RequestHeaderAuthenticationFilter class as an #Bean which is fine because there's no kind of DI needed for that particular filter. By not marking the filter with #Bean, Boot won't try to auto register it which removes the need to define the FilterRegistrationBean.
The provided answer is not complete most likely because Sean Casey made so many trial and error changes that lost track of which configuration actually fixed the problem. I am posting my findings which I believe have the correct configuration:
If you have incorrectly registered the RequestHeaderAuthenticationFilter as a #Bean, as the original answer says, then remove it. Just creating it as a normal instance and adding it as a filter registers it properly:
#Override
protected void configure(HttpSecurity http) throws Exception {
RequestHeaderAuthenticationFilter filter = new RequestHeaderAuthenticationFilter();
// configure your filter
http
.authorizeRequests().anyRequest().authenticated()
.and()
.addFilter(filter)
.csrf().disable();
}
The magical configuration which Sean Casey tried but initially failed (due to the double registering of the auth filter) is the WebSecurity configuration:
#Override
public void configure(WebSecurity web) throws Exception {
web.ignoring().antMatchers("/health");
}
Notice that adding the HttpSecurity antMatchers does nothing in that case, it seems that WebSecurity is the one taking precedence and controlling what goes through.
EXTRA: Per Spring Security documentation, the WebSecurity.ignore method should be used for static resources, not for dynamic ones which should instead be mapped to allow all users. However in this case it seems the mapping gets overridden by the PreAuthentication filter which forces the use of the more aggressive ignore scenario.

Resources