Skip to content

Commit a3e7e54

Browse files
nor-ekeleftherias
authored andcommitted
Security Context Dsl
Closes gh-11039
1 parent 23594b3 commit a3e7e54

File tree

3 files changed

+251
-0
lines changed

3 files changed

+251
-0
lines changed

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

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@ import javax.servlet.http.HttpServletRequest
5050
* ```
5151
*
5252
* @author Eleftheria Stein
53+
* @author Norbert Nowak
5354
* @since 5.3
5455
* @param httpConfiguration the configurations to apply to [HttpSecurity]
5556
*/
@@ -905,4 +906,32 @@ class HttpSecurityDsl(private val http: HttpSecurity, private val init: HttpSecu
905906
init()
906907
authenticationManager?.also { this.http.authenticationManager(authenticationManager) }
907908
}
909+
910+
/**
911+
* Enables security context configuration.
912+
*
913+
* Example:
914+
*
915+
* ```
916+
* @EnableWebSecurity
917+
* class SecurityConfig : WebSecurityConfigurerAdapter() {
918+
*
919+
* override fun configure(http: HttpSecurity) {
920+
* http {
921+
* securityContext {
922+
* securityContextRepository = SECURITY_CONTEXT_REPOSITORY
923+
* }
924+
* }
925+
* }
926+
* }
927+
* ```
928+
* @author Norbert Nowak
929+
* @since 5.7
930+
* @param securityContextConfiguration configuration to be applied to Security Context
931+
* @see [SecurityContextDsl]
932+
*/
933+
fun securityContext(securityContextConfiguration: SecurityContextDsl.() -> Unit) {
934+
val securityContextCustomizer = SecurityContextDsl().apply(securityContextConfiguration).get()
935+
this.http.securityContext(securityContextCustomizer)
936+
}
908937
}
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
/*
2+
* Copyright 2002-2022 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+
package org.springframework.security.config.web.servlet
17+
18+
import org.springframework.security.config.annotation.web.builders.HttpSecurity
19+
import org.springframework.security.config.annotation.web.configurers.SecurityContextConfigurer
20+
import org.springframework.security.web.context.SecurityContextRepository
21+
22+
23+
/**
24+
* A Kotlin DSL to configure [HttpSecurity] security context using idiomatic Kotlin code.
25+
*
26+
* @property securityContextRepository the [SecurityContextRepository] used for persisting [org.springframework.security.core.context.SecurityContext] between requests
27+
* @author Norbert Nowak
28+
* @since 5.7
29+
*/
30+
@SecurityMarker
31+
class SecurityContextDsl {
32+
33+
var securityContextRepository: SecurityContextRepository? = null
34+
var requireExplicitSave: Boolean? = null
35+
36+
internal fun get(): (SecurityContextConfigurer<HttpSecurity>) -> Unit {
37+
return { securityContext ->
38+
securityContextRepository?.also { securityContext.securityContextRepository(it) }
39+
requireExplicitSave?.also { securityContext.requireExplicitSave(it) }
40+
}
41+
}
42+
}
Lines changed: 180 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,180 @@
1+
/*
2+
* Copyright 2002-2022 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 io.mockk.every
20+
import io.mockk.mockk
21+
import io.mockk.spyk
22+
import io.mockk.verify
23+
import org.assertj.core.api.Assertions.assertThat
24+
import org.junit.jupiter.api.Test
25+
import org.junit.jupiter.api.assertDoesNotThrow
26+
import org.junit.jupiter.api.extension.ExtendWith
27+
import org.springframework.beans.factory.annotation.Autowired
28+
import org.springframework.context.annotation.Bean
29+
import org.springframework.security.config.annotation.ObjectPostProcessor
30+
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder
31+
import org.springframework.security.config.annotation.web.builders.HttpSecurity
32+
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity
33+
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter
34+
import org.springframework.security.config.test.SpringTestContext
35+
import org.springframework.security.config.test.SpringTestContextExtension
36+
import org.springframework.security.core.context.SecurityContext
37+
import org.springframework.security.core.userdetails.PasswordEncodedUser
38+
import org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestBuilders
39+
import org.springframework.security.web.FilterChainProxy
40+
import org.springframework.security.web.context.*
41+
import org.springframework.security.web.context.request.async.WebAsyncManagerIntegrationFilter
42+
import org.springframework.test.web.servlet.MockMvc
43+
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get
44+
45+
@ExtendWith(SpringTestContextExtension::class)
46+
class SecurityContextDslTests {
47+
48+
@JvmField
49+
val spring = SpringTestContext(this)
50+
51+
@Autowired
52+
lateinit var mvc: MockMvc
53+
54+
@Test
55+
fun `configure when registering object post processor then invoked on security context persistence filter`() {
56+
spring.register(ObjectPostProcessorConfig::class.java).autowire()
57+
verify { ObjectPostProcessorConfig.objectPostProcessor.postProcess(any<SecurityContextPersistenceFilter>()) }
58+
}
59+
60+
@EnableWebSecurity
61+
open class ObjectPostProcessorConfig : WebSecurityConfigurerAdapter() {
62+
override fun configure(http: HttpSecurity) {
63+
// @formatter:off
64+
http {
65+
securityContext { }
66+
}
67+
// @formatter:on
68+
}
69+
70+
@Bean
71+
open fun objectPostProcessor(): ObjectPostProcessor<Any> = objectPostProcessor
72+
73+
companion object {
74+
var objectPostProcessor: ObjectPostProcessor<Any> = spyk(ReflectingObjectPostProcessor())
75+
76+
class ReflectingObjectPostProcessor : ObjectPostProcessor<Any> {
77+
override fun <O> postProcess(`object`: O): O = `object`
78+
}
79+
}
80+
}
81+
82+
@Test
83+
fun `security context when invoked twice then uses original security context repository`() {
84+
spring.register(DuplicateDoesNotOverrideConfig::class.java).autowire()
85+
every { DuplicateDoesNotOverrideConfig.SECURITY_CONTEXT_REPOSITORY.loadContext(any<HttpRequestResponseHolder>()) } returns mockk<SecurityContext>(relaxed = true)
86+
mvc.perform(get("/"))
87+
verify(exactly = 1) { DuplicateDoesNotOverrideConfig.SECURITY_CONTEXT_REPOSITORY.loadContext(any<HttpRequestResponseHolder>()) }
88+
}
89+
90+
91+
@EnableWebSecurity
92+
open class DuplicateDoesNotOverrideConfig : WebSecurityConfigurerAdapter() {
93+
override fun configure(http: HttpSecurity) {
94+
// @formatter:off
95+
http {
96+
securityContext {
97+
securityContextRepository = SECURITY_CONTEXT_REPOSITORY
98+
}
99+
securityContext { }
100+
}
101+
// @formatter:on
102+
}
103+
104+
companion object {
105+
var SECURITY_CONTEXT_REPOSITORY = mockk<SecurityContextRepository>(relaxed = true)
106+
}
107+
}
108+
109+
@Test
110+
fun `security context when security context repository not configured then does not throw exception`() {
111+
spring.register(SecurityContextRepositoryDefaultsSecurityContextRepositoryConfig::class.java).autowire()
112+
assertDoesNotThrow { mvc.perform(get("/")) }
113+
}
114+
115+
@EnableWebSecurity
116+
open class SecurityContextRepositoryDefaultsSecurityContextRepositoryConfig : WebSecurityConfigurerAdapter(true) {
117+
override fun configure(http: HttpSecurity) {
118+
// @formatter:off
119+
http {
120+
addFilterAt<WebAsyncManagerIntegrationFilter>(WebAsyncManagerIntegrationFilter())
121+
anonymous { }
122+
securityContext { }
123+
authorizeRequests {
124+
authorize(anyRequest, permitAll)
125+
}
126+
httpBasic { }
127+
}
128+
// @formatter:on
129+
}
130+
131+
override fun configure(auth: AuthenticationManagerBuilder) {
132+
// @formatter:off
133+
auth
134+
.inMemoryAuthentication()
135+
.withUser("user").password("password").roles("USER")
136+
// @formatter:on
137+
}
138+
}
139+
140+
@Test
141+
fun `security context when require explicit save is true then configure SecurityContextHolderFilter`() {
142+
val repository = HttpSessionSecurityContextRepository()
143+
val testContext = spring.register(RequireExplicitSaveConfig::class.java)
144+
testContext.autowire()
145+
val filterChainProxy = testContext.context.getBean(FilterChainProxy::class.java)
146+
// @formatter:off
147+
val filterTypes = filterChainProxy.getFilters("/").toList()
148+
149+
assertThat(filterTypes)
150+
.anyMatch { it is SecurityContextHolderFilter }
151+
.noneMatch { it is SecurityContextPersistenceFilter }
152+
// @formatter:on
153+
val mvcResult = mvc.perform(SecurityMockMvcRequestBuilders.formLogin()).andReturn()
154+
val securityContext = repository
155+
.loadContext(HttpRequestResponseHolder(mvcResult.request, mvcResult.response))
156+
assertThat(securityContext.authentication).isNotNull
157+
}
158+
159+
@EnableWebSecurity
160+
open class RequireExplicitSaveConfig : WebSecurityConfigurerAdapter() {
161+
override fun configure(http: HttpSecurity) {
162+
// @formatter:off
163+
http {
164+
formLogin { }
165+
securityContext {
166+
requireExplicitSave = true
167+
}
168+
}
169+
// @formatter:on
170+
}
171+
172+
override fun configure(auth: AuthenticationManagerBuilder) {
173+
// @formatter:off
174+
auth
175+
.inMemoryAuthentication()
176+
.withUser(PasswordEncodedUser.user())
177+
// @formatter:on
178+
}
179+
}
180+
}

0 commit comments

Comments
 (0)