Skip to content

Commit 7c43fc1

Browse files
Support RoleHierarchy Bean in authorizeHttpRequests Kotlin DSL
Closes gh-15136
1 parent ed2b654 commit 7c43fc1

File tree

3 files changed

+106
-10
lines changed

3 files changed

+106
-10
lines changed

config/src/main/kotlin/org/springframework/security/config/annotation/web/AuthorizeHttpRequestsDsl.kt

Lines changed: 35 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@ package org.springframework.security.config.annotation.web
1818

1919
import org.springframework.context.ApplicationContext
2020
import org.springframework.http.HttpMethod
21+
import org.springframework.security.access.hierarchicalroles.NullRoleHierarchy
22+
import org.springframework.security.access.hierarchicalroles.RoleHierarchy
2123
import org.springframework.security.authorization.AuthenticatedAuthorizationManager
2224
import org.springframework.security.authorization.AuthorityAuthorizationManager
2325
import org.springframework.security.authorization.AuthorizationDecision
@@ -65,6 +67,7 @@ class AuthorizeHttpRequestsDsl : AbstractRequestMatcherDsl {
6567

6668
private val authorizationRules = mutableListOf<AuthorizationManagerRule>()
6769
private val rolePrefix: String
70+
private val roleHierarchy: RoleHierarchy
6871

6972
private val HANDLER_MAPPING_INTROSPECTOR_BEAN_NAME = "mvcHandlerMappingIntrospector"
7073
private val HANDLER_MAPPING_INTROSPECTOR = "org.springframework.web.servlet.handler.HandlerMappingIntrospector"
@@ -210,7 +213,8 @@ class AuthorizeHttpRequestsDsl : AbstractRequestMatcherDsl {
210213
* @return the [AuthorizationManager] with the provided authority
211214
*/
212215
fun hasAuthority(authority: String): AuthorizationManager<RequestAuthorizationContext> {
213-
return AuthorityAuthorizationManager.hasAuthority(authority)
216+
val manager = AuthorityAuthorizationManager.hasAuthority<RequestAuthorizationContext>(authority)
217+
return withRoleHierarchy(manager)
214218
}
215219

216220
/**
@@ -220,7 +224,8 @@ class AuthorizeHttpRequestsDsl : AbstractRequestMatcherDsl {
220224
* @return the [AuthorizationManager] with the provided authorities
221225
*/
222226
fun hasAnyAuthority(vararg authorities: String): AuthorizationManager<RequestAuthorizationContext> {
223-
return AuthorityAuthorizationManager.hasAnyAuthority(*authorities)
227+
val manager = AuthorityAuthorizationManager.hasAnyAuthority<RequestAuthorizationContext>(*authorities)
228+
return withRoleHierarchy(manager)
224229
}
225230

226231
/**
@@ -230,7 +235,8 @@ class AuthorizeHttpRequestsDsl : AbstractRequestMatcherDsl {
230235
* @return the [AuthorizationManager] with the provided role
231236
*/
232237
fun hasRole(role: String): AuthorizationManager<RequestAuthorizationContext> {
233-
return AuthorityAuthorizationManager.hasAnyRole(this.rolePrefix, arrayOf(role))
238+
val manager = AuthorityAuthorizationManager.hasAnyRole<RequestAuthorizationContext>(this.rolePrefix, arrayOf(role))
239+
return withRoleHierarchy(manager)
234240
}
235241

236242
/**
@@ -240,7 +246,8 @@ class AuthorizeHttpRequestsDsl : AbstractRequestMatcherDsl {
240246
* @return the [AuthorizationManager] with the provided roles
241247
*/
242248
fun hasAnyRole(vararg roles: String): AuthorizationManager<RequestAuthorizationContext> {
243-
return AuthorityAuthorizationManager.hasAnyRole(this.rolePrefix, arrayOf(*roles))
249+
val manager = AuthorityAuthorizationManager.hasAnyRole<RequestAuthorizationContext>(this.rolePrefix, arrayOf(*roles))
250+
return withRoleHierarchy(manager)
244251
}
245252

246253
/**
@@ -296,15 +303,34 @@ class AuthorizeHttpRequestsDsl : AbstractRequestMatcherDsl {
296303

297304
constructor() {
298305
this.rolePrefix = "ROLE_"
306+
this.roleHierarchy = NullRoleHierarchy()
299307
}
300308

301309
constructor(context: ApplicationContext) {
310+
val rolePrefix = resolveRolePrefix(context)
311+
this.rolePrefix = rolePrefix
312+
val roleHierarchy = resolveRoleHierarchy(context)
313+
this.roleHierarchy = roleHierarchy
314+
}
315+
316+
private fun resolveRolePrefix(context: ApplicationContext): String {
302317
val beanNames = context.getBeanNamesForType(GrantedAuthorityDefaults::class.java)
303-
if (beanNames.size > 0) {
304-
val grantedAuthorityDefaults = context.getBean(GrantedAuthorityDefaults::class.java);
305-
this.rolePrefix = grantedAuthorityDefaults.rolePrefix
306-
} else {
307-
this.rolePrefix = "ROLE_"
318+
if (beanNames.isNotEmpty()) {
319+
return context.getBean(GrantedAuthorityDefaults::class.java).rolePrefix
308320
}
321+
return "ROLE_";
322+
}
323+
324+
private fun resolveRoleHierarchy(context: ApplicationContext): RoleHierarchy {
325+
val beanNames = context.getBeanNamesForType(RoleHierarchy::class.java)
326+
if (beanNames.isNotEmpty()) {
327+
return context.getBean(RoleHierarchy::class.java)
328+
}
329+
return NullRoleHierarchy()
330+
}
331+
332+
private fun withRoleHierarchy(manager: AuthorityAuthorizationManager<RequestAuthorizationContext>): AuthorityAuthorizationManager<RequestAuthorizationContext> {
333+
manager.setRoleHierarchy(this.roleHierarchy)
334+
return manager
309335
}
310336
}

config/src/test/kotlin/org/springframework/security/config/annotation/web/AuthorizeHttpRequestsDslTests.kt

Lines changed: 69 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2022 the original author or authors.
2+
* Copyright 2002-2024 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -25,6 +25,8 @@ import org.springframework.beans.factory.annotation.Autowired
2525
import org.springframework.context.annotation.Bean
2626
import org.springframework.context.annotation.Configuration
2727
import org.springframework.http.HttpMethod
28+
import org.springframework.security.access.hierarchicalroles.RoleHierarchy
29+
import org.springframework.security.access.hierarchicalroles.RoleHierarchyImpl
2830
import org.springframework.security.authorization.AuthorizationDecision
2931
import org.springframework.security.authorization.AuthorizationManager
3032
import org.springframework.security.config.annotation.web.builders.HttpSecurity
@@ -892,4 +894,70 @@ class AuthorizeHttpRequestsDslTests {
892894
return GrantedAuthorityDefaults("CUSTOM_")
893895
}
894896
}
897+
898+
@Test
899+
fun `hasRole when role hierarchy configured then honor hierarchy`() {
900+
this.spring.register(RoleHierarchyConfig::class.java).autowire()
901+
this.mockMvc.get("/protected") {
902+
with(httpBasic("admin", "password"))
903+
}.andExpect {
904+
status {
905+
isOk()
906+
}
907+
}
908+
this.mockMvc.get("/protected") {
909+
with(httpBasic("user", "password"))
910+
}.andExpect {
911+
status {
912+
isOk()
913+
}
914+
}
915+
}
916+
917+
@Configuration
918+
@EnableWebSecurity
919+
@EnableWebMvc
920+
open class RoleHierarchyConfig {
921+
922+
@Bean
923+
open fun securityFilterChain(http: HttpSecurity): SecurityFilterChain {
924+
http {
925+
authorizeHttpRequests {
926+
authorize("/protected", hasRole("USER"))
927+
}
928+
httpBasic { }
929+
}
930+
return http.build()
931+
}
932+
933+
@Bean
934+
open fun roleHierarchy(): RoleHierarchy {
935+
return RoleHierarchyImpl.fromHierarchy("ROLE_ADMIN > ROLE_USER")
936+
}
937+
938+
@Bean
939+
open fun userDetailsService(): UserDetailsService {
940+
val user = User.withDefaultPasswordEncoder()
941+
.username("user")
942+
.password("password")
943+
.roles("USER")
944+
.build()
945+
val admin = User.withDefaultPasswordEncoder()
946+
.username("admin")
947+
.password("password")
948+
.roles("ADMIN")
949+
.build()
950+
return InMemoryUserDetailsManager(user, admin)
951+
}
952+
953+
@RestController
954+
internal class PathController {
955+
956+
@RequestMapping("/protected")
957+
fun path() {
958+
}
959+
960+
}
961+
962+
}
895963
}

docs/modules/ROOT/pages/whats-new.adoc

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,3 +5,5 @@ Spring Security 6.4 provides a number of new features.
55
Below are the highlights of the release, or you can view https://github.com/spring-projects/spring-security/releases[the release notes] for a detailed listing of each feature and bug fix.
66

77
- https://github.com/spring-projects/spring-security/issues/4186[gh-4186] - Support `RoleHierarchy` in `AclAuthorizationStrategyImpl`
8+
- https://github.com/spring-projects/spring-security/issues/15136[gh-15136] - Support `RoleHierarchy` Bean in `authorizeHttpRequests` Kotlin DSL
9+

0 commit comments

Comments
 (0)