Explorar o código

Security Context Dsl

Closes gh-11039
nor-ek %!s(int64=3) %!d(string=hai) anos
pai
achega
a3e7e54b70

+ 29 - 0
config/src/main/kotlin/org/springframework/security/config/web/servlet/HttpSecurityDsl.kt

@@ -50,6 +50,7 @@ import javax.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)
+    }
 }

+ 42 - 0
config/src/main/kotlin/org/springframework/security/config/web/servlet/SecurityContextDsl.kt

@@ -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.web.servlet
+
+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) }
+        }
+    }
+}

+ 180 - 0
config/src/test/kotlin/org/springframework/security/config/web/servlet/SecurityContextDslTests.kt

@@ -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.web.servlet
+
+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
+        }
+    }
+}