Add authorize() DSL method that accepts HttpMethod
Fixes: gh-8307
This commit is contained in:
parent
16a7cbee4b
commit
0f29bee1b0
|
@ -16,6 +16,7 @@
|
||||||
|
|
||||||
package org.springframework.security.config.web.servlet
|
package org.springframework.security.config.web.servlet
|
||||||
|
|
||||||
|
import org.springframework.http.HttpMethod
|
||||||
import org.springframework.security.web.util.matcher.AnyRequestMatcher
|
import org.springframework.security.web.util.matcher.AnyRequestMatcher
|
||||||
import org.springframework.security.web.util.matcher.RequestMatcher
|
import org.springframework.security.web.util.matcher.RequestMatcher
|
||||||
|
|
||||||
|
@ -38,6 +39,7 @@ abstract class AbstractRequestMatcherDsl {
|
||||||
protected data class PatternAuthorizationRule(val pattern: String,
|
protected data class PatternAuthorizationRule(val pattern: String,
|
||||||
val patternType: PatternType,
|
val patternType: PatternType,
|
||||||
val servletPath: String? = null,
|
val servletPath: String? = null,
|
||||||
|
val httpMethod: HttpMethod? = null,
|
||||||
override val rule: String) : AuthorizationRule(rule)
|
override val rule: String) : AuthorizationRule(rule)
|
||||||
|
|
||||||
protected abstract class AuthorizationRule(open val rule: String)
|
protected abstract class AuthorizationRule(open val rule: String)
|
||||||
|
|
|
@ -16,6 +16,7 @@
|
||||||
|
|
||||||
package org.springframework.security.config.web.servlet
|
package org.springframework.security.config.web.servlet
|
||||||
|
|
||||||
|
import org.springframework.http.HttpMethod
|
||||||
import org.springframework.security.config.annotation.web.builders.HttpSecurity
|
import org.springframework.security.config.annotation.web.builders.HttpSecurity
|
||||||
import org.springframework.security.config.annotation.web.configurers.ExpressionUrlAuthorizationConfigurer
|
import org.springframework.security.config.annotation.web.configurers.ExpressionUrlAuthorizationConfigurer
|
||||||
import org.springframework.security.web.util.matcher.AnyRequestMatcher
|
import org.springframework.security.web.util.matcher.AnyRequestMatcher
|
||||||
|
@ -70,6 +71,29 @@ class AuthorizeRequestsDsl : AbstractRequestMatcherDsl() {
|
||||||
rule = access))
|
rule = access))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds a request authorization rule for an endpoint matching the provided
|
||||||
|
* pattern.
|
||||||
|
* If Spring MVC is on the classpath, it will use an MVC matcher.
|
||||||
|
* If Spring MVC is not an the classpath, it will use an ant matcher.
|
||||||
|
* The MVC will use the same rules that Spring MVC uses for matching.
|
||||||
|
* For example, often times a mapping of the path "/path" will match on
|
||||||
|
* "/path", "/path/", "/path.html", etc.
|
||||||
|
* If the current request will not be processed by Spring MVC, a reasonable default
|
||||||
|
* using the pattern as an ant pattern will be used.
|
||||||
|
*
|
||||||
|
* @param method the HTTP method to match the income requests against.
|
||||||
|
* @param pattern the pattern to match incoming requests against.
|
||||||
|
* @param access the SpEL expression to secure the matching request
|
||||||
|
* (i.e. "hasAuthority('ROLE_USER') and hasAuthority('ROLE_SUPER')")
|
||||||
|
*/
|
||||||
|
fun authorize(method: HttpMethod, pattern: String, access: String = "authenticated") {
|
||||||
|
authorizationRules.add(PatternAuthorizationRule(pattern = pattern,
|
||||||
|
patternType = PATTERN_TYPE,
|
||||||
|
httpMethod = method,
|
||||||
|
rule = access))
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Adds a request authorization rule for an endpoint matching the provided
|
* Adds a request authorization rule for an endpoint matching the provided
|
||||||
* pattern.
|
* pattern.
|
||||||
|
@ -94,6 +118,32 @@ class AuthorizeRequestsDsl : AbstractRequestMatcherDsl() {
|
||||||
rule = access))
|
rule = access))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds a request authorization rule for an endpoint matching the provided
|
||||||
|
* pattern.
|
||||||
|
* If Spring MVC is on the classpath, it will use an MVC matcher.
|
||||||
|
* If Spring MVC is not an the classpath, it will use an ant matcher.
|
||||||
|
* The MVC will use the same rules that Spring MVC uses for matching.
|
||||||
|
* For example, often times a mapping of the path "/path" will match on
|
||||||
|
* "/path", "/path/", "/path.html", etc.
|
||||||
|
* If the current request will not be processed by Spring MVC, a reasonable default
|
||||||
|
* using the pattern as an ant pattern will be used.
|
||||||
|
*
|
||||||
|
* @param method the HTTP method to match the income requests against.
|
||||||
|
* @param pattern the pattern to match incoming requests against.
|
||||||
|
* @param servletPath the servlet path to match incoming requests against. This
|
||||||
|
* only applies when using an MVC pattern matcher.
|
||||||
|
* @param access the SpEL expression to secure the matching request
|
||||||
|
* (i.e. "hasAuthority('ROLE_USER') and hasAuthority('ROLE_SUPER')")
|
||||||
|
*/
|
||||||
|
fun authorize(method: HttpMethod, pattern: String, servletPath: String, access: String = "authenticated") {
|
||||||
|
authorizationRules.add(PatternAuthorizationRule(pattern = pattern,
|
||||||
|
patternType = PATTERN_TYPE,
|
||||||
|
servletPath = servletPath,
|
||||||
|
httpMethod = method,
|
||||||
|
rule = access))
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Specify that URLs require a particular authority.
|
* Specify that URLs require a particular authority.
|
||||||
*
|
*
|
||||||
|
@ -150,12 +200,10 @@ class AuthorizeRequestsDsl : AbstractRequestMatcherDsl() {
|
||||||
is MatcherAuthorizationRule -> requests.requestMatchers(rule.matcher).access(rule.rule)
|
is MatcherAuthorizationRule -> requests.requestMatchers(rule.matcher).access(rule.rule)
|
||||||
is PatternAuthorizationRule -> {
|
is PatternAuthorizationRule -> {
|
||||||
when (rule.patternType) {
|
when (rule.patternType) {
|
||||||
PatternType.ANT -> requests.antMatchers(rule.pattern).access(rule.rule)
|
PatternType.ANT -> requests.antMatchers(rule.httpMethod, rule.pattern).access(rule.rule)
|
||||||
PatternType.MVC -> {
|
PatternType.MVC -> requests.mvcMatchers(rule.httpMethod, rule.pattern)
|
||||||
val mvcMatchersAuthorizeUrl = requests.mvcMatchers(rule.pattern)
|
.apply { if(rule.servletPath != null) servletPath(rule.servletPath) }
|
||||||
rule.servletPath?.also { mvcMatchersAuthorizeUrl.servletPath(rule.servletPath) }
|
.access(rule.rule)
|
||||||
mvcMatchersAuthorizeUrl.access(rule.rule)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -20,6 +20,7 @@ import org.junit.Rule
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
import org.springframework.beans.factory.annotation.Autowired
|
import org.springframework.beans.factory.annotation.Autowired
|
||||||
import org.springframework.context.annotation.Bean
|
import org.springframework.context.annotation.Bean
|
||||||
|
import org.springframework.http.HttpMethod
|
||||||
import org.springframework.security.config.annotation.web.builders.HttpSecurity
|
import org.springframework.security.config.annotation.web.builders.HttpSecurity
|
||||||
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity
|
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity
|
||||||
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter
|
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter
|
||||||
|
@ -27,10 +28,13 @@ import org.springframework.security.config.test.SpringTestRule
|
||||||
import org.springframework.security.core.userdetails.User
|
import org.springframework.security.core.userdetails.User
|
||||||
import org.springframework.security.core.userdetails.UserDetailsService
|
import org.springframework.security.core.userdetails.UserDetailsService
|
||||||
import org.springframework.security.provisioning.InMemoryUserDetailsManager
|
import org.springframework.security.provisioning.InMemoryUserDetailsManager
|
||||||
|
import org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.csrf
|
||||||
import org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.httpBasic
|
import org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.httpBasic
|
||||||
import org.springframework.security.web.util.matcher.RegexRequestMatcher
|
import org.springframework.security.web.util.matcher.RegexRequestMatcher
|
||||||
import org.springframework.test.web.servlet.MockMvc
|
import org.springframework.test.web.servlet.MockMvc
|
||||||
import org.springframework.test.web.servlet.get
|
import org.springframework.test.web.servlet.get
|
||||||
|
import org.springframework.test.web.servlet.post
|
||||||
|
import org.springframework.test.web.servlet.put
|
||||||
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders
|
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders
|
||||||
import org.springframework.test.web.servlet.result.MockMvcResultMatchers.status
|
import org.springframework.test.web.servlet.result.MockMvcResultMatchers.status
|
||||||
import org.springframework.web.bind.annotation.GetMapping
|
import org.springframework.web.bind.annotation.GetMapping
|
||||||
|
@ -72,12 +76,29 @@ class AuthorizeRequestsDslTests {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `request when allowed by regex matcher with http method then responds based on method`() {
|
||||||
|
this.spring.register(AuthorizeRequestsByRegexConfig::class.java).autowire()
|
||||||
|
|
||||||
|
this.mockMvc.post("/onlyPostPermitted") { with(csrf()) }
|
||||||
|
.andExpect {
|
||||||
|
status { isOk }
|
||||||
|
}
|
||||||
|
|
||||||
|
this.mockMvc.get("/onlyPostPermitted")
|
||||||
|
.andExpect {
|
||||||
|
status { isForbidden }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@EnableWebSecurity
|
@EnableWebSecurity
|
||||||
open class AuthorizeRequestsByRegexConfig : WebSecurityConfigurerAdapter() {
|
open class AuthorizeRequestsByRegexConfig : WebSecurityConfigurerAdapter() {
|
||||||
override fun configure(http: HttpSecurity) {
|
override fun configure(http: HttpSecurity) {
|
||||||
http {
|
http {
|
||||||
authorizeRequests {
|
authorizeRequests {
|
||||||
authorize(RegexRequestMatcher("/path", null), permitAll)
|
authorize(RegexRequestMatcher("/path", null), permitAll)
|
||||||
|
authorize(RegexRequestMatcher("/onlyPostPermitted", "POST"), permitAll)
|
||||||
|
authorize(RegexRequestMatcher("/onlyPostPermitted", "GET"), denyAll)
|
||||||
authorize(RegexRequestMatcher(".*", null), authenticated)
|
authorize(RegexRequestMatcher(".*", null), authenticated)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -88,6 +109,10 @@ class AuthorizeRequestsDslTests {
|
||||||
@RequestMapping("/path")
|
@RequestMapping("/path")
|
||||||
fun path() {
|
fun path() {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@RequestMapping("/onlyPostPermitted")
|
||||||
|
fun onlyPostPermitted() {
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -271,4 +296,91 @@ class AuthorizeRequestsDslTests {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@EnableWebSecurity
|
||||||
|
@EnableWebMvc
|
||||||
|
open class AuthorizeRequestsByMvcConfigWithHttpMethod : WebSecurityConfigurerAdapter() {
|
||||||
|
override fun configure(http: HttpSecurity) {
|
||||||
|
http {
|
||||||
|
authorizeRequests {
|
||||||
|
authorize(HttpMethod.GET, "/path", permitAll)
|
||||||
|
authorize(HttpMethod.PUT, "/path", denyAll)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@RestController
|
||||||
|
internal class PathController {
|
||||||
|
@RequestMapping("/path")
|
||||||
|
fun path() {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `request when secured by mvc with http method then responds based on http method`() {
|
||||||
|
this.spring.register(AuthorizeRequestsByMvcConfigWithHttpMethod::class.java).autowire()
|
||||||
|
|
||||||
|
this.mockMvc.get("/path")
|
||||||
|
.andExpect {
|
||||||
|
status { isOk }
|
||||||
|
}
|
||||||
|
|
||||||
|
this.mockMvc.put("/path") { with(csrf()) }
|
||||||
|
.andExpect {
|
||||||
|
status { isForbidden }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@EnableWebSecurity
|
||||||
|
@EnableWebMvc
|
||||||
|
open class MvcMatcherServletPathHttpMethodConfig : WebSecurityConfigurerAdapter() {
|
||||||
|
override fun configure(http: HttpSecurity) {
|
||||||
|
http {
|
||||||
|
authorizeRequests {
|
||||||
|
authorize(HttpMethod.GET, "/path", "/spring", denyAll)
|
||||||
|
authorize(HttpMethod.PUT, "/path", "/spring", denyAll)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@RestController
|
||||||
|
internal class PathController {
|
||||||
|
@RequestMapping("/path")
|
||||||
|
fun path() {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `request when secured by mvc with servlet path and http method then responds based on path and method`() {
|
||||||
|
this.spring.register(MvcMatcherServletPathConfig::class.java).autowire()
|
||||||
|
|
||||||
|
this.mockMvc.perform(MockMvcRequestBuilders.get("/spring/path")
|
||||||
|
.with { request ->
|
||||||
|
request.apply {
|
||||||
|
servletPath = "/spring"
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.andExpect(status().isForbidden)
|
||||||
|
|
||||||
|
this.mockMvc.perform(MockMvcRequestBuilders.put("/spring/path")
|
||||||
|
.with { request ->
|
||||||
|
request.apply {
|
||||||
|
servletPath = "/spring"
|
||||||
|
csrf()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.andExpect(status().isForbidden)
|
||||||
|
|
||||||
|
this.mockMvc.perform(MockMvcRequestBuilders.get("/other/path")
|
||||||
|
.with { request ->
|
||||||
|
request.apply {
|
||||||
|
servletPath = "/other"
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.andExpect(status().isOk)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue