diff --git a/config/src/main/kotlin/org/springframework/security/config/web/servlet/AuthorizeRequestsDsl.kt b/config/src/main/kotlin/org/springframework/security/config/web/servlet/AuthorizeRequestsDsl.kt index eb0887d7bf..663287a118 100644 --- a/config/src/main/kotlin/org/springframework/security/config/web/servlet/AuthorizeRequestsDsl.kt +++ b/config/src/main/kotlin/org/springframework/security/config/web/servlet/AuthorizeRequestsDsl.kt @@ -153,6 +153,18 @@ class AuthorizeRequestsDsl : AbstractRequestMatcherDsl() { */ fun hasAuthority(authority: String) = "hasAuthority('$authority')" + /** + * Specify that URLs requires any of a number authorities. + * + * @param authorities the authorities to require (i.e. ROLE_USER, ROLE_ADMIN, etc). + * @return the SpEL expression "hasAnyAuthority" with the given authorities as a + * parameter + */ + fun hasAnyAuthority(vararg authorities: String): String { + val anyAuthorities = authorities.joinToString("','") + return "hasAnyAuthority('$anyAuthorities')" + } + /** * Specify that URLs require a particular role. * @@ -162,6 +174,18 @@ class AuthorizeRequestsDsl : AbstractRequestMatcherDsl() { */ fun hasRole(role: String) = "hasRole('$role')" + /** + * Specify that URLs requires any of a number roles. + * + * @param roles the roles to require (i.e. USER, ADMIN, etc). + * @return the SpEL expression "hasAnyRole" with the given roles as a + * parameter + */ + fun hasAnyRole(vararg roles: String): String { + val anyRoles = roles.joinToString("','") + return "hasAnyRole('$anyRoles')" + } + /** * Specify that URLs are allowed by anyone. */ diff --git a/config/src/test/kotlin/org/springframework/security/config/web/servlet/AuthorizeRequestsDslTests.kt b/config/src/test/kotlin/org/springframework/security/config/web/servlet/AuthorizeRequestsDslTests.kt index 72c5ff57be..d0f27b1350 100644 --- a/config/src/test/kotlin/org/springframework/security/config/web/servlet/AuthorizeRequestsDslTests.kt +++ b/config/src/test/kotlin/org/springframework/security/config/web/servlet/AuthorizeRequestsDslTests.kt @@ -267,6 +267,142 @@ class AuthorizeRequestsDslTests { } } + @Test + fun `request when user has some allowed roles then responds with OK`() { + this.spring.register(HasAnyRoleConfig::class.java).autowire() + + this.mockMvc.get("/") { + with(httpBasic("user", "password")) + }.andExpect { + status { isOk } + } + + this.mockMvc.get("/") { + with(httpBasic("admin", "password")) + }.andExpect { + status { isOk } + } + } + + @Test + fun `request when user does not have any allowed roles then responds with forbidden`() { + this.spring.register(HasAnyRoleConfig::class.java).autowire() + + this.mockMvc.get("/") { + with(httpBasic("other", "password")) + }.andExpect { + status { isForbidden } + } + } + + @EnableWebSecurity + @EnableWebMvc + open class HasAnyRoleConfig : WebSecurityConfigurerAdapter() { + override fun configure(http: HttpSecurity) { + http { + authorizeRequests { + authorize("/**", hasAnyRole("ADMIN", "USER")) + } + httpBasic { } + } + } + + @RestController + internal class PathController { + @GetMapping("/") + fun index() { + } + } + + @Bean + override fun userDetailsService(): UserDetailsService { + val userDetails = User.withDefaultPasswordEncoder() + .username("user") + .password("password") + .roles("USER") + .build() + val admin1Details = User.withDefaultPasswordEncoder() + .username("admin") + .password("password") + .roles("ADMIN") + .build() + val admin2Details = User.withDefaultPasswordEncoder() + .username("other") + .password("password") + .roles("OTHER") + .build() + return InMemoryUserDetailsManager(userDetails, admin1Details, admin2Details) + } + } + + @Test + fun `request when user has some allowed authorities then responds with OK`() { + this.spring.register(HasAnyAuthorityConfig::class.java).autowire() + + this.mockMvc.get("/") { + with(httpBasic("user", "password")) + }.andExpect { + status { isOk } + } + + this.mockMvc.get("/") { + with(httpBasic("admin", "password")) + }.andExpect { + status { isOk } + } + } + + @Test + fun `request when user does not have any allowed authorities then responds with forbidden`() { + this.spring.register(HasAnyAuthorityConfig::class.java).autowire() + + this.mockMvc.get("/") { + with(httpBasic("other", "password")) + }.andExpect { + status { isForbidden } + } + } + + @EnableWebSecurity + @EnableWebMvc + open class HasAnyAuthorityConfig : WebSecurityConfigurerAdapter() { + override fun configure(http: HttpSecurity) { + http { + authorizeRequests { + authorize("/**", hasAnyAuthority("ROLE_ADMIN", "ROLE_USER")) + } + httpBasic { } + } + } + + @RestController + internal class PathController { + @GetMapping("/") + fun index() { + } + } + + @Bean + override fun userDetailsService(): UserDetailsService { + val userDetails = User.withDefaultPasswordEncoder() + .username("user") + .password("password") + .authorities("ROLE_USER") + .build() + val admin1Details = User.withDefaultPasswordEncoder() + .username("admin") + .password("password") + .authorities("ROLE_ADMIN") + .build() + val admin2Details = User.withDefaultPasswordEncoder() + .username("other") + .password("password") + .authorities("ROLE_OTHER") + .build() + return InMemoryUserDetailsManager(userDetails, admin1Details, admin2Details) + } + } + @Test fun `request when secured by mvc with servlet path then responds based on servlet path`() { this.spring.register(MvcMatcherServletPathConfig::class.java).autowire()