akuma8
akuma8

Reputation: 4691

How to solve `permitAll only works with HttpSecurity.authorizeRequests()` when migrating from Spring Security OAuth2 to Keycloak?

I am currently migrating from Spring Security OAuth2 to Keycloak (after the Spring Security team's decision to deprecate the Spring Security OAuth2 project) but I am stuck with this exception:

org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'springSecurityFilterChain' defined in class path resource [org/springframework/security/config/annotation/web/configuration/WebSecurityConfiguration.class]: Bean instantiation via factory method failed; nested exception is org.springframework.beans.BeanInstantiationException: Failed to instantiate [javax.servlet.Filter]: Factory method 'springSecurityFilterChain' threw exception; nested exception is java.lang.IllegalStateException: permitAll only works with HttpSecurity.authorizeRequests()
at org.springframework.beans.factory.support.ConstructorResolver.instantiate(ConstructorResolver.java:656)
at org.springframework.beans.factory.support.ConstructorResolver.instantiateUsingFactoryMethod(ConstructorResolver.java:484)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.instantiateUsingFactoryMethod(AbstractAutowireCapableBeanFactory.java:1338)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBeanInstance(AbstractAutowireCapableBeanFactory.java:1177)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:557)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:517)
at org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$0(AbstractBeanFactory.java:323)
at org.springframework.beans.factory.support.AbstractBeanFactory$$Lambda$330.00000000ED7F64A0.getObject(Unknown Source)
at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:222)
at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:321)
at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:202)
at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:310)
at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:202)

I have 10 microservices to migrate, I defined a common jar which contains all configurations. This is the Keycloak configuration in common:

@EnableGlobalMethodSecurity( prePostEnabled = true, securedEnabled = true )
@KeycloakConfiguration
@Import( KeycloakSpringBootConfigResolver.class )
public class CommonsKeycloakSecurityConfigurerAdapter extends KeycloakWebSecurityConfigurerAdapter {

@Override
protected void configure( HttpSecurity http ) throws Exception {
    http.oauth2ResourceServer(); // Equivalent to @EnableResourceServer in S.S. OAuth2
    super.configure( http );
}


@Autowired
public void configureGlobal( AuthenticationManagerBuilder auth ) {

    SimpleAuthorityMapper grantedAuthorityMapper = new SimpleAuthorityMapper();
    grantedAuthorityMapper.setPrefix( "ROLE_" ); 
    grantedAuthorityMapper.setConvertToUpperCase( true );

    KeycloakAuthenticationProvider keycloakAuthenticationProvider = keycloakAuthenticationProvider();
    keycloakAuthenticationProvider.setGrantedAuthoritiesMapper( grantedAuthorityMapper );
    auth.authenticationProvider( keycloakAuthenticationProvider );
}

@Value("${spring.application.name}"
public String APP_NAME;

@Bean
public SessionAuthenticationStrategy sessionAuthenticationStrategyPorvider() {
    return APP_NAME.equals( "securityservice")
            ? new RegisterSessionAuthenticationStrategy( new SessionRegistryImpl() )
            : new NullAuthenticatedSessionStrategy();
}

@Bean
@DependsOn( "sessionAuthenticationStrategyPorvider" )
@Override
protected SessionAuthenticationStrategy sessionAuthenticationStrategy() {
    return sessionAuthenticationStrategyPorvider();
}

@Bean
@Scope( scopeName = WebApplicationContext.SCOPE_REQUEST, proxyMode = ScopedProxyMode.TARGET_CLASS )
public KeycloakSecurityContext provideKeycloakSecurityContext() {
    ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
    Principal principal = requireNonNull( attributes ).getRequest().getUserPrincipal();
    if ( principal == null ) {
        return null;
    }
    if ( principal instanceof KeycloakAuthenticationToken ) {
        principal = (Principal) ( (KeycloakAuthenticationToken) principal ).getPrincipal();
    }
    if ( principal instanceof KeycloakPrincipal ) {
        return ( (KeycloakPrincipal) principal ).getKeycloakSecurityContext();
    }
    return null;
}

}

And properties in common/src/main/resources/application.yml:

keycloak:
  auth-server-url: "" #defined in application-dev.yml and application-prod.yml
  realm: ${project.name}
  resource: ${spring.application.name}
  credentials:
    secret: ${application.kck.secret} 
  use-resource-role-mappings: true
  ssl-required: 'none'
  principal-attribute: preferred_username

The common.jar is then imported in all other microservices. In productservice I have defined these specifique http security rules:

@Configuration
@Order( 1 )
public class UIResourceProtection extends WebSecurityConfigurerAdapter { // Note that I use WebSecurityConfigurerAdapter instead of KeycloakWebSecurityConfigurerAdapter 

    @Override
    public void configure( HttpSecurity http ) throws Exception {
        http.sessionManagement().sessionCreationPolicy( STATELESS );
        http.requestMatchers().antMatchers( "/ui/product/**" )
                .and()
                .cors().and()
                .authorizeRequests()
                .antMatchers( "/ui/product/private").hasRole( "USER" )
                .antMatchers( HttpMethod.PUT, "/ui/product/public" ).hasRole( "USER" )
                .antMatchers( HttpMethod.GET, ""/ui/product/cost"","/ui/product/public").authenticated()
                .antMatchers( HttpMethod.GET, "/ui/product/public/{id}" ).authenticated()
                .antMatchers( "/ui/product/public/dashboard" ).hasRole( "USER" );                    
    }
}

And another one in the same project (i-e: productservice)

@Configuration
@RequiredArgsConstructor
@Order( 2 )
public class SelfResourceProtection extends WebSecurityConfigurerAdapter { // Note that I use WebSecurityConfigurerAdapter instead of KeycloakWebSecurityConfigurerAdapter 

    @Override
    public void configure( HttpSecurity http ) throws Exception {
        http.requestMatchers().antMatchers("/product/**")
                .and()
                .authorizeRequests()
                .antMatchers( HttpMethod.GET, "/product/update-db" ).hasRole( "ADMIN" )
                .antMatchers( HttpMethod.GET, "/product/private/{id}" ).permitAll();     
    }
}

When I start productservice with that configuration I have the exception above: Caused by: java.lang.IllegalStateException: permitAll only works with HttpSecurity.authorizeRequests()

After changing configuration parameters, the exception is still thrown.

Any idea how to solve it?

Dependencies version :

Spring Boot : 2.2.4.RELEASE
Keycloak : 8.0.1
Spring Security: 5.2.1.RELEASE
Spring Security OAuth2 Resource Server: 5.2.1.RELEASE

Thanks a lot

Upvotes: 5

Views: 15787

Answers (2)

Chahine Laater
Chahine Laater

Reputation: 19

http.authorizeRequests().antMatchers().permitAll()

use authorizeRequests not authorizeHttpRequests

Upvotes: 1

This exception is caused by the class CommonsKeycloakSecurityConfigurerAdapter.
If you look at the source code for KeycloakWebSecurityConfigurerAdapter which you are extending, you will notice that part of the configuration states

.logoutUrl("/sso/logout").permitAll()

In order for permitAll() to be applied, Spring Security expects authorizeRequests() to be configured.

Since there is no such configuration in KeycloakWebSecurityConfigurerAdapter, you will need to add an authorizeRequests() block to your custom configuration that extends it.

Upvotes: 4

Related Questions