Skip to content

Commit 39e09e4

Browse files
committed
Idiomatic Kotlin DSL for server HTTP security
Issue: gh-5558
1 parent 6017510 commit 39e09e4

File tree

50 files changed

+6532
-0
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

50 files changed

+6532
-0
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,135 @@
1+
/*
2+
* Copyright 2002-2020 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.security.config.web.server
18+
19+
import org.springframework.security.authorization.AuthenticatedReactiveAuthorizationManager
20+
import org.springframework.security.authorization.AuthorityReactiveAuthorizationManager
21+
import org.springframework.security.authorization.AuthorizationDecision
22+
import org.springframework.security.authorization.ReactiveAuthorizationManager
23+
import org.springframework.security.core.Authentication
24+
import org.springframework.security.web.server.authorization.AuthorizationContext
25+
import org.springframework.security.web.server.util.matcher.ServerWebExchangeMatcher
26+
import org.springframework.security.web.server.util.matcher.ServerWebExchangeMatchers
27+
import org.springframework.security.web.util.matcher.RequestMatcher
28+
import reactor.core.publisher.Mono
29+
30+
/**
31+
* A Kotlin DSL to configure [ServerHttpSecurity] exchange authorization using idiomatic
32+
* Kotlin code.
33+
*
34+
* @author Eleftheria Stein
35+
* @since 5.4
36+
*/
37+
class AuthorizeExchangeDsl {
38+
private val authorizationRules = mutableListOf<ExchangeAuthorizationRule>()
39+
40+
/**
41+
* Adds an exchange authorization rule for an endpoint matching the provided
42+
* matcher.
43+
*
44+
* @param matcher the [RequestMatcher] to match incoming requests against
45+
* @param access the [ReactiveAuthorizationManager] which determines the access
46+
* to the specific matcher.
47+
* Some predefined shortcuts have already been created, such as
48+
* [hasAnyAuthority], [hasAnyRole], [permitAll], [authenticated] and more
49+
*/
50+
fun authorize(matcher: ServerWebExchangeMatcher = ServerWebExchangeMatchers.anyExchange(),
51+
access: ReactiveAuthorizationManager<AuthorizationContext> = authenticated) {
52+
authorizationRules.add(MatcherExchangeAuthorizationRule(matcher, access))
53+
}
54+
55+
/**
56+
* Adds an exchange authorization rule for an endpoint matching the provided
57+
* ant pattern.
58+
*
59+
* @param antPattern the ant ant pattern to match incoming requests against.
60+
* @param access the [ReactiveAuthorizationManager] which determines the access
61+
* to the specific matcher.
62+
* Some predefined shortcuts have already been created, such as
63+
* [hasAnyAuthority], [hasAnyRole], [permitAll], [authenticated] and more
64+
*/
65+
fun authorize(antPattern: String, access: ReactiveAuthorizationManager<AuthorizationContext> = authenticated) {
66+
authorizationRules.add(PatternExchangeAuthorizationRule(antPattern, access))
67+
}
68+
69+
/**
70+
* Matches any exchange.
71+
*/
72+
val anyExchange: ServerWebExchangeMatcher = ServerWebExchangeMatchers.anyExchange()
73+
74+
/**
75+
* Allow access for anyone.
76+
*/
77+
val permitAll: ReactiveAuthorizationManager<AuthorizationContext> =
78+
ReactiveAuthorizationManager { _: Mono<Authentication>, _: AuthorizationContext -> Mono.just(AuthorizationDecision(true)) }
79+
80+
/**
81+
* Deny access for everyone.
82+
*/
83+
val denyAll: ReactiveAuthorizationManager<AuthorizationContext> =
84+
ReactiveAuthorizationManager { _: Mono<Authentication>, _: AuthorizationContext -> Mono.just(AuthorizationDecision(false)) }
85+
86+
/**
87+
* Require a specific role. This is a shortcut for [hasAuthority].
88+
*/
89+
fun hasRole(role: String): ReactiveAuthorizationManager<AuthorizationContext> =
90+
AuthorityReactiveAuthorizationManager.hasRole<AuthorizationContext>(role)
91+
92+
/**
93+
* Require any specific role. This is a shortcut for [hasAnyAuthority].
94+
*/
95+
fun hasAnyRole(vararg roles: String): ReactiveAuthorizationManager<AuthorizationContext> =
96+
AuthorityReactiveAuthorizationManager.hasAnyRole<AuthorizationContext>(*roles)
97+
98+
/**
99+
* Require a specific authority.
100+
*/
101+
fun hasAuthority(authority: String): ReactiveAuthorizationManager<AuthorizationContext> =
102+
AuthorityReactiveAuthorizationManager.hasAuthority<AuthorizationContext>(authority)
103+
104+
/**
105+
* Require any authority.
106+
*/
107+
fun hasAnyAuthority(vararg authorities: String): ReactiveAuthorizationManager<AuthorizationContext> =
108+
AuthorityReactiveAuthorizationManager.hasAnyAuthority<AuthorizationContext>(*authorities)
109+
110+
/**
111+
* Require an authenticated user.
112+
*/
113+
val authenticated: ReactiveAuthorizationManager<AuthorizationContext> =
114+
AuthenticatedReactiveAuthorizationManager.authenticated<AuthorizationContext>()
115+
116+
internal fun get(): (ServerHttpSecurity.AuthorizeExchangeSpec) -> Unit {
117+
return { requests ->
118+
authorizationRules.forEach { rule ->
119+
when (rule) {
120+
is MatcherExchangeAuthorizationRule -> requests.matchers(rule.matcher).access(rule.rule)
121+
is PatternExchangeAuthorizationRule -> requests.pathMatchers(rule.pattern).access(rule.rule)
122+
}
123+
}
124+
}
125+
}
126+
127+
private data class MatcherExchangeAuthorizationRule(val matcher: ServerWebExchangeMatcher,
128+
override val rule: ReactiveAuthorizationManager<AuthorizationContext>) : ExchangeAuthorizationRule(rule)
129+
130+
private data class PatternExchangeAuthorizationRule(val pattern: String,
131+
override val rule: ReactiveAuthorizationManager<AuthorizationContext>) : ExchangeAuthorizationRule(rule)
132+
133+
private abstract class ExchangeAuthorizationRule(open val rule: ReactiveAuthorizationManager<AuthorizationContext>)
134+
}
135+
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
/*
2+
* Copyright 2002-2020 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.security.config.web.server
18+
19+
import org.springframework.security.core.Authentication
20+
import org.springframework.security.core.GrantedAuthority
21+
import org.springframework.security.web.server.authentication.AnonymousAuthenticationWebFilter
22+
23+
/**
24+
* A Kotlin DSL to configure [ServerHttpSecurity] anonymous authentication using idiomatic
25+
* Kotlin code.
26+
*
27+
* @author Eleftheria Stein
28+
* @since 5.4
29+
* @property key the key to identify tokens created for anonymous authentication
30+
* @property principal the principal for [Authentication] objects of anonymous users
31+
* @property authorities the [Authentication.getAuthorities] for anonymous users
32+
* @property authenticationFilter the [AnonymousAuthenticationWebFilter] used to populate
33+
* an anonymous user.
34+
*/
35+
class ServerAnonymousDsl {
36+
var key: String? = null
37+
var principal: Any? = null
38+
var authorities: List<GrantedAuthority>? = null
39+
var authenticationFilter: AnonymousAuthenticationWebFilter? = null
40+
41+
private var disabled = false
42+
43+
/**
44+
* Disables anonymous authentication
45+
*/
46+
fun disable() {
47+
disabled = true
48+
}
49+
50+
internal fun get(): (ServerHttpSecurity.AnonymousSpec) -> Unit {
51+
return { anonymous ->
52+
key?.also { anonymous.key(key) }
53+
principal?.also { anonymous.principal(principal) }
54+
authorities?.also { anonymous.authorities(authorities) }
55+
authenticationFilter?.also { anonymous.authenticationFilter(authenticationFilter) }
56+
if (disabled) {
57+
anonymous.disable()
58+
}
59+
}
60+
}
61+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
/*
2+
* Copyright 2002-2020 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.security.config.web.server
18+
19+
import org.springframework.web.cors.reactive.CorsConfigurationSource
20+
21+
/**
22+
* A Kotlin DSL to configure [ServerHttpSecurity] CORS headers using idiomatic
23+
* Kotlin code.
24+
*
25+
* @author Eleftheria Stein
26+
* @since 5.4
27+
* @property configurationSource the [CorsConfigurationSource] to use.
28+
*/
29+
class ServerCorsDsl {
30+
var configurationSource: CorsConfigurationSource? = null
31+
32+
private var disabled = false
33+
34+
/**
35+
* Disables CORS support within Spring Security.
36+
*/
37+
fun disable() {
38+
disabled = true
39+
}
40+
41+
internal fun get(): (ServerHttpSecurity.CorsSpec) -> Unit {
42+
return { cors ->
43+
configurationSource?.also { cors.configurationSource(configurationSource) }
44+
if (disabled) {
45+
cors.disable()
46+
}
47+
}
48+
}
49+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
/*
2+
* Copyright 2002-2020 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.security.config.web.server
18+
19+
import org.springframework.security.config.web.server.ServerHttpSecurity
20+
import org.springframework.security.web.server.authorization.ServerAccessDeniedHandler
21+
import org.springframework.security.web.server.csrf.ServerCsrfTokenRepository
22+
import org.springframework.security.web.server.util.matcher.ServerWebExchangeMatcher
23+
24+
/**
25+
* A Kotlin DSL to configure [ServerHttpSecurity] CSRF protection using idiomatic
26+
* Kotlin code.
27+
*
28+
* @author Eleftheria Stein
29+
* @since 5.4
30+
* @property accessDeniedHandler the [ServerAccessDeniedHandler] used when a CSRF token is invalid.
31+
* @property csrfTokenRepository the [ServerCsrfTokenRepository] used to persist the CSRF token.
32+
* @property requireCsrfProtectionMatcher the [ServerWebExchangeMatcher] used to determine when CSRF protection
33+
* is enabled.
34+
*/
35+
class ServerCsrfDsl {
36+
var accessDeniedHandler: ServerAccessDeniedHandler? = null
37+
var csrfTokenRepository: ServerCsrfTokenRepository? = null
38+
var requireCsrfProtectionMatcher: ServerWebExchangeMatcher? = null
39+
40+
private var disabled = false
41+
42+
/**
43+
* Disables CSRF protection
44+
*/
45+
fun disable() {
46+
disabled = true
47+
}
48+
49+
internal fun get(): (ServerHttpSecurity.CsrfSpec) -> Unit {
50+
return { csrf ->
51+
accessDeniedHandler?.also { csrf.accessDeniedHandler(accessDeniedHandler) }
52+
csrfTokenRepository?.also { csrf.csrfTokenRepository(csrfTokenRepository) }
53+
requireCsrfProtectionMatcher?.also { csrf.requireCsrfProtectionMatcher(requireCsrfProtectionMatcher) }
54+
if (disabled) {
55+
csrf.disable()
56+
}
57+
}
58+
}
59+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
/*
2+
* Copyright 2002-2020 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.security.config.web.server
18+
19+
import org.springframework.security.web.server.ServerAuthenticationEntryPoint
20+
import org.springframework.security.web.server.authorization.ServerAccessDeniedHandler
21+
22+
/**
23+
* A Kotlin DSL to configure [ServerHttpSecurity] exception handling using idiomatic Kotlin
24+
* code.
25+
*
26+
* @author Eleftheria Stein
27+
* @since 5.4
28+
* @property authenticationEntryPoint the [ServerAuthenticationEntryPoint] to use when
29+
* the application request authentication
30+
* @property accessDeniedHandler the [ServerAccessDeniedHandler] to use when an
31+
* authenticated user does not hold a required authority
32+
*/
33+
class ServerExceptionHandlingDsl {
34+
var authenticationEntryPoint: ServerAuthenticationEntryPoint? = null
35+
var accessDeniedHandler: ServerAccessDeniedHandler? = null
36+
37+
internal fun get(): (ServerHttpSecurity.ExceptionHandlingSpec) -> Unit {
38+
return { exceptionHandling ->
39+
authenticationEntryPoint?.also { exceptionHandling.authenticationEntryPoint(authenticationEntryPoint) }
40+
accessDeniedHandler?.also { exceptionHandling.accessDeniedHandler(accessDeniedHandler) }
41+
}
42+
}
43+
}

0 commit comments

Comments
 (0)