mirror of
https://github.com/spring-projects/spring-security.git
synced 2025-06-24 13:02:13 +00:00
Add Saml2Logout DSL Support
Closes gh-14935
This commit is contained in:
parent
3677c66aa2
commit
2bcbef1695
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
* Copyright 2002-2024 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.
|
||||
@ -707,6 +707,69 @@ class HttpSecurityDsl(private val http: HttpSecurity, private val init: HttpSecu
|
||||
this.http.saml2Login(saml2LoginCustomizer)
|
||||
}
|
||||
|
||||
/**
|
||||
* Configures logout support for a SAML 2.0 Service Provider. <br>
|
||||
* <br>
|
||||
*
|
||||
* Implements the <b>Single Logout Profile, using POST and REDIRECT bindings</b>, as
|
||||
* documented in the
|
||||
* <a target="_blank" href="https://docs.oasis-open.org/security/saml/">SAML V2.0
|
||||
* Core, Profiles and Bindings</a> specifications. <br>
|
||||
* <br>
|
||||
*
|
||||
* As a prerequisite to using this feature, is that you have a SAML v2.0 Asserting
|
||||
* Party to send a logout request to. The representation of the relying party and the
|
||||
* asserting party is contained within [RelyingPartyRegistration]. <br>
|
||||
* <br>
|
||||
*
|
||||
* [RelyingPartyRegistration] (s) are composed within a
|
||||
* [RelyingPartyRegistrationRepository], which is <b>required</b> and must be
|
||||
* registered with the [ApplicationContext] or configured via
|
||||
* [HttpSecurityDsl.saml2Login].<br>
|
||||
* <br>
|
||||
*
|
||||
* The default configuration provides an auto-generated logout endpoint at
|
||||
* `/logout` and redirects to `/login?logout` when
|
||||
* logout completes. <br>
|
||||
* <br>
|
||||
*
|
||||
* <p>
|
||||
* <h2>Example Configuration</h2>
|
||||
*
|
||||
* The following example shows the minimal configuration required, using a
|
||||
* hypothetical asserting party.
|
||||
*
|
||||
* Example:
|
||||
*
|
||||
* ```
|
||||
* @Configuration
|
||||
* @EnableWebSecurity
|
||||
* class SecurityConfig {
|
||||
*
|
||||
* @Bean
|
||||
* fun securityFilterChain(http: HttpSecurity): SecurityFilterChain {
|
||||
* http {
|
||||
* saml2Login {
|
||||
* relyingPartyRegistration = getSaml2RelyingPartyRegistration()
|
||||
* }
|
||||
* saml2Logout { }
|
||||
* }
|
||||
* return http.build()
|
||||
* }
|
||||
* }
|
||||
* ```
|
||||
*
|
||||
* <p>
|
||||
* @param saml2LogoutConfiguration custom configuration to configure the
|
||||
* SAML 2.0 service provider
|
||||
* @since 6.3
|
||||
* @see [Saml2LogoutDsl]
|
||||
*/
|
||||
fun saml2Logout(saml2LogoutConfiguration: Saml2LogoutDsl.() -> Unit) {
|
||||
val saml2LogoutCustomizer = Saml2LogoutDsl().apply(saml2LogoutConfiguration).get()
|
||||
this.http.saml2Logout(saml2LogoutCustomizer)
|
||||
}
|
||||
|
||||
/**
|
||||
* Configures a SAML 2.0 relying party metadata endpoint.
|
||||
*
|
||||
|
@ -0,0 +1,69 @@
|
||||
/*
|
||||
* Copyright 2002-2021 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.saml2.Saml2LogoutConfigurer
|
||||
import org.springframework.security.config.annotation.web.saml2.LogoutRequestDsl
|
||||
import org.springframework.security.config.annotation.web.saml2.LogoutResponseDsl
|
||||
import org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistrationRepository
|
||||
|
||||
/**
|
||||
* A Kotlin DSL to configure [HttpSecurity] SAML2 logout using idiomatic Kotlin code.
|
||||
*
|
||||
* @author Josh Cummings
|
||||
* @since 6.3
|
||||
* @property relyingPartyRegistrationRepository the [RelyingPartyRegistrationRepository] of relying parties,
|
||||
* each party representing a service provider, SP and this host, and identity provider, IDP pair that
|
||||
* communicate with each other.
|
||||
* @property logoutUrl the logout page to begin the SLO redirect flow
|
||||
*/
|
||||
@SecurityMarker
|
||||
class Saml2LogoutDsl {
|
||||
var relyingPartyRegistrationRepository: RelyingPartyRegistrationRepository? = null
|
||||
var logoutUrl: String? = null
|
||||
|
||||
private var logoutRequest: ((Saml2LogoutConfigurer<HttpSecurity>.LogoutRequestConfigurer) -> Unit)? = null
|
||||
private var logoutResponse: ((Saml2LogoutConfigurer<HttpSecurity>.LogoutResponseConfigurer) -> Unit)? = null
|
||||
|
||||
/**
|
||||
* Configures SAML 2.0 Logout Request components
|
||||
* @param logoutRequestConfig the {@link Customizer} to provide more
|
||||
* options for the {@link LogoutRequestConfigurer}
|
||||
*/
|
||||
fun logoutRequest(logoutRequestConfig: LogoutRequestDsl.() -> Unit) {
|
||||
this.logoutRequest = LogoutRequestDsl().apply(logoutRequestConfig).get()
|
||||
}
|
||||
|
||||
/**
|
||||
* Configures SAML 2.0 Logout Response components
|
||||
* @param logoutResponseConfig the {@link Customizer} to provide more
|
||||
* options for the {@link LogoutResponseConfigurer}
|
||||
*/
|
||||
fun logoutResponse(logoutResponseConfig: LogoutResponseDsl.() -> Unit) {
|
||||
this.logoutResponse = LogoutResponseDsl().apply(logoutResponseConfig).get()
|
||||
}
|
||||
|
||||
internal fun get(): (Saml2LogoutConfigurer<HttpSecurity>) -> Unit {
|
||||
return { saml2Logout ->
|
||||
relyingPartyRegistrationRepository?.also { saml2Logout.relyingPartyRegistrationRepository(relyingPartyRegistrationRepository) }
|
||||
logoutUrl?.also { saml2Logout.logoutUrl(logoutUrl) }
|
||||
logoutRequest?.also { saml2Logout.logoutRequest(logoutRequest) }
|
||||
logoutResponse?.also { saml2Logout.logoutResponse(logoutResponse) }
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,53 @@
|
||||
/*
|
||||
* Copyright 2002-2024 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.saml2
|
||||
|
||||
import org.springframework.security.config.annotation.web.builders.HttpSecurity
|
||||
import org.springframework.security.config.annotation.web.configurers.saml2.Saml2LogoutConfigurer
|
||||
import org.springframework.security.saml2.provider.service.authentication.logout.Saml2LogoutRequestValidator
|
||||
import org.springframework.security.saml2.provider.service.web.authentication.logout.HttpSessionLogoutRequestRepository
|
||||
import org.springframework.security.saml2.provider.service.web.authentication.logout.Saml2LogoutRequestRepository
|
||||
import org.springframework.security.saml2.provider.service.web.authentication.logout.Saml2LogoutRequestResolver
|
||||
|
||||
/**
|
||||
* A Kotlin DSL to configure SAML 2.0 Logout Request components using idiomatic Kotlin code.
|
||||
*
|
||||
* @author Josh Cummings
|
||||
* @since 6.3
|
||||
* @property logoutUrl The URL by which the asserting party can send a SAML 2.0 Logout Request.
|
||||
* The Asserting Party should use whatever HTTP method specified in {@link RelyingPartyRegistration#getSingleLogoutServiceBindings()}.
|
||||
* @property logoutRequestValidator the [Saml2LogoutRequestValidator] to use for validating incoming {@code LogoutRequest}s.
|
||||
* @property logoutRequestResolver the [Saml2LogoutRequestResolver] to use for generating outgoing {@code LogoutRequest}s.
|
||||
* @property logoutRequestRepository the [Saml2LogoutRequestRepository] to use for storing outgoing {@code LogoutRequest}s for
|
||||
* linking to the corresponding {@code LogoutResponse} from the asserting party
|
||||
*/
|
||||
@Saml2SecurityMarker
|
||||
class LogoutRequestDsl {
|
||||
var logoutUrl = "/logout/saml2/slo"
|
||||
var logoutRequestValidator: Saml2LogoutRequestValidator? = null
|
||||
var logoutRequestResolver: Saml2LogoutRequestResolver? = null
|
||||
var logoutRequestRepository: Saml2LogoutRequestRepository = HttpSessionLogoutRequestRepository()
|
||||
|
||||
internal fun get(): (Saml2LogoutConfigurer<HttpSecurity>.LogoutRequestConfigurer) -> Unit {
|
||||
return { logoutRequest ->
|
||||
logoutUrl.also { logoutRequest.logoutUrl(logoutUrl) }
|
||||
logoutRequestValidator?.also { logoutRequest.logoutRequestValidator(logoutRequestValidator) }
|
||||
logoutRequestResolver?.also { logoutRequest.logoutRequestResolver(logoutRequestResolver) }
|
||||
logoutRequestRepository.also { logoutRequest.logoutRequestRepository(logoutRequestRepository) }
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,47 @@
|
||||
/*
|
||||
* Copyright 2002-2024 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.saml2
|
||||
|
||||
import org.springframework.security.config.annotation.web.builders.HttpSecurity
|
||||
import org.springframework.security.config.annotation.web.configurers.saml2.Saml2LogoutConfigurer
|
||||
import org.springframework.security.saml2.provider.service.authentication.logout.Saml2LogoutResponseValidator
|
||||
import org.springframework.security.saml2.provider.service.web.authentication.logout.Saml2LogoutResponseResolver
|
||||
|
||||
/**
|
||||
* A Kotlin DSL to configure SAML 2.0 Logout Response components using idiomatic Kotlin code.
|
||||
*
|
||||
* @author Josh Cummings
|
||||
* @since 6.3
|
||||
* @property logoutUrl The URL by which the asserting party can send a SAML 2.0 Logout Response.
|
||||
* The Asserting Party should use whatever HTTP method specified in {@link RelyingPartyRegistration#getSingleLogoutServiceBindings()}.
|
||||
* @property logoutResponseValidator the [Saml2LogoutResponseValidator] to use for validating incoming {@code LogoutResponse}s.
|
||||
* @property logoutResponseResolver the [Saml2LogoutResponseResolver] to use for generating outgoing {@code LogoutResponse}s.
|
||||
*/
|
||||
@Saml2SecurityMarker
|
||||
class LogoutResponseDsl {
|
||||
var logoutUrl = "/logout/saml2/slo"
|
||||
var logoutResponseValidator: Saml2LogoutResponseValidator? = null
|
||||
var logoutResponseResolver: Saml2LogoutResponseResolver? = null
|
||||
|
||||
internal fun get(): (Saml2LogoutConfigurer<HttpSecurity>.LogoutResponseConfigurer) -> Unit {
|
||||
return { logoutResponse ->
|
||||
logoutUrl.also { logoutResponse.logoutUrl(logoutUrl) }
|
||||
logoutResponseValidator?.also { logoutResponse.logoutResponseValidator(logoutResponseValidator) }
|
||||
logoutResponseResolver?.also { logoutResponse.logoutResponseResolver(logoutResponseResolver) }
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,26 @@
|
||||
/*
|
||||
* Copyright 2002-2024 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.saml2
|
||||
|
||||
/**
|
||||
* Marker annotation indicating that the annotated class is part of the SAML 2.0 logout security DSL.
|
||||
*
|
||||
* @author Josh Cummings
|
||||
* @since 6.3
|
||||
*/
|
||||
@DslMarker
|
||||
annotation class Saml2SecurityMarker
|
@ -0,0 +1,126 @@
|
||||
/*
|
||||
* Copyright 2002-2024 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.assertj.core.api.Assertions
|
||||
import org.junit.jupiter.api.Test
|
||||
import org.junit.jupiter.api.extension.ExtendWith
|
||||
import org.springframework.beans.factory.BeanCreationException
|
||||
import org.springframework.beans.factory.annotation.Autowired
|
||||
import org.springframework.context.annotation.Bean
|
||||
import org.springframework.context.annotation.Configuration
|
||||
import org.springframework.security.authentication.TestAuthentication
|
||||
import org.springframework.security.config.annotation.web.builders.HttpSecurity
|
||||
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity
|
||||
import org.springframework.security.config.test.SpringTestContext
|
||||
import org.springframework.security.config.test.SpringTestContextExtension
|
||||
import org.springframework.security.core.authority.AuthorityUtils
|
||||
import org.springframework.security.saml2.provider.service.authentication.DefaultSaml2AuthenticatedPrincipal
|
||||
import org.springframework.security.saml2.provider.service.authentication.Saml2Authentication
|
||||
import org.springframework.security.saml2.provider.service.registration.InMemoryRelyingPartyRegistrationRepository
|
||||
import org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistrationRepository
|
||||
import org.springframework.security.saml2.provider.service.registration.TestRelyingPartyRegistrations
|
||||
import org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors
|
||||
import org.springframework.security.web.SecurityFilterChain
|
||||
import org.springframework.test.web.servlet.MockMvc
|
||||
import org.springframework.test.web.servlet.MvcResult
|
||||
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders
|
||||
import org.springframework.test.web.servlet.result.MockMvcResultMatchers
|
||||
import java.util.*
|
||||
|
||||
/**
|
||||
* Tests for [Saml2LogoutDsl]
|
||||
*
|
||||
* @author Josh Cummings
|
||||
*/
|
||||
@ExtendWith(SpringTestContextExtension::class)
|
||||
class Saml2LogoutDslTests {
|
||||
@JvmField
|
||||
val spring = SpringTestContext(this)
|
||||
|
||||
@Autowired
|
||||
lateinit var mockMvc: MockMvc
|
||||
|
||||
@Test
|
||||
fun `saml2Logout when no relying party registration repository then exception`() {
|
||||
Assertions.assertThatThrownBy { this.spring.register(Saml2LogoutNoRelyingPartyRegistrationRepoConfig::class.java).autowire() }
|
||||
.isInstanceOf(BeanCreationException::class.java)
|
||||
.hasMessageContaining("relyingPartyRegistrationRepository cannot be null")
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
@Throws(Exception::class)
|
||||
fun `saml2Logout when defaults and not saml login then default logout`() {
|
||||
this.spring.register(Saml2LogoutDefaultsConfig::class.java).autowire()
|
||||
val user = TestAuthentication.authenticatedUser()
|
||||
val result: MvcResult = this.mockMvc.perform(
|
||||
MockMvcRequestBuilders.post("/logout").with(SecurityMockMvcRequestPostProcessors.authentication(user))
|
||||
.with(SecurityMockMvcRequestPostProcessors.csrf()))
|
||||
.andExpect(MockMvcResultMatchers.status().isFound())
|
||||
.andReturn()
|
||||
val location = result.response.getHeader("Location")
|
||||
Assertions.assertThat(location).isEqualTo("/login?logout")
|
||||
}
|
||||
|
||||
@Test
|
||||
@Throws(Exception::class)
|
||||
fun saml2LogoutWhenDefaultsThenLogsOutAndSendsLogoutRequest() {
|
||||
this.spring.register(Saml2LogoutDefaultsConfig::class.java).autowire()
|
||||
val principal = DefaultSaml2AuthenticatedPrincipal("user", emptyMap())
|
||||
principal.relyingPartyRegistrationId = "registration-id"
|
||||
val user = Saml2Authentication(principal, "response", AuthorityUtils.createAuthorityList("ROLE_USER"))
|
||||
val result: MvcResult = this.mockMvc.perform(MockMvcRequestBuilders.post("/logout")
|
||||
.with(SecurityMockMvcRequestPostProcessors.authentication(user))
|
||||
.with(SecurityMockMvcRequestPostProcessors.csrf()))
|
||||
.andExpect(MockMvcResultMatchers.status().isFound())
|
||||
.andReturn()
|
||||
val location = result.response.getHeader("Location")
|
||||
Assertions.assertThat(location).startsWith("https://ap.example.org/logout/saml2/request")
|
||||
}
|
||||
|
||||
@Configuration
|
||||
@EnableWebSecurity
|
||||
open class Saml2LogoutNoRelyingPartyRegistrationRepoConfig {
|
||||
@Bean
|
||||
open fun securityFilterChain(http: HttpSecurity): SecurityFilterChain {
|
||||
http {
|
||||
saml2Logout { }
|
||||
}
|
||||
return http.build()
|
||||
}
|
||||
}
|
||||
|
||||
@Configuration
|
||||
@EnableWebSecurity
|
||||
open class Saml2LogoutDefaultsConfig {
|
||||
|
||||
@Bean
|
||||
open fun securityFilterChain(http: HttpSecurity): SecurityFilterChain {
|
||||
http {
|
||||
saml2Logout { }
|
||||
}
|
||||
return http.build()
|
||||
}
|
||||
|
||||
@Bean
|
||||
open fun registrations(): RelyingPartyRegistrationRepository =
|
||||
InMemoryRelyingPartyRegistrationRepository(TestRelyingPartyRegistrations.full().build())
|
||||
|
||||
}
|
||||
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user