소스 검색

Support RoleHierarchy Bean in authorizeHttpRequests Kotlin DSL

Closes gh-15136
Marcus Hert Da Coregio 1 년 전
부모
커밋
7c43fc111f

+ 35 - 9
config/src/main/kotlin/org/springframework/security/config/annotation/web/AuthorizeHttpRequestsDsl.kt

@@ -18,6 +18,8 @@ package org.springframework.security.config.annotation.web
 
 import org.springframework.context.ApplicationContext
 import org.springframework.http.HttpMethod
+import org.springframework.security.access.hierarchicalroles.NullRoleHierarchy
+import org.springframework.security.access.hierarchicalroles.RoleHierarchy
 import org.springframework.security.authorization.AuthenticatedAuthorizationManager
 import org.springframework.security.authorization.AuthorityAuthorizationManager
 import org.springframework.security.authorization.AuthorizationDecision
@@ -65,6 +67,7 @@ class AuthorizeHttpRequestsDsl : AbstractRequestMatcherDsl {
 
     private val authorizationRules = mutableListOf<AuthorizationManagerRule>()
     private val rolePrefix: String
+    private val roleHierarchy: RoleHierarchy
 
     private val HANDLER_MAPPING_INTROSPECTOR_BEAN_NAME = "mvcHandlerMappingIntrospector"
     private val HANDLER_MAPPING_INTROSPECTOR = "org.springframework.web.servlet.handler.HandlerMappingIntrospector"
@@ -210,7 +213,8 @@ class AuthorizeHttpRequestsDsl : AbstractRequestMatcherDsl {
      * @return the [AuthorizationManager] with the provided authority
      */
     fun hasAuthority(authority: String): AuthorizationManager<RequestAuthorizationContext> {
-        return AuthorityAuthorizationManager.hasAuthority(authority)
+        val manager = AuthorityAuthorizationManager.hasAuthority<RequestAuthorizationContext>(authority)
+        return withRoleHierarchy(manager)
     }
 
     /**
@@ -220,7 +224,8 @@ class AuthorizeHttpRequestsDsl : AbstractRequestMatcherDsl {
      * @return the [AuthorizationManager] with the provided authorities
      */
     fun hasAnyAuthority(vararg authorities: String): AuthorizationManager<RequestAuthorizationContext> {
-        return AuthorityAuthorizationManager.hasAnyAuthority(*authorities)
+        val manager = AuthorityAuthorizationManager.hasAnyAuthority<RequestAuthorizationContext>(*authorities)
+        return withRoleHierarchy(manager)
     }
 
     /**
@@ -230,7 +235,8 @@ class AuthorizeHttpRequestsDsl : AbstractRequestMatcherDsl {
      * @return the [AuthorizationManager] with the provided role
      */
     fun hasRole(role: String): AuthorizationManager<RequestAuthorizationContext> {
-        return AuthorityAuthorizationManager.hasAnyRole(this.rolePrefix, arrayOf(role))
+        val manager = AuthorityAuthorizationManager.hasAnyRole<RequestAuthorizationContext>(this.rolePrefix, arrayOf(role))
+        return withRoleHierarchy(manager)
     }
 
     /**
@@ -240,7 +246,8 @@ class AuthorizeHttpRequestsDsl : AbstractRequestMatcherDsl {
      * @return the [AuthorizationManager] with the provided roles
      */
     fun hasAnyRole(vararg roles: String): AuthorizationManager<RequestAuthorizationContext> {
-        return AuthorityAuthorizationManager.hasAnyRole(this.rolePrefix, arrayOf(*roles))
+        val manager = AuthorityAuthorizationManager.hasAnyRole<RequestAuthorizationContext>(this.rolePrefix, arrayOf(*roles))
+        return withRoleHierarchy(manager)
     }
 
     /**
@@ -296,15 +303,34 @@ class AuthorizeHttpRequestsDsl : AbstractRequestMatcherDsl {
 
     constructor() {
         this.rolePrefix = "ROLE_"
+        this.roleHierarchy = NullRoleHierarchy()
     }
 
     constructor(context: ApplicationContext) {
+        val rolePrefix = resolveRolePrefix(context)
+        this.rolePrefix = rolePrefix
+        val roleHierarchy = resolveRoleHierarchy(context)
+        this.roleHierarchy = roleHierarchy
+    }
+
+    private fun resolveRolePrefix(context: ApplicationContext): String {
         val beanNames = context.getBeanNamesForType(GrantedAuthorityDefaults::class.java)
-        if (beanNames.size > 0) {
-            val grantedAuthorityDefaults = context.getBean(GrantedAuthorityDefaults::class.java);
-            this.rolePrefix = grantedAuthorityDefaults.rolePrefix
-        } else {
-            this.rolePrefix = "ROLE_"
+        if (beanNames.isNotEmpty()) {
+            return context.getBean(GrantedAuthorityDefaults::class.java).rolePrefix
         }
+        return "ROLE_";
+    }
+
+    private fun resolveRoleHierarchy(context: ApplicationContext): RoleHierarchy {
+        val beanNames = context.getBeanNamesForType(RoleHierarchy::class.java)
+        if (beanNames.isNotEmpty()) {
+            return context.getBean(RoleHierarchy::class.java)
+        }
+        return NullRoleHierarchy()
+    }
+
+    private fun withRoleHierarchy(manager: AuthorityAuthorizationManager<RequestAuthorizationContext>): AuthorityAuthorizationManager<RequestAuthorizationContext> {
+        manager.setRoleHierarchy(this.roleHierarchy)
+        return manager
     }
 }

+ 69 - 1
config/src/test/kotlin/org/springframework/security/config/annotation/web/AuthorizeHttpRequestsDslTests.kt

@@ -1,5 +1,5 @@
 /*
- * Copyright 2002-2022 the original author or authors.
+ * Copyright 2002-2024 the original author or authors.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -25,6 +25,8 @@ import org.springframework.beans.factory.annotation.Autowired
 import org.springframework.context.annotation.Bean
 import org.springframework.context.annotation.Configuration
 import org.springframework.http.HttpMethod
+import org.springframework.security.access.hierarchicalroles.RoleHierarchy
+import org.springframework.security.access.hierarchicalroles.RoleHierarchyImpl
 import org.springframework.security.authorization.AuthorizationDecision
 import org.springframework.security.authorization.AuthorizationManager
 import org.springframework.security.config.annotation.web.builders.HttpSecurity
@@ -892,4 +894,70 @@ class AuthorizeHttpRequestsDslTests {
             return GrantedAuthorityDefaults("CUSTOM_")
         }
     }
+
+    @Test
+    fun `hasRole when role hierarchy configured then honor hierarchy`() {
+        this.spring.register(RoleHierarchyConfig::class.java).autowire()
+        this.mockMvc.get("/protected") {
+            with(httpBasic("admin", "password"))
+        }.andExpect {
+            status {
+                isOk()
+            }
+        }
+        this.mockMvc.get("/protected") {
+            with(httpBasic("user", "password"))
+        }.andExpect {
+            status {
+                isOk()
+            }
+        }
+    }
+
+    @Configuration
+    @EnableWebSecurity
+    @EnableWebMvc
+    open class RoleHierarchyConfig {
+
+        @Bean
+        open fun securityFilterChain(http: HttpSecurity): SecurityFilterChain {
+            http {
+                authorizeHttpRequests {
+                    authorize("/protected", hasRole("USER"))
+                }
+                httpBasic { }
+            }
+            return http.build()
+        }
+
+        @Bean
+        open fun roleHierarchy(): RoleHierarchy {
+            return RoleHierarchyImpl.fromHierarchy("ROLE_ADMIN > ROLE_USER")
+        }
+
+        @Bean
+        open fun userDetailsService(): UserDetailsService {
+            val user = User.withDefaultPasswordEncoder()
+                .username("user")
+                .password("password")
+                .roles("USER")
+                .build()
+            val admin = User.withDefaultPasswordEncoder()
+                .username("admin")
+                .password("password")
+                .roles("ADMIN")
+                .build()
+            return InMemoryUserDetailsManager(user, admin)
+        }
+
+        @RestController
+        internal class PathController {
+
+            @RequestMapping("/protected")
+            fun path() {
+            }
+
+        }
+
+    }
 }

+ 2 - 0
docs/modules/ROOT/pages/whats-new.adoc

@@ -5,3 +5,5 @@ Spring Security 6.4 provides a number of new features.
 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.
 
 - https://github.com/spring-projects/spring-security/issues/4186[gh-4186] - Support `RoleHierarchy` in `AclAuthorizationStrategyImpl`
+- https://github.com/spring-projects/spring-security/issues/15136[gh-15136] - Support `RoleHierarchy` Bean in `authorizeHttpRequests` Kotlin DSL
+