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()
}
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.
Thanks for the report, @ttasjwi. This is now merged into main and will go out in the next milestone. Please check the latest 6.4 snapshot to confirm the feature works for you.
Overview
GrantedAuthorityDefaults
bean.Expected Behavior
Current Behavior
Example Code
1. Common - Controller
2. Common - SecurityConfig
3. JavaDSL SecurityFilterChain
4. KotlinDSL SecurityFilterChain
Probable Cause
/user
endpoint.