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.
Related
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.
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());
}
}
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.
I have a Spring Boot project with keycloak integrated. Now I want to disable keycloak for testing purposes.
I tried by adding keycloak.enabled=false to application.properties as mentioned in Keycloak documentation but it didnt work.
So how do I disable it?
Update 2022
Please follow this excellent guide on Baeldung.
For anyone who might have the same trouble, here is what I did.
I didn't disable Keycloak but I made a separate a Keycloak config file for testing purposes.
Here is my config file
#Profile("test")
#Configuration
#EnableWebSecurity
public class SecurityTestConfig extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests().antMatchers("/**").permitAll();
http.headers().frameOptions().disable();
http.csrf().disable();
}
#Override
public void configure(WebSecurity web) throws Exception {
web.ignoring().antMatchers("/**");
}
#Bean
#Scope(scopeName = WebApplicationContext.SCOPE_REQUEST, proxyMode = ScopedProxyMode.TARGET_CLASS)
public AccessToken accessToken() {
AccessToken accessToken = new AccessToken();
accessToken.setSubject("abc");
accessToken.setName("Tester");
return accessToken;
}
}
Please note it is important to use this only in a test environment and therefore I have annotated the config as #Profile("test"). I have also added an AccessToken bean since some of the auditing features in my application depend on it.
It should work, but based on the last comment on the jira ticket for this, it seems it is not.
As the description state you could exclude the spring boot autoconfiguration for keycloak adding to your application.properties: spring.autoconfigure.exclude=org.keycloak.adapters.springboot.KeycloakSpringBootConfiguration
You need to exclude keycloak auto configuration. In order to do this just add this entry to your related spring configuration file, in your case application.properties.
spring.autoconfigure.exclude = org.keycloak.adapters.springboot.KeycloakAutoConfiguration
My Workaround:
1. Create a Custom-Filter and add it to the (Spring) Security-Chain in early position.
2. Create a flag in the application.yml (securityEnabled)
3. Query the flag in the Custom-Filter. If 'true' simply go on with the next filter by calling chain.doFilter(). If 'false' create a dummy Keycloak-Account set the roles you need and set it to the context.
4. By the way the roles are also outsourced to the application.yml
5. Skip the rest of the filters in the Security-Chain (so no keycloak-stuff is executed and the corresponding Authorization happend)
In Detail:
1. Class of Custom-Filter
public class CustomFilter extends OncePerRequestFilter {
#Value("${securityEnabled}")
private boolean securityEnabled;
#Value("${grantedRoles}")
private String[] grantedRoles;
#Override
public void doFilterInternal(HttpServletRequest req, HttpServletResponse res,
FilterChain chain) throws IOException, ServletException {
if (!securityEnabled){
// Read roles from application.yml
Set<String> roles = Arrays.stream(grantedRoles)
.collect(Collectors.toCollection(HashSet::new));
// Dummy Keycloak-Account
RefreshableKeycloakSecurityContext session = new RefreshableKeycloakSecurityContext(null, null, null, null, null, null, null);
final KeycloakPrincipal<RefreshableKeycloakSecurityContext> principal = new KeycloakPrincipal<>("Dummy_Principal", session);
final KeycloakAccount account = new SimpleKeycloakAccount(principal, roles, principal.getKeycloakSecurityContext());
// Dummy Security Context
SecurityContext context = SecurityContextHolder.createEmptyContext();
context.setAuthentication(new KeycloakAuthenticationToken(account, false));
SecurityContextHolder.setContext(context);
// Skip the rest of the filters
req.getRequestDispatcher(req.getServletPath()).forward(req, res);
}
chain.doFilter(req, res);
}
}
2. Insert Custom-Filter in the http-Configuration of Spring-Security
protected void configure(HttpSecurity http) throws Exception {
super.configure(http);
http
.cors()
.and()
.csrf()
.disable()
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.sessionAuthenticationStrategy(sessionAuthenticationStrategy())
.and()
.addFilterAfter(CustomFilter(), CsrfFilter.class)
.authorizeRequests()
.anyRequest().permitAll();
}
Have a look at the default Filter-Chain after configuring Keycloak:
Filter-Chain
So it´s obvious to insert the Custom-Filter at position 5 to avoid the whole Keycloak-Magic.
I have used this workaround to defeat the method security and it´s #Secured-Annotation.
Updated answer for spring boot 2.5.6 and keycloak 16.1.0
Set this in your application.properties:
spring.autoconfigure.exclude=org.keycloak.adapters.springboot.KeycloakAutoConfiguration
(The autoconfig class name has changed since earlier answers)
The keycloak adapter dependency brings in the standard spring security autoconfig too, so if you want to disable both use this:
spring.autoconfigure.exclude=org.keycloak.adapters.springboot.KeycloakAutoConfiguration,org.springframework.boot.autoconfigure.security.servlet.SecurityAutoConfiguration
I have a custom filter which is called before BasicAuthenticationFilter, the Bean is autowired in the SecurityConfig file.
.addFilterBefore(preAuthTenantContextInitializerFilter, BasicAuthenticationFilter.class)
Here is how the filter looks like.
#Component
public class PreAuthTenantContextInitializerFilter extends OncePerRequestFilter {
#Autowired
private TenantService tenantService;
.....
.....
I want this filter to not fire just like the rest of the Spring Security filter chain for paths included in WebSecurityConfigurerAdapter#configure(WebSecurity web) web.ignoring().
Here is how it looks like
#Configuration
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
#Override
public void configure(WebSecurity web) throws Exception {
web.ignoring().antMatchers("/v2/api-docs", "/configuration/ui", "/swagger-resources",
"/configuration/security", "/swagger-ui.html",
"/webjars/**","/swagger-resources/configuration/ui",
"/swagger-ui.html", "/docs/**");
}
}
What I have already tried.
Remove the #Component annotation from the filter class, it only prevented the filter from invoking in any case since the filter is no more picked as a bean and will never make it to the filter chain.
What I am looking for
I want this filter to be called when the rest of the Spring Security chain is called and be ignored for the paths in web.ignoring() just like the rest of the Spring Security filters. Thanks.
Any Servlet, Filter or Servlet *Listener instance that is a Spring bean will be registered with the embedded container. This can be particularly convenient if you want to refer to a value from your application.properties during configuration.
This snippet comes from the Spring Boot reference guide. Basically any Filter detected in the application context will be registered to the default filter chain and mapped to the DispatcherServlet or / .
In your case, as the filter is marked as #Component it will be a bean, Spring Boot detects is and registers it with the embedded container. However you don't want that as you want it to, only, be part of the Spring Security filter chain.
To do so you have 2 options.
Remove the #Component and #Autowired and construct your own instance and don't make it a bean.
Add an additional [FilterRegistrationBean] and set the enabled property to false, this will prevent Spring Boot from registering it with the embedded container.
Herer is the solution for option 2:
#Bean
public FilterRegistrationBean preAuthTenantContextInitializerFilterRegistration(PreAuthTenantContextInitializerFilter filter) {
FilterRegistrationBean registration = new FilterRegistrationBean(filter);
registration.setEnabled(false);
return registration;
}
With this added bean Spring Boot will not register the filter with the embedded container and as such it will only be invoked as part of your Spring Security filter chain.
I presume you are trying to ignore security for loading the Swagger UI page. If that's the case you need to add another path, /swagger-resources/configuration/security, to your list of ignored paths. Once you add this path, your PreAuthTenantContextInitializerFilter will not be invoked when you call any of the ignored paths.
Also, manage the creation of the PreAuthTenantContextInitializerFilter manually instead of having Spring managed. It will involve removing #Component from your filter. You can find a working example here.
#Configuration
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
private TenantService service;
#Autowired
public SecurityConfiguration(TenantService service) {
this.service = service;
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http.addFilterBefore(
new PreAuthTenantContextInitializerFilter(service),
BasicAuthenticationFilter.class);
...
}
#Override
public void configure(WebSecurity web) throws Exception {
web.ignoring().antMatchers("/v2/api-docs",
"/configuration/ui",
"/swagger-resources",
"/configuration/security", "/swagger-ui.html",
"/webjars/**", "/swagger-resources/configuration/ui",
"/swagger-ui.html", "/docs/**",
"/swagger-resources/configuration/security");
}
}