Spring (boot) Security preauthentication with permitted resources still authenticated - spring

I am using Spring Boot 1.5.6 (also have tried with 1.5.4).
I am using a
RequestHeaderAuthenticationFilter
and a
PreAuthenticatedAuthenticationProvider
to secure my spring mvc web app and also permit access to both a controller path and static resources.
In my
RequestHeaderAuthenticationFilter
set up I want
setExceptionIfHeaderMissing(true);
so that I know if the header variable has been sent in the request.
When I try to access any of the permitted resources, Spring Security always looks for the header variable in the request and throws a
PreAuthenticatedCredentialsNotFoundException
Why is spring security still trying to look up the preauthenticated principal even though I am trying to access a permitted (non-protected) resource?
How can I circumvent this behaviour?
My java config for WebSecurityConfigurerAdapter is below
#Configuration
#EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter{
private static final Logger log = LoggerFactory.getLogger(SecurityConfig.class);
#Autowired
protected UserDetailsService userDetailsService;
#Bean
public PreAuthenticatedAuthenticationProvider preAuthenticatedAuthenticationProvider(){
log.info("Configuring pre authentication provider");
UserDetailsByNameServiceWrapper<PreAuthenticatedAuthenticationToken> wrapper =
new UserDetailsByNameServiceWrapper<PreAuthenticatedAuthenticationToken>(
userDetailsService);
PreAuthenticatedAuthenticationProvider it = new PreAuthenticatedAuthenticationProvider();
it.setPreAuthenticatedUserDetailsService(wrapper);
return it;
}
#Bean
public RequestHeaderAuthenticationFilter requestHeaderAuthenticationFilter() throws Exception{
RequestHeaderAuthenticationFilter it = new RequestHeaderAuthenticationFilter();
it.setAuthenticationManager(authenticationManager());
it.setExceptionIfHeaderMissing(true);
return it;
}
#Override
public void configure(AuthenticationManagerBuilder auth) throws Exception {
log.info("configure authentication provider");
auth.authenticationProvider(preAuthenticatedAuthenticationProvider());
}
#Override
protected void configure(HttpSecurity http) throws Exception {
log.info("Configure HttpSecurity");
http
.authorizeRequests()
.antMatchers("/permitted/**", "/css/**", "/js/**", "/images/**", "/webjars/**")
.permitAll()
.anyRequest()
.authenticated()
.and().addFilter(requestHeaderAuthenticationFilter())
;
}
#Override
public void configure(WebSecurity web) throws Exception {
web
.ignoring()
.antMatchers("/permitted/**", "/css/**", "/js/**", "/images/**", "/webjars/**");
}
}

I had the same problem and it turned out it was related to the fact that in addition to being registered in the SecurityFilterChain, Spring Boot was also registering the RequestHeaderAuthenticationFilter with the Servlet Context. The solution is to use a FilterRegistrationBean to prevent Boot from auto registering the filter with the Servlet Context.
More details here:
Spring Boot Security PreAuthenticated Scenario with Anonymous access

Related

spring boot actuator endpoints with Keycloak security

we have a spring boot project (2.3.0.RELEASE) with actuator endpoints and we are introducing keycloak to the project with KeycloakWebSecurityConfigurerAdapter how can I prevent actuator endpoints being secured by the keycloak filter chain.
We would like to have the "/actuator/**" endpoints secured by basic auth.
Currently we have a custom WebSecurityConfigurerAdapter with #Order(1) where we apply the basic auth to "/actuator/**" and then we have with #Order(2) antotated the KeycloakWebSecurityConfigurerAdapter
so 2 filter chains gets registered and when I call the actuator endpoints the second filter chain fails as unauthorised 401
is it possible to prevent handling the "/actuator/**" resorce path on the second filter chain?
First actuator security configuration.
#Configuration
#Order(1)
public class ActuatorWebSecurityConfig extends WebSecurityConfigurerAdapter {
private final String username;
private final String password;
private final PasswordEncoder encoder;
public ActuatorWebSecurityConfig(
#Value("${spring.security.user.name}") String username,
#Value("${spring.security.user.password}") String password,
Optional<PasswordEncoder> encoder) {
this.username = username;
this.password = password;
this.encoder = encoder.orElseGet(PasswordEncoderFactories::createDelegatingPasswordEncoder);
}
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.inMemoryAuthentication()
.withUser(username)
.password(encoder.encode(password))
.roles("USER");
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.antMatcher("/actuator/**")
.authorizeRequests(authorize -> authorize.anyRequest().authenticated())
.httpBasic(Customizer.withDefaults());
}
}
second keycloak securoty configuration
#Order(2)
#KeycloakConfiguration
public class SecurityConfig extends KeycloakWebSecurityConfigurerAdapter {
private final String swaggerUrl;
private final CorsFilter corsFilter;
private final CustomSecurityConfig customSecurityConfig;
#Autowired
public SecurityConfig(
#Value("${springdoc.swagger-ui.url:#{null}}") String swaggerUrl,
CorsFilter corsFilter,
CustomSecurityConfig customSecurityConfig) {
this.swaggerUrl = swaggerUrl;
this.corsFilter = corsFilter;
this.customSecurityConfig = customSecurityConfig;
}
#Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
KeycloakAuthenticationProvider keycloakProvider = keycloakAuthenticationProvider();
keycloakProvider.setGrantedAuthoritiesMapper(new SimpleAuthorityMapper());
auth.authenticationProvider(keycloakProvider);
}
#Bean
#Override
protected SessionAuthenticationStrategy sessionAuthenticationStrategy() {
return new NullAuthenticatedSessionStrategy();
}
#Override
protected void configure(HttpSecurity http) throws Exception {
super.configure(http);
http.csrf().disable()
.requestMatcher(new NegatedRequestMatcher(new AntPathRequestMatcher("/actuator/**")));
.headers().frameOptions().disable()
.and()
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.authorizeRequests()
.antMatchers("/public/**", "/resources/**", "/resources/public/**").permitAll()
.antMatchers(OPTIONS, "/**").permitAll();
.authorizeRequests()
.antMatchers("/**")
.authenticated();
}
}
I have tried with on keycloak config
.antMatchers("/actuator/**").permitAll();
and with
http.requestMatcher(new NegatedRequestMatcher(new AntPathRequestMatcher("/actuator/**")));
but nothing works I receive unauthorised 401 for actuator
the registered filter chains :
2022-01-18 17:38:44,688 INFO org.springframework.security.web.DefaultSecurityFilterChain [main] Creating filter chain: Ant [pattern='/actuator/**'], [org.springframework.security.web.context.request.async.WebAsyncManagerIntegrationFilter#25c6a9de, org.springframework.security.web.context.SecurityContextPersistenceFilter#56f3f9da, org.springframework.security.web.header.HeaderWriterFilter#33dcbdc2, org.springframework.security.web.csrf.CsrfFilter#522fdf0c, org.springframework.security.web.authentication.logout.LogoutFilter#365ad794, org.springframework.security.web.authentication.www.BasicAuthenticationFilter#23df16cf, org.springframework.security.web.savedrequest.RequestCacheAwareFilter#227cba85, org.springframework.security.web.servletapi.SecurityContextHolderAwareRequestFilter#b38dc7d, org.springframework.security.web.authentication.AnonymousAuthenticationFilter#142422a4, org.springframework.security.web.session.SessionManagementFilter#2f0b7b6d, org.springframework.security.web.access.ExceptionTranslationFilter#74bca236, org.springframework.security.web.access.intercept.FilterSecurityInterceptor#30587737]
2022-01-18 17:38:44,691 INFO org.springframework.security.web.DefaultSecurityFilterChain [main] Creating filter chain: NegatedRequestMatcher [requestMatcher=Ant [pattern='/actuator/**']], [com.betex.auth.filters.CorsFilter#20a9f5fb, org.springframework.security.web.context.request.async.WebAsyncManagerIntegrationFilter#10e28d97, org.springframework.security.web.context.SecurityContextPersistenceFilter#c6b08a5, org.springframework.security.web.header.HeaderWriterFilter#5f05cd7e, org.keycloak.adapters.springsecurity.filter.KeycloakPreAuthActionsFilter#2a54c92e, org.keycloak.adapters.springsecurity.filter.KeycloakAuthenticationProcessingFilter#55b62db8, org.springframework.security.web.authentication.logout.LogoutFilter#274f51ad, org.springframework.security.web.savedrequest.RequestCacheAwareFilter#54980154, org.springframework.security.web.servletapi.SecurityContextHolderAwareRequestFilter#25874884, org.keycloak.adapters.springsecurity.filter.KeycloakSecurityContextRequestFilter#8cb7185, org.keycloak.adapters.springsecurity.filter.KeycloakAuthenticatedActionsFilter#4dac40b, org.springframework.security.web.authentication.AnonymousAuthenticationFilter#37d43b9b, org.springframework.security.web.session.SessionManagementFilter#11e8e183, org.springframework.security.web.access.ExceptionTranslationFilter#56f1db5f, org.springframework.security.web.access.intercept.FilterSecurityInterceptor#78543f0d]
When you extend KeycloakWebSecurityConfigurerAdapter, the adapter register a Bean of type KeycloakAuthenticationProcessingFilter. This filter is registered in the Spring Security's SecurityFilterChain, and because it's a Bean, it is also automatically registered by Spring Boot in the original chain, therefore even if Spring Security doesn't apply it, it will be applied later on in original the filter chain.
Try disabling this filter from being registered by Spring Boot, like so:
#Bean
public FilterRegistrationBean registration(KeycloakAuthenticationProcessingFilter filter) {
FilterRegistrationBean registration = new FilterRegistrationBean(filter);
registration.setEnabled(false);
return registration;
}
In addition, if you are using OAuth 2, you may consider using spring-security-oauth2-resource-server and simplifying your Resource Server's configuration. Take a look at the documentation. This way you don't need to extend the custom adapter, just rely on the out-of-the-box configuration from Spring Security.

Spring boot multiple authentication

I've done a spring boot application with spring security integration and a custom Authentication Provider. Now I want that some link are going to use custom Authent provider and some other an inmemory authentication. How can I do that?
You're right. Here is my SecurityConfig class. Now i want protect path like '/api/**' with a basic authentication.
#Autowired
private CustomAuthenticationProvider authProvider;
#Autowired
DataSource dataSource;
#Autowired
CustomLogoutSuccessHandler customLogoutSuccessHandler;
#Autowired
CustomAuthenticationSuccessHandler customAutheincationSuccessHandler;
#Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
auth.authenticationProvider(authProvider);
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.antMatchers("/list/**").hasAnyAuthority("Administrator","Operator")
.antMatchers("/api/**").permitAll()
.and()
.formLogin()
.loginPage("/login")
.loginProcessingUrl("/authenticateTheUser")
.successHandler(customAutheincationSuccessHandler)
.and().logout()
.logoutUrl("/logout")
.logoutSuccessHandler(customLogoutSuccessHandler)
.logoutSuccessUrl("/login").and().exceptionHandling().accessDeniedPage("/accessDenied")
.and()
.csrf().disable();
}
You can create two spring security configurations with different paths. In first configuration u can exclude /api/**. But second configuration will be able to check it.

Spring Boot: Authenticating both a Stateless REST API and a Stateful "Login" Web Controller in the same project?

So I have an application that contains a REST API which is used by a custom java application on an IOT device with no user interaction.And I also have a web app which needs a stateful session for maintaining user login.
Is it possible to use Spring Security to authenticate requests to my API and web controller differently?What form of authentication should I be using for the REST API?
One way to achieve what you are looking for is to have 2 configurations in your spring security. E.g.
Pay attention to antMatcher (matcher not matchers). The antMatcher will control on what set of url your entire config applies i.e. FormLoginWebSecurityConfigurerAdapter in below example will apply only to uri matching /api/test/**. Of course, you can define the antMatcher only in one of the configs say config1 and the other config in that case will be a catch all (i.e catch everything that does not match config1)
#EnableWebSecurity
#Configuration
public class SecurityConfig {
#Configuration
#Order(1)
public static class ApiWebSecurityConfigurationAdapter extends WebSecurityConfigurerAdapter {
#Override
public void configure(AuthenticationManagerBuilder auth)
throws Exception {
auth.inMemoryAuthentication().withUser("user").password("user").roles("USER");
auth.inMemoryAuthentication().withUser("admin").password("admin").roles("ADMIN");
}
protected void configure(HttpSecurity http) throws Exception {
http.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
http
.antMatcher("/api/v1/**")
.authorizeRequests()
.antMatchers("/api/v1/**").authenticated()
.and()
.httpBasic();
}
}
#Configuration
#Order(2)
public static class FormLoginWebSecurityConfigurerAdapter extends WebSecurityConfigurerAdapter {
#Override
public void configure(AuthenticationManagerBuilder auth)
throws Exception {
auth.inMemoryAuthentication().withUser("user1").password("user").roles("USER");
auth.inMemoryAuthentication().withUser("admin1").password("admin").roles("ADMIN");
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.IF_REQUIRED); // CONFIGURE TYPE OF SESSION POLICY
http
.antMatcher("/api/test/**")
.authorizeRequests()
.antMatchers("/api/test/**").authenticated()
.and()
.formLogin();
}
}
}

Spring Boot Management security works differently with port set

I'm trying to configure a Spring Boot application (1.2.3, but this also fails with the 1.2.4.BUILD-SNAPSHOT version) with Actuator support. I want to use the Actuator security config for controlling access to the management endpoints, and our own authentication for the rest of the application.
Here is my security config:
#Configuration
#EnableWebSecurity
#Order(SecurityProperties.ACCESS_OVERRIDE_ORDER)
public class SecurityConfiguration extends WebSecurityConfigurerAdapter
{
#Autowired
private CustomAuthenticationProvider customAuthProvider;
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception
{
auth.authenticationProvider(customAuthProvider);
}
#Override
protected void configure(HttpSecurity http) throws Exception
{
http
.authorizeRequests()
.regexMatchers(API_DOC_REGEX).permitAll()
.regexMatchers(String.format(PATH_REGEX, PUBLIC_ACCESS)).permitAll()
.regexMatchers(String.format(PATH_REGEX, INTERNAL_ACCESS)).access("isAuthenticated() && authentication.hasOrigin('INTERNAL')")
.regexMatchers(String.format(PATH_REGEX, EXTERNAL_AUTHENTICATED_ACCESS)).authenticated()
.antMatchers("/**").denyAll()
.and()
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.NEVER)
.and()
.addFilterAfter(customAuthProcessingFilter(), BasicAuthenticationFilter.class)
.csrf().disable();
}
}
This works correctly when I don't set a management port, but when I set the management port, the management URLs return 401 responses. If I comment out the line .antMatchers("/**").denyAll(), then everything goes through without requiring authentication at all. So it looks like it is using my application's security config for the Actuator endpoints when I set a custom port, but I'm not sure why.
How do I get it to use it's own security when running on a custom port?
Expanding on the comment from #M. Deinum, adding another adapter for the Management stuff (even though it already has one) seems to have fixed it. This is the class I ended up with:
#Order(0)
#Configuration
public class ManagementSecurityConfig extends WebSecurityConfigurerAdapter
{
#Autowired
ManagementServerProperties managementProperties;
#Override
protected void configure(HttpSecurity http) throws Exception
{
http
.requestMatchers()
.requestMatchers(new RequestMatcher()
{
#Override
public boolean matches(HttpServletRequest request)
{
return managementProperties.getContextPath().equals(request.getContextPath());
}
})
.and()
.authorizeRequests()
.anyRequest().hasRole("ADMIN")
.and()
.httpBasic();
}
}

In Spring Security 3.2.5, what is causing an infinite loop inside the AuthenticationManager implementation?

I had an interesting situation not long ago which caused an infinite loop (and eventually a stack overflow) in Spring Security's AuthenticationManager. For months, everything worked as expected, but then I decided to transfer my XML configuration to code-only configuration. Here was my basic setup in Java configuration:
#Configuration
#EnableWebMvcSecurity
#ComponentScan(basePackages = { "com.my.company" })
public class SecurityConfig extends WebSecurityConfigurerAdapter {
// Disable default configuration
public SecurityConfig() {
super(true);
}
#Autowired
AuthenticationProviderImpl authenticationProvider;
#Autowired
MyAuthenticationEntryPoint customAuthenticationEntryPoint;
#Autowired
AuthenticationTokenProcessingFilter authenticationTokenProcessingFilter;
#Bean(name = "authenticationManager")
#Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
#Override
public void configure(WebSecurity web) throws Exception {
// Ignore requests of resources in security
web.ignoring().antMatchers("/resources/**")
// Ignore requests to authentication
.and().ignoring().antMatchers("/auth/**");
}
#Override
protected void configure(HttpSecurity http) throws Exception {
// Define main authentication filter
http.addFilterBefore(authenticationTokenProcessingFilter,
UsernamePasswordAuthenticationFilter.class)
// Request path authorization
.authorizeRequests()
.antMatchers("/api/**")
.access("isAuthenticated()")
// Authentication provider
.and()
.authenticationProvider(authenticationProvider)
// Security failure exception handling
.exceptionHandling()
.authenticationEntryPoint(customAuthenticationEntryPoint)
// Session Management
.and().sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
// Default security HTTP headers
.and().headers().xssProtection().frameOptions()
.cacheControl().contentTypeOptions();
}
}
However, I soon found out that this configuration causes issues with my AuthenticationProviderImpl (which implements the Spring Security AuthenticationProvider interface). When the implementation's overridden authenticate method throws a BadCredentialsException, the exact same method in that class is called again perpetually until the stack overflows. The good news is that I fixed my configuration by simply overriding configure(AuthenticationManagerBuilder builder) in the SecurityConfig and declaring my implementation of the AuthenticationProvider there instead of in configure(HttpSecurity http). Here is the fixed version:
#Configuration
#EnableWebMvcSecurity
#ComponentScan(basePackages = { "com.my.company" })
public class SecurityConfig extends WebSecurityConfigurerAdapter {
// Disable default configuration
public SecurityConfig() {
super(true);
}
#Autowired
AuthenticationProviderImpl authenticationProvider;
#Autowired
MyAuthenticationEntryPoint customAuthenticationEntryPoint;
#Autowired
AuthenticationTokenProcessingFilter authenticationTokenProcessingFilter;
#Bean(name = "authenticationManager")
#Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
#Override
public void configure(AuthenticationManagerBuilder builder) {
// Configure the authentication manager WITH the authentication
// provider. Not overriding this method causes very bad things to
// happen.
builder.authenticationProvider(authenticationProvider);
}
#Override
public void configure(WebSecurity web) throws Exception {
// Ignore requests of resources in security
web.ignoring().antMatchers("/resources/**")
// Ignore requests to authentication
.and().ignoring().antMatchers("/auth/**");
}
#Override
protected void configure(HttpSecurity http) throws Exception {
// Define main authentication filter
http.addFilterBefore(authenticationTokenProcessingFilter,
UsernamePasswordAuthenticationFilter.class)
// Request path authorization
.authorizeRequests()
.antMatchers("/api/**")
.access("isAuthenticated()")
.and()
// Security failure exception handling
.exceptionHandling()
.authenticationEntryPoint(customAuthenticationEntryPoint)
// Session Management
.and().sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
// Default security HTTP headers
.and().headers().xssProtection().frameOptions()
.cacheControl().contentTypeOptions();
}
}
Though I believe my problem is solved with the fixed configuration, I still have no idea why the application was infinitely calling authenticate() when an exception was thrown by my implementation of AuthenticationProvider? I tried stepping through and examining the Spring Security classes, but I was not finding a logical answer. Thanks ahead for your expertise!
A few weeks ago I reproduced this behavior, too, see this thread on stackoverflow.
Dealing with the question I figured out that loops occur when the AuthenticationManager internally iterates through it's list of associated AuthenticationProviders, then finds a custom provider and tries to do the authentication using the provider that has been found. If the provider delegates the authentication back to the AuthenticationManager by calling authenticate(), you are in the loop. I guess your AuthenticationProviderImpl does something like that?
The order of your in the providers inside the java.util.List of the AuthenticationManager matters. The order is given by your configuration, e.g. by doing what you tried at first:
// Authentication provider
.and()
.authenticationProvider(authenticationProvider)
By changing your configuration, you influenced the internally managed list of providers attached to your manager, which in the end will solve your code.

Resources