From a7f9ccb6d6236fb0f181528bd71ce087763656f8 Mon Sep 17 00:00:00 2001 From: Josh Cummings Date: Thu, 6 Jun 2024 14:56:37 -0600 Subject: [PATCH] Use GrantedAuthorityDefaults Bean in Kotlin DSL Closes gh-15171 --- .../web/AuthorizeHttpRequestsDsl.kt | 25 +++++++++-- .../config/annotation/web/HttpSecurityDsl.kt | 3 +- .../web/AuthorizeHttpRequestsDslTests.kt | 45 +++++++++++++++++-- 3 files changed, 65 insertions(+), 8 deletions(-) diff --git a/config/src/main/kotlin/org/springframework/security/config/annotation/web/AuthorizeHttpRequestsDsl.kt b/config/src/main/kotlin/org/springframework/security/config/annotation/web/AuthorizeHttpRequestsDsl.kt index b49ec9fbb2..7869f6c6c5 100644 --- a/config/src/main/kotlin/org/springframework/security/config/annotation/web/AuthorizeHttpRequestsDsl.kt +++ b/config/src/main/kotlin/org/springframework/security/config/annotation/web/AuthorizeHttpRequestsDsl.kt @@ -16,6 +16,7 @@ package org.springframework.security.config.annotation.web +import org.springframework.context.ApplicationContext import org.springframework.http.HttpMethod import org.springframework.security.authorization.AuthenticatedAuthorizationManager import org.springframework.security.authorization.AuthorityAuthorizationManager @@ -23,10 +24,11 @@ import org.springframework.security.authorization.AuthorizationDecision import org.springframework.security.authorization.AuthorizationManager import org.springframework.security.config.annotation.web.builders.HttpSecurity import org.springframework.security.config.annotation.web.configurers.AuthorizeHttpRequestsConfigurer +import org.springframework.security.config.core.GrantedAuthorityDefaults import org.springframework.security.core.Authentication +import org.springframework.security.web.access.IpAddressAuthorizationManager import org.springframework.security.web.access.intercept.AuthorizationFilter import org.springframework.security.web.access.intercept.RequestAuthorizationContext -import org.springframework.security.web.access.IpAddressAuthorizationManager import org.springframework.security.web.servlet.util.matcher.MvcRequestMatcher import org.springframework.security.web.util.matcher.AnyRequestMatcher import org.springframework.security.web.util.matcher.RequestMatcher @@ -41,7 +43,7 @@ import java.util.function.Supplier * @since 5.7 * @property shouldFilterAllDispatcherTypes whether the [AuthorizationFilter] should filter all dispatcher types */ -class AuthorizeHttpRequestsDsl : AbstractRequestMatcherDsl() { +class AuthorizeHttpRequestsDsl : AbstractRequestMatcherDsl { @Deprecated(""" Add authorization rules to DispatcherType directly. @@ -62,6 +64,7 @@ class AuthorizeHttpRequestsDsl : AbstractRequestMatcherDsl() { var shouldFilterAllDispatcherTypes: Boolean? = null private val authorizationRules = mutableListOf() + private val rolePrefix: String private val HANDLER_MAPPING_INTROSPECTOR_BEAN_NAME = "mvcHandlerMappingIntrospector" private val HANDLER_MAPPING_INTROSPECTOR = "org.springframework.web.servlet.handler.HandlerMappingIntrospector" @@ -227,7 +230,7 @@ class AuthorizeHttpRequestsDsl : AbstractRequestMatcherDsl() { * @return the [AuthorizationManager] with the provided role */ fun hasRole(role: String): AuthorizationManager { - return AuthorityAuthorizationManager.hasRole(role) + return AuthorityAuthorizationManager.hasAnyRole(this.rolePrefix, arrayOf(role)) } /** @@ -237,7 +240,7 @@ class AuthorizeHttpRequestsDsl : AbstractRequestMatcherDsl() { * @return the [AuthorizationManager] with the provided roles */ fun hasAnyRole(vararg roles: String): AuthorizationManager { - return AuthorityAuthorizationManager.hasAnyRole(*roles) + return AuthorityAuthorizationManager.hasAnyRole(this.rolePrefix, arrayOf(*roles)) } /** @@ -290,4 +293,18 @@ class AuthorizeHttpRequestsDsl : AbstractRequestMatcherDsl() { } } } + + constructor() { + this.rolePrefix = "ROLE_" + } + + constructor(context: ApplicationContext) { + 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_" + } + } } diff --git a/config/src/main/kotlin/org/springframework/security/config/annotation/web/HttpSecurityDsl.kt b/config/src/main/kotlin/org/springframework/security/config/annotation/web/HttpSecurityDsl.kt index d4977415d7..6e7636c4a9 100644 --- a/config/src/main/kotlin/org/springframework/security/config/annotation/web/HttpSecurityDsl.kt +++ b/config/src/main/kotlin/org/springframework/security/config/annotation/web/HttpSecurityDsl.kt @@ -77,6 +77,7 @@ class HttpSecurityDsl(private val http: HttpSecurity, private val init: HttpSecu private val HANDLER_MAPPING_INTROSPECTOR = "org.springframework.web.servlet.handler.HandlerMappingIntrospector" var authenticationManager: AuthenticationManager? = null + val context: ApplicationContext = http.getSharedObject(ApplicationContext::class.java) /** * Applies a [SecurityConfigurerAdapter] to this [HttpSecurity] @@ -298,7 +299,7 @@ class HttpSecurityDsl(private val http: HttpSecurity, private val init: HttpSecu * @since 5.7 */ fun authorizeHttpRequests(authorizeHttpRequestsConfiguration: AuthorizeHttpRequestsDsl.() -> Unit) { - val authorizeHttpRequestsCustomizer = AuthorizeHttpRequestsDsl().apply(authorizeHttpRequestsConfiguration).get() + val authorizeHttpRequestsCustomizer = AuthorizeHttpRequestsDsl(this.context).apply(authorizeHttpRequestsConfiguration).get() this.http.authorizeHttpRequests(authorizeHttpRequestsCustomizer) } diff --git a/config/src/test/kotlin/org/springframework/security/config/annotation/web/AuthorizeHttpRequestsDslTests.kt b/config/src/test/kotlin/org/springframework/security/config/annotation/web/AuthorizeHttpRequestsDslTests.kt index 0af6bbb483..d1544b67cd 100644 --- a/config/src/test/kotlin/org/springframework/security/config/annotation/web/AuthorizeHttpRequestsDslTests.kt +++ b/config/src/test/kotlin/org/springframework/security/config/annotation/web/AuthorizeHttpRequestsDslTests.kt @@ -16,7 +16,8 @@ package org.springframework.security.config.annotation.web -import org.assertj.core.api.Assertions.* +import jakarta.servlet.DispatcherType +import org.assertj.core.api.Assertions.assertThatThrownBy import org.junit.jupiter.api.Test import org.junit.jupiter.api.extension.ExtendWith import org.springframework.beans.factory.UnsatisfiedDependencyException @@ -28,6 +29,7 @@ import org.springframework.security.authorization.AuthorizationDecision import org.springframework.security.authorization.AuthorizationManager import org.springframework.security.config.annotation.web.builders.HttpSecurity import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity +import org.springframework.security.config.core.GrantedAuthorityDefaults import org.springframework.security.config.test.SpringTestContext import org.springframework.security.config.test.SpringTestContextExtension import org.springframework.security.core.Authentication @@ -55,7 +57,6 @@ import org.springframework.web.servlet.config.annotation.PathMatchConfigurer import org.springframework.web.servlet.config.annotation.WebMvcConfigurer import org.springframework.web.util.WebUtils import java.util.function.Supplier -import jakarta.servlet.DispatcherType /** * Tests for [AuthorizeHttpRequestsDsl] @@ -835,7 +836,6 @@ class AuthorizeHttpRequestsDslTests { @EnableWebSecurity @EnableWebMvc open class HasIpAddressConfig { - @Bean open fun securityFilterChain(http: HttpSecurity): SecurityFilterChain { http { @@ -853,4 +853,43 @@ class AuthorizeHttpRequestsDslTests { } } } + + fun `hasRole when prefixed by configured role prefix should fail to configure`() { + assertThatThrownBy { this.spring.register(RoleValidationConfig::class.java).autowire() } + .isInstanceOf(UnsatisfiedDependencyException::class.java) + .hasRootCauseInstanceOf(IllegalArgumentException::class.java) + .hasMessageContaining( + "ROLE_JUNIPER should not start with ROLE_ since ROLE_ is automatically prepended when using hasAnyRole. Consider using hasAnyAuthority instead." + ) + assertThatThrownBy { this.spring.register(RoleValidationConfig::class.java, GrantedAuthorityDefaultsConfig::class.java).autowire() } + .isInstanceOf(UnsatisfiedDependencyException::class.java) + .hasRootCauseInstanceOf(IllegalArgumentException::class.java) + .hasMessageContaining( + "CUSTOM_JUNIPER should not start with CUSTOM_ since CUSTOM_ is automatically prepended when using hasAnyRole. Consider using hasAnyAuthority instead." + ) + } + + @Configuration + @EnableWebSecurity + @EnableWebMvc + open class RoleValidationConfig { + @Bean + open fun securityFilterChain(http: HttpSecurity): SecurityFilterChain { + http { + authorizeHttpRequests { + authorize("/role", hasAnyRole("ROLE_JUNIPER")) + authorize("/custom", hasRole("CUSTOM_JUNIPER")) + } + } + return http.build() + } + } + + @Configuration + open class GrantedAuthorityDefaultsConfig { + @Bean + open fun grantedAuthorityDefaults(): GrantedAuthorityDefaults { + return GrantedAuthorityDefaults("CUSTOM_") + } + } }