Skip to content

Commit 0f29bee

Browse files
adamueleftherias
authored andcommitted
Add authorize() DSL method that accepts HttpMethod
Fixes: gh-8307
1 parent 16a7cbe commit 0f29bee

File tree

3 files changed

+168
-6
lines changed

3 files changed

+168
-6
lines changed

config/src/main/kotlin/org/springframework/security/config/web/servlet/AbstractRequestMatcherDsl.kt

+2
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616

1717
package org.springframework.security.config.web.servlet
1818

19+
import org.springframework.http.HttpMethod
1920
import org.springframework.security.web.util.matcher.AnyRequestMatcher
2021
import org.springframework.security.web.util.matcher.RequestMatcher
2122

@@ -38,6 +39,7 @@ abstract class AbstractRequestMatcherDsl {
3839
protected data class PatternAuthorizationRule(val pattern: String,
3940
val patternType: PatternType,
4041
val servletPath: String? = null,
42+
val httpMethod: HttpMethod? = null,
4143
override val rule: String) : AuthorizationRule(rule)
4244

4345
protected abstract class AuthorizationRule(open val rule: String)

config/src/main/kotlin/org/springframework/security/config/web/servlet/AuthorizeRequestsDsl.kt

+54-6
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616

1717
package org.springframework.security.config.web.servlet
1818

19+
import org.springframework.http.HttpMethod
1920
import org.springframework.security.config.annotation.web.builders.HttpSecurity
2021
import org.springframework.security.config.annotation.web.configurers.ExpressionUrlAuthorizationConfigurer
2122
import org.springframework.security.web.util.matcher.AnyRequestMatcher
@@ -70,6 +71,29 @@ class AuthorizeRequestsDsl : AbstractRequestMatcherDsl() {
7071
rule = access))
7172
}
7273

74+
/**
75+
* Adds a request authorization rule for an endpoint matching the provided
76+
* pattern.
77+
* If Spring MVC is on the classpath, it will use an MVC matcher.
78+
* If Spring MVC is not an the classpath, it will use an ant matcher.
79+
* The MVC will use the same rules that Spring MVC uses for matching.
80+
* For example, often times a mapping of the path "/path" will match on
81+
* "/path", "/path/", "/path.html", etc.
82+
* If the current request will not be processed by Spring MVC, a reasonable default
83+
* using the pattern as an ant pattern will be used.
84+
*
85+
* @param method the HTTP method to match the income requests against.
86+
* @param pattern the pattern to match incoming requests against.
87+
* @param access the SpEL expression to secure the matching request
88+
* (i.e. "hasAuthority('ROLE_USER') and hasAuthority('ROLE_SUPER')")
89+
*/
90+
fun authorize(method: HttpMethod, pattern: String, access: String = "authenticated") {
91+
authorizationRules.add(PatternAuthorizationRule(pattern = pattern,
92+
patternType = PATTERN_TYPE,
93+
httpMethod = method,
94+
rule = access))
95+
}
96+
7397
/**
7498
* Adds a request authorization rule for an endpoint matching the provided
7599
* pattern.
@@ -94,6 +118,32 @@ class AuthorizeRequestsDsl : AbstractRequestMatcherDsl() {
94118
rule = access))
95119
}
96120

121+
/**
122+
* Adds a request authorization rule for an endpoint matching the provided
123+
* pattern.
124+
* If Spring MVC is on the classpath, it will use an MVC matcher.
125+
* If Spring MVC is not an the classpath, it will use an ant matcher.
126+
* The MVC will use the same rules that Spring MVC uses for matching.
127+
* For example, often times a mapping of the path "/path" will match on
128+
* "/path", "/path/", "/path.html", etc.
129+
* If the current request will not be processed by Spring MVC, a reasonable default
130+
* using the pattern as an ant pattern will be used.
131+
*
132+
* @param method the HTTP method to match the income requests against.
133+
* @param pattern the pattern to match incoming requests against.
134+
* @param servletPath the servlet path to match incoming requests against. This
135+
* only applies when using an MVC pattern matcher.
136+
* @param access the SpEL expression to secure the matching request
137+
* (i.e. "hasAuthority('ROLE_USER') and hasAuthority('ROLE_SUPER')")
138+
*/
139+
fun authorize(method: HttpMethod, pattern: String, servletPath: String, access: String = "authenticated") {
140+
authorizationRules.add(PatternAuthorizationRule(pattern = pattern,
141+
patternType = PATTERN_TYPE,
142+
servletPath = servletPath,
143+
httpMethod = method,
144+
rule = access))
145+
}
146+
97147
/**
98148
* Specify that URLs require a particular authority.
99149
*
@@ -150,12 +200,10 @@ class AuthorizeRequestsDsl : AbstractRequestMatcherDsl() {
150200
is MatcherAuthorizationRule -> requests.requestMatchers(rule.matcher).access(rule.rule)
151201
is PatternAuthorizationRule -> {
152202
when (rule.patternType) {
153-
PatternType.ANT -> requests.antMatchers(rule.pattern).access(rule.rule)
154-
PatternType.MVC -> {
155-
val mvcMatchersAuthorizeUrl = requests.mvcMatchers(rule.pattern)
156-
rule.servletPath?.also { mvcMatchersAuthorizeUrl.servletPath(rule.servletPath) }
157-
mvcMatchersAuthorizeUrl.access(rule.rule)
158-
}
203+
PatternType.ANT -> requests.antMatchers(rule.httpMethod, rule.pattern).access(rule.rule)
204+
PatternType.MVC -> requests.mvcMatchers(rule.httpMethod, rule.pattern)
205+
.apply { if(rule.servletPath != null) servletPath(rule.servletPath) }
206+
.access(rule.rule)
159207
}
160208
}
161209
}

config/src/test/kotlin/org/springframework/security/config/web/servlet/AuthorizeRequestsDslTests.kt

+112
Original file line numberDiff line numberDiff line change
@@ -20,17 +20,21 @@ import org.junit.Rule
2020
import org.junit.Test
2121
import org.springframework.beans.factory.annotation.Autowired
2222
import org.springframework.context.annotation.Bean
23+
import org.springframework.http.HttpMethod
2324
import org.springframework.security.config.annotation.web.builders.HttpSecurity
2425
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity
2526
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter
2627
import org.springframework.security.config.test.SpringTestRule
2728
import org.springframework.security.core.userdetails.User
2829
import org.springframework.security.core.userdetails.UserDetailsService
2930
import org.springframework.security.provisioning.InMemoryUserDetailsManager
31+
import org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.csrf
3032
import org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.httpBasic
3133
import org.springframework.security.web.util.matcher.RegexRequestMatcher
3234
import org.springframework.test.web.servlet.MockMvc
3335
import org.springframework.test.web.servlet.get
36+
import org.springframework.test.web.servlet.post
37+
import org.springframework.test.web.servlet.put
3438
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders
3539
import org.springframework.test.web.servlet.result.MockMvcResultMatchers.status
3640
import org.springframework.web.bind.annotation.GetMapping
@@ -72,12 +76,29 @@ class AuthorizeRequestsDslTests {
7276
}
7377
}
7478

79+
@Test
80+
fun `request when allowed by regex matcher with http method then responds based on method`() {
81+
this.spring.register(AuthorizeRequestsByRegexConfig::class.java).autowire()
82+
83+
this.mockMvc.post("/onlyPostPermitted") { with(csrf()) }
84+
.andExpect {
85+
status { isOk }
86+
}
87+
88+
this.mockMvc.get("/onlyPostPermitted")
89+
.andExpect {
90+
status { isForbidden }
91+
}
92+
}
93+
7594
@EnableWebSecurity
7695
open class AuthorizeRequestsByRegexConfig : WebSecurityConfigurerAdapter() {
7796
override fun configure(http: HttpSecurity) {
7897
http {
7998
authorizeRequests {
8099
authorize(RegexRequestMatcher("/path", null), permitAll)
100+
authorize(RegexRequestMatcher("/onlyPostPermitted", "POST"), permitAll)
101+
authorize(RegexRequestMatcher("/onlyPostPermitted", "GET"), denyAll)
81102
authorize(RegexRequestMatcher(".*", null), authenticated)
82103
}
83104
}
@@ -88,6 +109,10 @@ class AuthorizeRequestsDslTests {
88109
@RequestMapping("/path")
89110
fun path() {
90111
}
112+
113+
@RequestMapping("/onlyPostPermitted")
114+
fun onlyPostPermitted() {
115+
}
91116
}
92117
}
93118

@@ -271,4 +296,91 @@ class AuthorizeRequestsDslTests {
271296
}
272297
}
273298
}
299+
300+
@EnableWebSecurity
301+
@EnableWebMvc
302+
open class AuthorizeRequestsByMvcConfigWithHttpMethod : WebSecurityConfigurerAdapter() {
303+
override fun configure(http: HttpSecurity) {
304+
http {
305+
authorizeRequests {
306+
authorize(HttpMethod.GET, "/path", permitAll)
307+
authorize(HttpMethod.PUT, "/path", denyAll)
308+
}
309+
}
310+
}
311+
312+
@RestController
313+
internal class PathController {
314+
@RequestMapping("/path")
315+
fun path() {
316+
}
317+
}
318+
}
319+
320+
@Test
321+
fun `request when secured by mvc with http method then responds based on http method`() {
322+
this.spring.register(AuthorizeRequestsByMvcConfigWithHttpMethod::class.java).autowire()
323+
324+
this.mockMvc.get("/path")
325+
.andExpect {
326+
status { isOk }
327+
}
328+
329+
this.mockMvc.put("/path") { with(csrf()) }
330+
.andExpect {
331+
status { isForbidden }
332+
}
333+
}
334+
335+
@EnableWebSecurity
336+
@EnableWebMvc
337+
open class MvcMatcherServletPathHttpMethodConfig : WebSecurityConfigurerAdapter() {
338+
override fun configure(http: HttpSecurity) {
339+
http {
340+
authorizeRequests {
341+
authorize(HttpMethod.GET, "/path", "/spring", denyAll)
342+
authorize(HttpMethod.PUT, "/path", "/spring", denyAll)
343+
}
344+
}
345+
}
346+
347+
@RestController
348+
internal class PathController {
349+
@RequestMapping("/path")
350+
fun path() {
351+
}
352+
}
353+
}
354+
355+
356+
357+
@Test
358+
fun `request when secured by mvc with servlet path and http method then responds based on path and method`() {
359+
this.spring.register(MvcMatcherServletPathConfig::class.java).autowire()
360+
361+
this.mockMvc.perform(MockMvcRequestBuilders.get("/spring/path")
362+
.with { request ->
363+
request.apply {
364+
servletPath = "/spring"
365+
}
366+
})
367+
.andExpect(status().isForbidden)
368+
369+
this.mockMvc.perform(MockMvcRequestBuilders.put("/spring/path")
370+
.with { request ->
371+
request.apply {
372+
servletPath = "/spring"
373+
csrf()
374+
}
375+
})
376+
.andExpect(status().isForbidden)
377+
378+
this.mockMvc.perform(MockMvcRequestBuilders.get("/other/path")
379+
.with { request ->
380+
request.apply {
381+
servletPath = "/other"
382+
}
383+
})
384+
.andExpect(status().isOk)
385+
}
274386
}

0 commit comments

Comments
 (0)