Security Context Dsl

Closes gh-11039
This commit is contained in:
nor-ek 2022-04-26 15:18:36 +02:00 committed by Eleftheria Stein
parent eba091bad5
commit 558bb161c5
3 changed files with 251 additions and 0 deletions

View File

@ -50,6 +50,7 @@ import jakarta.servlet.http.HttpServletRequest
* ```
*
* @author Eleftheria Stein
* @author Norbert Nowak
* @since 5.3
* @param httpConfiguration the configurations to apply to [HttpSecurity]
*/
@ -905,4 +906,32 @@ class HttpSecurityDsl(private val http: HttpSecurity, private val init: HttpSecu
init()
authenticationManager?.also { this.http.authenticationManager(authenticationManager) }
}
/**
* Enables security context configuration.
*
* Example:
*
* ```
* @EnableWebSecurity
* class SecurityConfig : WebSecurityConfigurerAdapter() {
*
* override fun configure(http: HttpSecurity) {
* http {
* securityContext {
* securityContextRepository = SECURITY_CONTEXT_REPOSITORY
* }
* }
* }
* }
* ```
* @author Norbert Nowak
* @since 5.7
* @param securityContextConfiguration configuration to be applied to Security Context
* @see [SecurityContextDsl]
*/
fun securityContext(securityContextConfiguration: SecurityContextDsl.() -> Unit) {
val securityContextCustomizer = SecurityContextDsl().apply(securityContextConfiguration).get()
this.http.securityContext(securityContextCustomizer)
}
}

View File

@ -0,0 +1,42 @@
/*
* Copyright 2002-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.security.config.annotation.web
import org.springframework.security.config.annotation.web.builders.HttpSecurity
import org.springframework.security.config.annotation.web.configurers.SecurityContextConfigurer
import org.springframework.security.web.context.SecurityContextRepository
/**
* A Kotlin DSL to configure [HttpSecurity] security context using idiomatic Kotlin code.
*
* @property securityContextRepository the [SecurityContextRepository] used for persisting [org.springframework.security.core.context.SecurityContext] between requests
* @author Norbert Nowak
* @since 5.7
*/
@SecurityMarker
class SecurityContextDsl {
var securityContextRepository: SecurityContextRepository? = null
var requireExplicitSave: Boolean? = null
internal fun get(): (SecurityContextConfigurer<HttpSecurity>) -> Unit {
return { securityContext ->
securityContextRepository?.also { securityContext.securityContextRepository(it) }
requireExplicitSave?.also { securityContext.requireExplicitSave(it) }
}
}
}

View File

@ -0,0 +1,180 @@
/*
* Copyright 2002-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.security.config.annotation.web
import io.mockk.every
import io.mockk.mockk
import io.mockk.spyk
import io.mockk.verify
import org.assertj.core.api.Assertions.assertThat
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.assertDoesNotThrow
import org.junit.jupiter.api.extension.ExtendWith
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.context.annotation.Bean
import org.springframework.security.config.annotation.ObjectPostProcessor
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder
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.WebSecurityConfigurerAdapter
import org.springframework.security.config.test.SpringTestContext
import org.springframework.security.config.test.SpringTestContextExtension
import org.springframework.security.core.context.SecurityContext
import org.springframework.security.core.userdetails.PasswordEncodedUser
import org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestBuilders
import org.springframework.security.web.FilterChainProxy
import org.springframework.security.web.context.*
import org.springframework.security.web.context.request.async.WebAsyncManagerIntegrationFilter
import org.springframework.test.web.servlet.MockMvc
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get
@ExtendWith(SpringTestContextExtension::class)
class SecurityContextDslTests {
@JvmField
val spring = SpringTestContext(this)
@Autowired
lateinit var mvc: MockMvc
@Test
fun `configure when registering object post processor then invoked on security context persistence filter`() {
spring.register(ObjectPostProcessorConfig::class.java).autowire()
verify { ObjectPostProcessorConfig.objectPostProcessor.postProcess(any<SecurityContextPersistenceFilter>()) }
}
@EnableWebSecurity
open class ObjectPostProcessorConfig : WebSecurityConfigurerAdapter() {
override fun configure(http: HttpSecurity) {
// @formatter:off
http {
securityContext { }
}
// @formatter:on
}
@Bean
open fun objectPostProcessor(): ObjectPostProcessor<Any> = objectPostProcessor
companion object {
var objectPostProcessor: ObjectPostProcessor<Any> = spyk(ReflectingObjectPostProcessor())
class ReflectingObjectPostProcessor : ObjectPostProcessor<Any> {
override fun <O> postProcess(`object`: O): O = `object`
}
}
}
@Test
fun `security context when invoked twice then uses original security context repository`() {
spring.register(DuplicateDoesNotOverrideConfig::class.java).autowire()
every { DuplicateDoesNotOverrideConfig.SECURITY_CONTEXT_REPOSITORY.loadContext(any<HttpRequestResponseHolder>()) } returns mockk<SecurityContext>(relaxed = true)
mvc.perform(get("/"))
verify(exactly = 1) { DuplicateDoesNotOverrideConfig.SECURITY_CONTEXT_REPOSITORY.loadContext(any<HttpRequestResponseHolder>()) }
}
@EnableWebSecurity
open class DuplicateDoesNotOverrideConfig : WebSecurityConfigurerAdapter() {
override fun configure(http: HttpSecurity) {
// @formatter:off
http {
securityContext {
securityContextRepository = SECURITY_CONTEXT_REPOSITORY
}
securityContext { }
}
// @formatter:on
}
companion object {
var SECURITY_CONTEXT_REPOSITORY = mockk<SecurityContextRepository>(relaxed = true)
}
}
@Test
fun `security context when security context repository not configured then does not throw exception`() {
spring.register(SecurityContextRepositoryDefaultsSecurityContextRepositoryConfig::class.java).autowire()
assertDoesNotThrow { mvc.perform(get("/")) }
}
@EnableWebSecurity
open class SecurityContextRepositoryDefaultsSecurityContextRepositoryConfig : WebSecurityConfigurerAdapter(true) {
override fun configure(http: HttpSecurity) {
// @formatter:off
http {
addFilterAt<WebAsyncManagerIntegrationFilter>(WebAsyncManagerIntegrationFilter())
anonymous { }
securityContext { }
authorizeRequests {
authorize(anyRequest, permitAll)
}
httpBasic { }
}
// @formatter:on
}
override fun configure(auth: AuthenticationManagerBuilder) {
// @formatter:off
auth
.inMemoryAuthentication()
.withUser("user").password("password").roles("USER")
// @formatter:on
}
}
@Test
fun `security context when require explicit save is true then configure SecurityContextHolderFilter`() {
val repository = HttpSessionSecurityContextRepository()
val testContext = spring.register(RequireExplicitSaveConfig::class.java)
testContext.autowire()
val filterChainProxy = testContext.context.getBean(FilterChainProxy::class.java)
// @formatter:off
val filterTypes = filterChainProxy.getFilters("/").toList()
assertThat(filterTypes)
.anyMatch { it is SecurityContextHolderFilter }
.noneMatch { it is SecurityContextPersistenceFilter }
// @formatter:on
val mvcResult = mvc.perform(SecurityMockMvcRequestBuilders.formLogin()).andReturn()
val securityContext = repository
.loadContext(HttpRequestResponseHolder(mvcResult.request, mvcResult.response))
assertThat(securityContext.authentication).isNotNull
}
@EnableWebSecurity
open class RequireExplicitSaveConfig : WebSecurityConfigurerAdapter() {
override fun configure(http: HttpSecurity) {
// @formatter:off
http {
formLogin { }
securityContext {
requireExplicitSave = true
}
}
// @formatter:on
}
override fun configure(auth: AuthenticationManagerBuilder) {
// @formatter:off
auth
.inMemoryAuthentication()
.withUser(PasswordEncodedUser.user())
// @formatter:on
}
}
}