Skip to content

Support GrantedAuthorityDefaults Bean in authorizeHttpRequests Kotlin DSL #15171

Closed
@ttasjwi

Description

@ttasjwi

Overview

  • In the Java DSL configuration, the role prefix is correctly applied based on the configured GrantedAuthorityDefaultsbean.
  • However, in the Kotlin DSL configuration, the role prefix is disregarded, leading to unexpected behavior.

Expected Behavior

  • The role prefix configured in the GrantedAuthorityDefaults bean should be respected and applied consistently across both Java DSL and Kotlin DSL configurations.

Current Behavior

  • The role prefix is properly applied in the Java DSL configuration but ignored in the Kotlin DSL configuration.

Example Code

1. Common - 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"
    }
}

2. Common - SecurityConfig

    @Bean
    fun grantedAuthorityDefaults(): GrantedAuthorityDefaults {
        return GrantedAuthorityDefaults("MYPREFIX_")
    }

    @Bean
    fun userDetailsService(): UserDetailsService {
        val user = User.withUsername("user").password("{noop}1111").authorities("MYPREFIX_USER").build()
        val db = User.withUsername("db").password("{noop}1111").authorities("MYPREFIX_DB").build()
        val admin = User.withUsername("admin").password("{noop}1111").authorities("MYPREFIX_ADMIN").build()
        return InMemoryUserDetailsManager(user, db, admin)
    }

3. JavaDSL SecurityFilterChain

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

4. KotlinDSL SecurityFilterChain

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

Probable Cause

  • In Java DSL, the AuthorizeHttpRequestsConfigurer operates, utilizing the GrantedAuthorityDefaults bean registered by the developer to apply the role prefix.
        // AuthorizeHttpRequestsConfigurer
	public AuthorizeHttpRequestsConfigurer(ApplicationContext context) {
		this.registry = new AuthorizationManagerRequestMatcherRegistry(context);
		if (context.getBeanNamesForType(AuthorizationEventPublisher.class).length > 0) {
			this.publisher = context.getBean(AuthorizationEventPublisher.class);
		}
		else {
			this.publisher = new SpringAuthorizationEventPublisher(context);
		}
		this.roleHierarchy = SingletonSupplier.of(() -> (context.getBeanNamesForType(RoleHierarchy.class).length > 0)
				? context.getBean(RoleHierarchy.class) : new NullRoleHierarchy());
		String[] grantedAuthorityDefaultsBeanNames = context.getBeanNamesForType(GrantedAuthorityDefaults.class);
		if (grantedAuthorityDefaultsBeanNames.length > 0) {
			GrantedAuthorityDefaults grantedAuthorityDefaults = context.getBean(GrantedAuthorityDefaults.class);
			this.rolePrefix = grantedAuthorityDefaults.getRolePrefix();
		}
	}
  • However, in Kotlin DSL, the hasRole configuration invokes the hasRole method of AuthorizeHttpRequestsDsl, which in turn calls AuthorityAuthorizationManager.hasRole(role), disregarding the GrantedAuthorityDefaults bean registered by the developer.
    // AuthorizeHttpRequestsDsl.hasRole
    fun hasRole(role: String): AuthorizationManager<RequestAuthorizationContext> {
        return AuthorityAuthorizationManager.hasRole(role)
    }
        // AuthorityAuthorizationManager
	public static <T> AuthorityAuthorizationManager<T> hasRole(String role) {
		Assert.notNull(role, "role cannot be null");
		Assert.isTrue(!role.startsWith(ROLE_PREFIX), () -> role + " should not start with " + ROLE_PREFIX + " since "
				+ ROLE_PREFIX + " is automatically prepended when using hasRole. Consider using hasAuthority instead.");
		return hasAuthority(ROLE_PREFIX + role);
	}
  • This results in only the ROLE_ prefix being used, diverging from the developer's intention.
  • For instance, a user with the "MYPREFIX_USER" role might fail authorization for the /user endpoint.

Metadata

Metadata

Assignees

Labels

in: configAn issue in spring-security-configtype: enhancementA general enhancement

Type

No type

Projects

No projects

Milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions