spring-projects / spring-security

Spring Security
http://spring.io/projects/spring-security
Apache License 2.0
8.55k stars 5.79k forks source link

Support `RoleHierarchy` Bean in `authorizeHttpRequests` Kotlin DSL #15136

Closed ttasjwi closed 2 weeks ago

ttasjwi commented 1 month ago

Hello, Spring Security Team.

I have encountered an issue when configuring security with Kotlin DSL and RoleHierarchy. The behavior seems inconsistent compared to the traditional DSL configuration.

Controller

@RestController
class SecurityController {

    @GetMapping("/")
    fun index(): String {
        return "index"
    }

    @GetMapping("/user")
    fun user(): String {
        return "user"
    }

    @GetMapping("/db")
    fun db(): String {
        return "db"
    }

    @GetMapping("/admin")
    fun admin(): String {
        return "admin"
    }
}

UserDetailsService

    @Bean
    fun userDetailsService(): UserDetailsService {
        val user = User.withUsername("user").password("{noop}1111").roles("USER").build()
        val db = User.withUsername("db").password("{noop}1111").roles("DB").build()
        val admin = User.withUsername("admin").password("{noop}1111").roles("ADMIN").build()
        return InMemoryUserDetailsManager(user, db, admin)
    }

RoleHierarchy Bean

The RoleHierarchy bean is configured as follows:

    @Bean
    fun roleHierarchy(): RoleHierarchy {
        val roleHierarchy = RoleHierarchyImpl()
        roleHierarchy.setHierarchy(
            """
            ROLE_ADMIN > ROLE_DB
            ROLE_DB > ROLE_USER
            ROLE_USER > ROLE_ANONYMOUS
        """.trimIndent()
        )
        return roleHierarchy
    }

Traditional DSL Configuration

Using the traditional DSL configuration, RoleHierarchy works as expected:

@Bean
fun securityFilterChain(http: HttpSecurity): SecurityFilterChain {
    http.authorizeHttpRequests {
        it
            .requestMatchers("/user").hasRole("USER")
            .requestMatchers("/admin").hasRole("ADMIN")
            .requestMatchers("/db").hasRole("DB")
    }
        .formLogin(Customizer.withDefaults())
        .csrf { it.disable() }
    return http.build()
}

Kotlin DSL Configuration

However, when using the Kotlin DSL configuration, the RoleHierarchy seems to behave inconsistently:

import org.springframework.security.config.annotation.web.invoke

@Bean
fun securityFilterChain(http: HttpSecurity): SecurityFilterChain {
    http {
        authorizeHttpRequests {
            authorize("/user", hasRole("USER"))
            authorize("/admin", hasRole("ADMIN"))
            authorize("/db", hasRole("DB"))
            authorize(anyRequest, authenticated)
        }
        formLogin { }
        csrf { disable() }
    }
    return http.build()
}

Environment

marcusdacoregio commented 1 month ago

Hi, @ttasjwi. Thanks for the report.

Can you clarify what is inconsistent between the two configurations?

ttasjwi commented 1 month ago

Hi, @ttasjwi. Thanks for the report.

Can you clarify what is inconsistent between the two configurations?

Hi,

To clarify the inconsistencies between the two configurations:

When using the traditional DSL configuration, the RoleHierarchy works as expected. However, when using the Kotlin DSL configuration, the RoleHierarchy seems to behave inconsistently. For example, an ADMIN user should be able to access the /user endpoint, but in practice, this is not always the case.

Here's a summary of the issue I’m encountering:

The only difference between the two setups is the declaration of the securityFilterChain method. All other components, including the Controller, UserDetailsService, and RoleHierarchy bean, remain the same.

Here are the two securityFilterChain configurations for comparison:

Traditional DSL Configuration

@Bean
fun securityFilterChain(http: HttpSecurity): SecurityFilterChain {
    http.authorizeHttpRequests {
        it
            .requestMatchers("/user").hasRole("USER")
            .requestMatchers("/admin").hasRole("ADMIN")
            .requestMatchers("/db").hasRole("DB")
    }
        .formLogin(Customizer.withDefaults())
        .csrf { it.disable() }
    return http.build()
}

Kotlin DSL Configuration

import org.springframework.security.config.annotation.web.invoke

@Bean
fun securityFilterChain(http: HttpSecurity): SecurityFilterChain {
    http {
        authorizeHttpRequests {
            authorize("/user", hasRole("USER"))
            authorize("/admin", hasRole("ADMIN"))
            authorize("/db", hasRole("DB"))
            authorize(anyRequest, authenticated)
        }
        formLogin { }
        csrf { disable() }
    }
    return http.build()
}

I would like to know if I am missing something in my Kotlin DSL configuration or if there are additional steps required to properly set up the RoleHierarchy in Kotlin DSL.

Thank you for your assistance.

marcusdacoregio commented 1 month ago

Hi @ttasjwi. I see that there is no support for RoleHierarchy bean in the authorizeHttpRequests Kotlin DSL. The issue https://github.com/spring-projects/spring-security/issues/13188 has added it but only to the Java DSL.

I'll repurpose this ticket to add the support to the Kotlin DSL.