mirror of
https://github.com/spring-projects/spring-security.git
synced 2025-06-28 14:52:24 +00:00
Security Context Dsl
Closes gh-11039
This commit is contained in:
parent
eba091bad5
commit
558bb161c5
@ -50,6 +50,7 @@ import jakarta.servlet.http.HttpServletRequest
|
|||||||
* ```
|
* ```
|
||||||
*
|
*
|
||||||
* @author Eleftheria Stein
|
* @author Eleftheria Stein
|
||||||
|
* @author Norbert Nowak
|
||||||
* @since 5.3
|
* @since 5.3
|
||||||
* @param httpConfiguration the configurations to apply to [HttpSecurity]
|
* @param httpConfiguration the configurations to apply to [HttpSecurity]
|
||||||
*/
|
*/
|
||||||
@ -905,4 +906,32 @@ class HttpSecurityDsl(private val http: HttpSecurity, private val init: HttpSecu
|
|||||||
init()
|
init()
|
||||||
authenticationManager?.also { this.http.authenticationManager(authenticationManager) }
|
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)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -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) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user