Skip to content

Commit 6d8db39

Browse files
committed
Create idiomatic Kotlin DSL for configuring HTTP security
Fixes: spring-projectsgh-5558
1 parent e306482 commit 6d8db39

File tree

88 files changed

+9773
-2
lines changed

Some content is hidden

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

88 files changed

+9773
-2
lines changed

build.gradle

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,15 +4,18 @@ buildscript {
44
classpath "org.springframework.boot:spring-boot-gradle-plugin:$springBootVersion"
55
classpath 'io.spring.nohttp:nohttp-gradle:0.0.2.RELEASE'
66
classpath "io.freefair.gradle:aspectj-plugin:4.0.2"
7+
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlinVersion"
78
}
89
repositories {
910
maven { url 'https://repo.spring.io/plugins-snapshot' }
1011
maven { url 'https://plugins.gradle.org/m2/' }
1112
}
1213
}
14+
1315
apply plugin: 'io.spring.nohttp'
1416
apply plugin: 'locks'
1517
apply plugin: 'io.spring.convention.root'
18+
apply plugin: 'org.jetbrains.kotlin.jvm'
1619

1720
group = 'org.springframework.security'
1821
description = 'Spring Security'

config/spring-security-config.gradle

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
apply plugin: 'io.spring.convention.spring-module'
22
apply plugin: 'trang'
3+
apply plugin: 'kotlin'
34

45
dependencies {
56
// NB: Don't add other compile time dependencies to the config module as this breaks tooling
@@ -27,6 +28,8 @@ dependencies {
2728
optional'org.springframework:spring-web'
2829
optional'org.springframework:spring-webflux'
2930
optional'org.springframework:spring-websocket'
31+
optional 'org.jetbrains.kotlin:kotlin-reflect'
32+
optional 'org.jetbrains.kotlin:kotlin-stdlib-jdk8'
3033

3134
provided 'javax.servlet:javax.servlet-api'
3235

@@ -84,4 +87,11 @@ rncToXsd {
8487
xslFile = new File(rncDir, 'spring-security.xsl')
8588
}
8689

90+
compileKotlin {
91+
kotlinOptions {
92+
jvmTarget = "1.8"
93+
freeCompilerArgs = ["-Xjsr305=strict"]
94+
}
95+
}
96+
8797
build.dependsOn rncToXsd
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
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.servlet
18+
19+
import org.springframework.security.web.util.matcher.AnyRequestMatcher
20+
import org.springframework.security.web.util.matcher.RequestMatcher
21+
22+
abstract class AbstractRequestMatcherDsl {
23+
24+
/**
25+
* Matches any request.
26+
*/
27+
val anyRequest: RequestMatcher = AnyRequestMatcher.INSTANCE
28+
29+
protected data class MatcherAuthorizationRule(val matcher: RequestMatcher,
30+
override val rule: String) : AuthorizationRule(rule)
31+
32+
protected data class PatternAuthorizationRule(val pattern: String,
33+
val patternType: PatternType,
34+
val servletPath: String?,
35+
override val rule: String) : AuthorizationRule(rule)
36+
37+
protected abstract class AuthorizationRule(open val rule: String)
38+
39+
protected enum class PatternType {
40+
ANT, MVC
41+
}
42+
}
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
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.servlet
18+
19+
import org.springframework.security.authentication.AuthenticationProvider
20+
import org.springframework.security.config.annotation.web.builders.HttpSecurity
21+
import org.springframework.security.config.annotation.web.configurers.AnonymousConfigurer
22+
import org.springframework.security.core.Authentication
23+
import org.springframework.security.core.GrantedAuthority
24+
import org.springframework.security.web.authentication.AnonymousAuthenticationFilter
25+
26+
/**
27+
* A Kotlin DSL to configure [HttpSecurity] anonymous authentication using idiomatic
28+
* Kotlin code.
29+
*
30+
* @author Eleftheria Stein
31+
* @since 5.3
32+
* @property key the key to identify tokens created for anonymous authentication
33+
* @property principal the principal for [Authentication] objects of anonymous users
34+
* @property authorities the [Authentication.getAuthorities] for anonymous users
35+
* @property authenticationProvider the [AuthenticationProvider] used to validate an
36+
* anonymous user
37+
* @property authenticationFilter the [AnonymousAuthenticationFilter] used to populate
38+
* an anonymous user.
39+
*/
40+
class AnonymousDsl {
41+
var key: String? = null
42+
var principal: Any? = null
43+
var authorities: List<GrantedAuthority>? = null
44+
var authenticationProvider: AuthenticationProvider? = null
45+
var authenticationFilter: AnonymousAuthenticationFilter? = null
46+
47+
private var disabled = false
48+
49+
/**
50+
* Disable anonymous authentication
51+
*/
52+
fun disable() {
53+
disabled = true
54+
}
55+
56+
internal fun get(): (AnonymousConfigurer<HttpSecurity>) -> Unit {
57+
return { anonymous ->
58+
key?.also { anonymous.key(key) }
59+
principal?.also { anonymous.principal(principal) }
60+
authorities?.also { anonymous.authorities(authorities) }
61+
authenticationProvider?.also { anonymous.authenticationProvider(authenticationProvider) }
62+
authenticationFilter?.also { anonymous.authenticationFilter(authenticationFilter) }
63+
if (disabled) {
64+
anonymous.disable()
65+
}
66+
}
67+
}
68+
}
Lines changed: 159 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,159 @@
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.servlet
18+
19+
import org.springframework.security.config.annotation.web.builders.HttpSecurity
20+
import org.springframework.security.config.annotation.web.configurers.ExpressionUrlAuthorizationConfigurer
21+
import org.springframework.security.web.util.matcher.AnyRequestMatcher
22+
import org.springframework.security.web.util.matcher.RequestMatcher
23+
import org.springframework.util.ClassUtils
24+
25+
/**
26+
* A Kotlin DSL to configure [HttpSecurity] request authorization using idiomatic Kotlin code.
27+
*
28+
* @author Eleftheria Stein
29+
* @since 5.3
30+
*/
31+
class AuthorizeRequestsDsl : AbstractRequestMatcherDsl() {
32+
private val authorizationRules = mutableListOf<AuthorizationRule>()
33+
34+
private val HANDLER_MAPPING_INTROSPECTOR = "org.springframework.web.servlet.handler.HandlerMappingIntrospector"
35+
private val MVC_PRESENT = ClassUtils.isPresent(
36+
HANDLER_MAPPING_INTROSPECTOR,
37+
AuthorizeRequestsDsl::class.java.classLoader)
38+
39+
/**
40+
* Adds a request authorization rule.
41+
*
42+
* @param matches the [RequestMatcher] to match incoming requests against
43+
* @param access the SpEL expression to secure the matching request
44+
* (i.e. "hasAuthority('ROLE_USER') and hasAuthority('ROLE_SUPER')")
45+
*/
46+
fun authorize(matches: RequestMatcher = AnyRequestMatcher.INSTANCE,
47+
access: String = "authenticated") {
48+
authorizationRules.add(MatcherAuthorizationRule(matches, access))
49+
}
50+
51+
/**
52+
* Adds a request authorization rule for an endpoint matching the provided
53+
* pattern.
54+
* If Spring MVC is on the classpath, it will use an MVC matcher.
55+
* If Spring MVC is not an the classpath, it will use an ant matcher.
56+
* The MVC will use the same rules that Spring MVC uses for matching.
57+
* For example, often times a mapping of the path "/path" will match on
58+
* "/path", "/path/", "/path.html", etc.
59+
* If the current request will not be processed by Spring MVC, a reasonable default
60+
* using the pattern as an ant pattern will be used.
61+
*
62+
* @param pattern the pattern to match incoming requests against.
63+
* @param access the SpEL expression to secure the matching request
64+
* (i.e. "hasAuthority('ROLE_USER') and hasAuthority('ROLE_SUPER')")
65+
*/
66+
fun authorize(pattern: String, access: String = "authenticated") {
67+
if (MVC_PRESENT) {
68+
authorizationRules.add(PatternAuthorizationRule(pattern, PatternType.MVC, null, access))
69+
} else {
70+
authorizationRules.add(PatternAuthorizationRule(pattern, PatternType.ANT, null, access))
71+
}
72+
}
73+
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 pattern the pattern to match incoming requests against.
86+
* @param servletPath the servlet path to match incoming requests against. This
87+
* only applies when using an MVC pattern matcher.
88+
* @param access the SpEL expression to secure the matching request
89+
* (i.e. "hasAuthority('ROLE_USER') and hasAuthority('ROLE_SUPER')")
90+
*/
91+
fun authorize(pattern: String, servletPath: String, access: String = "authenticated") {
92+
if (MVC_PRESENT) {
93+
authorizationRules.add(PatternAuthorizationRule(pattern, PatternType.MVC, servletPath, access))
94+
} else {
95+
authorizationRules.add(PatternAuthorizationRule(pattern, PatternType.ANT, servletPath, access))
96+
}
97+
}
98+
99+
/**
100+
* Specify that URLs require a particular authority.
101+
*
102+
* @param authority the authority to require (i.e. ROLE_USER, ROLE_ADMIN, etc).
103+
* @return the SpEL expression "hasAuthority" with the given authority as a
104+
* parameter
105+
*/
106+
fun hasAuthority(authority: String) = "hasAuthority('$authority')"
107+
108+
/**
109+
* Specify that URLs are allowed by anyone.
110+
*/
111+
val permitAll = "permitAll"
112+
113+
/**
114+
* Specify that URLs are allowed by anonymous users.
115+
*/
116+
val anonymous = "anonymous"
117+
118+
/**
119+
* Specify that URLs are allowed by users that have been remembered.
120+
*/
121+
val rememberMe = "rememberMe"
122+
123+
/**
124+
* Specify that URLs are not allowed by anyone.
125+
*/
126+
val denyAll = "denyAll"
127+
128+
/**
129+
* Specify that URLs are allowed by any authenticated user.
130+
*/
131+
val authenticated = "authenticated"
132+
133+
/**
134+
* Specify that URLs are allowed by users who have authenticated and were not
135+
* "remembered".
136+
*/
137+
val fullyAuthenticated = "fullyAuthenticated"
138+
139+
internal fun get(): (ExpressionUrlAuthorizationConfigurer<HttpSecurity>.ExpressionInterceptUrlRegistry) -> Unit {
140+
return { requests ->
141+
authorizationRules.forEach { rule ->
142+
when (rule) {
143+
is MatcherAuthorizationRule -> requests.requestMatchers(rule.matcher).access(rule.rule)
144+
is PatternAuthorizationRule -> {
145+
when (rule.patternType) {
146+
PatternType.ANT -> requests.antMatchers(rule.pattern).access(rule.rule)
147+
PatternType.MVC -> {
148+
val mvcMatchersAuthorizeUrl = requests.mvcMatchers(rule.pattern)
149+
rule.servletPath?.also { mvcMatchersAuthorizeUrl.servletPath(rule.servletPath) }
150+
mvcMatchersAuthorizeUrl.access(rule.rule)
151+
}
152+
}
153+
}
154+
}
155+
}
156+
}
157+
}
158+
}
159+
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
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.servlet
18+
19+
import org.springframework.security.config.annotation.web.builders.HttpSecurity
20+
import org.springframework.security.config.annotation.web.configurers.CorsConfigurer
21+
22+
/**
23+
* A Kotlin DSL to configure [HttpSecurity] CORS using idiomatic Kotlin code.
24+
*
25+
* @author Eleftheria Stein
26+
* @since 5.3
27+
*/
28+
class CorsDsl {
29+
private var disabled = false
30+
31+
/**
32+
* Disable CORS.
33+
*/
34+
fun disable() {
35+
disabled = true
36+
}
37+
38+
internal fun get(): (CorsConfigurer<HttpSecurity>) -> Unit {
39+
return { cors ->
40+
if (disabled) {
41+
cors.disable()
42+
}
43+
}
44+
}
45+
}

0 commit comments

Comments
 (0)