diff --git a/config/src/main/kotlin/org/springframework/security/config/annotation/web/HttpSecurityDsl.kt b/config/src/main/kotlin/org/springframework/security/config/annotation/web/HttpSecurityDsl.kt index 06863f06e52..2696c3f6071 100644 --- a/config/src/main/kotlin/org/springframework/security/config/annotation/web/HttpSecurityDsl.kt +++ b/config/src/main/kotlin/org/springframework/security/config/annotation/web/HttpSecurityDsl.kt @@ -1059,6 +1059,39 @@ class HttpSecurityDsl(private val http: HttpSecurity, private val init: HttpSecu this.http.oauth2ResourceServer(oauth2ResourceServerCustomizer) } + /** + * Configures OAuth 2.1 Authorization Server support. + * + * Example: + * + * ``` + * @Configuration + * @EnableWebSecurity + * class SecurityConfig { + * + * @Bean + * fun securityFilterChain(http: HttpSecurity): SecurityFilterChain { + * http { + * oauth2AuthorizationServer { + * oidc { + * userInfoEndpoint { } + * } + * } + * } + * return http.build() + * } + * } + * ``` + * + * @param oauth2AuthorizationServerConfiguration custom configuration to configure the + * OAuth 2.1 Authorization Server support + * @see [OAuth2AuthorizationServerDsl] + */ + fun oauth2AuthorizationServer(oauth2AuthorizationServerConfiguration: OAuth2AuthorizationServerDsl.() -> Unit) { + val oauth2AuthorizationServerCustomizer = OAuth2AuthorizationServerDsl().apply(oauth2AuthorizationServerConfiguration).get() + this.http.oauth2AuthorizationServer(oauth2AuthorizationServerCustomizer) + } + /** * Configures OIDC 1.0 logout support. * diff --git a/config/src/main/kotlin/org/springframework/security/config/annotation/web/OAuth2AuthorizationServerDsl.kt b/config/src/main/kotlin/org/springframework/security/config/annotation/web/OAuth2AuthorizationServerDsl.kt new file mode 100644 index 00000000000..43b9def7ca7 --- /dev/null +++ b/config/src/main/kotlin/org/springframework/security/config/annotation/web/OAuth2AuthorizationServerDsl.kt @@ -0,0 +1,414 @@ +/* + * Copyright 2004-present 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.oauth2.server.authorization.* +import org.springframework.security.config.annotation.web.oauth2.server.authorization.OidcDsl +import org.springframework.security.oauth2.core.OAuth2Token +import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationConsentService +import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationService +import org.springframework.security.oauth2.server.authorization.client.RegisteredClientRepository +import org.springframework.security.oauth2.server.authorization.settings.AuthorizationServerSettings +import org.springframework.security.oauth2.server.authorization.token.OAuth2TokenGenerator + +/** + * A Kotlin DSL to configure [HttpSecurity] OAuth 2.1 Authorization Server support using idiomatic Kotlin code. + * + * @author Mehrdad Bozorgmehr + * @since 7.0 + * @property registeredClientRepository the repository of registered clients. + * @property authorizationService the authorization service. + * @property authorizationConsentService the authorization consent service. + * @property authorizationServerSettings the authorization server settings. + * @property tokenGenerator the token generator. + */ +@SecurityMarker +class OAuth2AuthorizationServerDsl { + var registeredClientRepository: RegisteredClientRepository? = null + var authorizationService: OAuth2AuthorizationService? = null + var authorizationConsentService: OAuth2AuthorizationConsentService? = null + var authorizationServerSettings: AuthorizationServerSettings? = null + var tokenGenerator: OAuth2TokenGenerator? = null + + private var clientAuthenticationConfig: ((OAuth2ClientAuthenticationConfigurer) -> Unit)? = null + private var authorizationServerMetadataEndpointConfig: ((OAuth2AuthorizationServerMetadataEndpointConfigurer) -> Unit)? = null + private var authorizationEndpointConfig: ((OAuth2AuthorizationEndpointConfigurer) -> Unit)? = null + private var pushedAuthorizationRequestEndpointConfig: ((OAuth2PushedAuthorizationRequestEndpointConfigurer) -> Unit)? = null + private var tokenEndpointConfig: ((OAuth2TokenEndpointConfigurer) -> Unit)? = null + private var tokenIntrospectionEndpointConfig: ((OAuth2TokenIntrospectionEndpointConfigurer) -> Unit)? = null + private var tokenRevocationEndpointConfig: ((OAuth2TokenRevocationEndpointConfigurer) -> Unit)? = null + private var deviceAuthorizationEndpointConfig: ((OAuth2DeviceAuthorizationEndpointConfigurer) -> Unit)? = null + private var deviceVerificationEndpointConfig: ((OAuth2DeviceVerificationEndpointConfigurer) -> Unit)? = null + private var clientRegistrationEndpointConfig: ((OAuth2ClientRegistrationEndpointConfigurer) -> Unit)? = null + private var oidcConfig: ((OidcConfigurer) -> Unit)? = null + + /** + * Configures OAuth 2.0 Client Authentication. + * + * Example: + * + * ``` + * @Configuration + * @EnableWebSecurity + * class SecurityConfig { + * + * @Bean + * fun securityFilterChain(http: HttpSecurity): SecurityFilterChain { + * http { + * oauth2AuthorizationServer { + * clientAuthentication { + * // custom configuration + * } + * } + * } + * return http.build() + * } + * } + * ``` + * + * @param clientAuthenticationConfiguration custom configurations to configure OAuth 2.0 client authentication + */ + fun clientAuthentication(clientAuthenticationConfiguration: OAuth2ClientAuthenticationConfigurer.() -> Unit) { + this.clientAuthenticationConfig = clientAuthenticationConfiguration + } + + /** + * Configures the OAuth 2.0 Authorization Server Metadata Endpoint. + * + * Example: + * + * ``` + * @Configuration + * @EnableWebSecurity + * class SecurityConfig { + * + * @Bean + * fun securityFilterChain(http: HttpSecurity): SecurityFilterChain { + * http { + * oauth2AuthorizationServer { + * authorizationServerMetadataEndpoint { + * // custom configuration + * } + * } + * } + * return http.build() + * } + * } + * ``` + * + * @param authorizationServerMetadataEndpointConfiguration custom configurations to configure the metadata endpoint + */ + fun authorizationServerMetadataEndpoint(authorizationServerMetadataEndpointConfiguration: OAuth2AuthorizationServerMetadataEndpointConfigurer.() -> Unit) { + this.authorizationServerMetadataEndpointConfig = authorizationServerMetadataEndpointConfiguration + } + + /** + * Configures the OAuth 2.0 Authorization Endpoint. + * + * Example: + * + * ``` + * @Configuration + * @EnableWebSecurity + * class SecurityConfig { + * + * @Bean + * fun securityFilterChain(http: HttpSecurity): SecurityFilterChain { + * http { + * oauth2AuthorizationServer { + * authorizationEndpoint { + * // custom configuration + * } + * } + * } + * return http.build() + * } + * } + * ``` + * + * @param authorizationEndpointConfiguration custom configurations to configure the authorization endpoint + */ + fun authorizationEndpoint(authorizationEndpointConfiguration: OAuth2AuthorizationEndpointConfigurer.() -> Unit) { + this.authorizationEndpointConfig = authorizationEndpointConfiguration + } + + /** + * Configures the OAuth 2.0 Pushed Authorization Request Endpoint. + * + * Example: + * + * ``` + * @Configuration + * @EnableWebSecurity + * class SecurityConfig { + * + * @Bean + * fun securityFilterChain(http: HttpSecurity): SecurityFilterChain { + * http { + * oauth2AuthorizationServer { + * pushedAuthorizationRequestEndpoint { + * // custom configuration + * } + * } + * } + * return http.build() + * } + * } + * ``` + * + * @param pushedAuthorizationRequestEndpointConfiguration custom configurations to configure the PAR endpoint + */ + fun pushedAuthorizationRequestEndpoint(pushedAuthorizationRequestEndpointConfiguration: OAuth2PushedAuthorizationRequestEndpointConfigurer.() -> Unit) { + this.pushedAuthorizationRequestEndpointConfig = pushedAuthorizationRequestEndpointConfiguration + } + + /** + * Configures the OAuth 2.0 Token Endpoint. + * + * Example: + * + * ``` + * @Configuration + * @EnableWebSecurity + * class SecurityConfig { + * + * @Bean + * fun securityFilterChain(http: HttpSecurity): SecurityFilterChain { + * http { + * oauth2AuthorizationServer { + * tokenEndpoint { + * // custom configuration + * } + * } + * } + * return http.build() + * } + * } + * ``` + * + * @param tokenEndpointConfiguration custom configurations to configure the token endpoint + */ + fun tokenEndpoint(tokenEndpointConfiguration: OAuth2TokenEndpointConfigurer.() -> Unit) { + this.tokenEndpointConfig = tokenEndpointConfiguration + } + + /** + * Configures the OAuth 2.0 Token Introspection Endpoint. + * + * Example: + * + * ``` + * @Configuration + * @EnableWebSecurity + * class SecurityConfig { + * + * @Bean + * fun securityFilterChain(http: HttpSecurity): SecurityFilterChain { + * http { + * oauth2AuthorizationServer { + * tokenIntrospectionEndpoint { + * // custom configuration + * } + * } + * } + * return http.build() + * } + * } + * ``` + * + * @param tokenIntrospectionEndpointConfiguration custom configurations to configure the token introspection endpoint + */ + fun tokenIntrospectionEndpoint(tokenIntrospectionEndpointConfiguration: OAuth2TokenIntrospectionEndpointConfigurer.() -> Unit) { + this.tokenIntrospectionEndpointConfig = tokenIntrospectionEndpointConfiguration + } + + /** + * Configures the OAuth 2.0 Token Revocation Endpoint. + * + * Example: + * + * ``` + * @Configuration + * @EnableWebSecurity + * class SecurityConfig { + * + * @Bean + * fun securityFilterChain(http: HttpSecurity): SecurityFilterChain { + * http { + * oauth2AuthorizationServer { + * tokenRevocationEndpoint { + * // custom configuration + * } + * } + * } + * return http.build() + * } + * } + * ``` + * + * @param tokenRevocationEndpointConfiguration custom configurations to configure the token revocation endpoint + */ + fun tokenRevocationEndpoint(tokenRevocationEndpointConfiguration: OAuth2TokenRevocationEndpointConfigurer.() -> Unit) { + this.tokenRevocationEndpointConfig = tokenRevocationEndpointConfiguration + } + + /** + * Configures the OAuth 2.0 Device Authorization Endpoint. + * + * Example: + * + * ``` + * @Configuration + * @EnableWebSecurity + * class SecurityConfig { + * + * @Bean + * fun securityFilterChain(http: HttpSecurity): SecurityFilterChain { + * http { + * oauth2AuthorizationServer { + * deviceAuthorizationEndpoint { + * // custom configuration + * } + * } + * } + * return http.build() + * } + * } + * ``` + * + * @param deviceAuthorizationEndpointConfiguration custom configurations to configure the device authorization endpoint + */ + fun deviceAuthorizationEndpoint(deviceAuthorizationEndpointConfiguration: OAuth2DeviceAuthorizationEndpointConfigurer.() -> Unit) { + this.deviceAuthorizationEndpointConfig = deviceAuthorizationEndpointConfiguration + } + + /** + * Configures the OAuth 2.0 Device Verification Endpoint. + * + * Example: + * + * ``` + * @Configuration + * @EnableWebSecurity + * class SecurityConfig { + * + * @Bean + * fun securityFilterChain(http: HttpSecurity): SecurityFilterChain { + * http { + * oauth2AuthorizationServer { + * deviceVerificationEndpoint { + * // custom configuration + * } + * } + * } + * return http.build() + * } + * } + * ``` + * + * @param deviceVerificationEndpointConfiguration custom configurations to configure the device verification endpoint + */ + fun deviceVerificationEndpoint(deviceVerificationEndpointConfiguration: OAuth2DeviceVerificationEndpointConfigurer.() -> Unit) { + this.deviceVerificationEndpointConfig = deviceVerificationEndpointConfiguration + } + + /** + * Configures the OAuth 2.0 Dynamic Client Registration Endpoint. + * + * Example: + * + * ``` + * @Configuration + * @EnableWebSecurity + * class SecurityConfig { + * + * @Bean + * fun securityFilterChain(http: HttpSecurity): SecurityFilterChain { + * http { + * oauth2AuthorizationServer { + * clientRegistrationEndpoint { + * // custom configuration + * } + * } + * } + * return http.build() + * } + * } + * ``` + * + * @param clientRegistrationEndpointConfiguration custom configurations to configure the client registration endpoint + */ + fun clientRegistrationEndpoint(clientRegistrationEndpointConfiguration: OAuth2ClientRegistrationEndpointConfigurer.() -> Unit) { + this.clientRegistrationEndpointConfig = clientRegistrationEndpointConfiguration + } + + /** + * Configures OpenID Connect 1.0 support (disabled by default). + * + * Example: + * + * ``` + * @Configuration + * @EnableWebSecurity + * class SecurityConfig { + * + * @Bean + * fun securityFilterChain(http: HttpSecurity): SecurityFilterChain { + * http { + * oauth2AuthorizationServer { + * oidc { + * userInfoEndpoint { + * userInfoMapper = myUserInfoMapper + * } + * } + * } + * } + * return http.build() + * } + * } + * ``` + * + * @param oidcConfiguration custom configurations to configure OpenID Connect 1.0 support + * @see [OidcDsl] + */ + fun oidc(oidcConfiguration: OidcDsl.() -> Unit) { + this.oidcConfig = OidcDsl().apply(oidcConfiguration).get() + } + + internal fun get(): (OAuth2AuthorizationServerConfigurer) -> Unit { + return { oauth2AuthorizationServer -> + registeredClientRepository?.also { oauth2AuthorizationServer.registeredClientRepository(it) } + authorizationService?.also { oauth2AuthorizationServer.authorizationService(it) } + authorizationConsentService?.also { oauth2AuthorizationServer.authorizationConsentService(it) } + authorizationServerSettings?.also { oauth2AuthorizationServer.authorizationServerSettings(it) } + tokenGenerator?.also { oauth2AuthorizationServer.tokenGenerator(it) } + clientAuthenticationConfig?.also { oauth2AuthorizationServer.clientAuthentication(it) } + authorizationServerMetadataEndpointConfig?.also { oauth2AuthorizationServer.authorizationServerMetadataEndpoint(it) } + authorizationEndpointConfig?.also { oauth2AuthorizationServer.authorizationEndpoint(it) } + pushedAuthorizationRequestEndpointConfig?.also { oauth2AuthorizationServer.pushedAuthorizationRequestEndpoint(it) } + tokenEndpointConfig?.also { oauth2AuthorizationServer.tokenEndpoint(it) } + tokenIntrospectionEndpointConfig?.also { oauth2AuthorizationServer.tokenIntrospectionEndpoint(it) } + tokenRevocationEndpointConfig?.also { oauth2AuthorizationServer.tokenRevocationEndpoint(it) } + deviceAuthorizationEndpointConfig?.also { oauth2AuthorizationServer.deviceAuthorizationEndpoint(it) } + deviceVerificationEndpointConfig?.also { oauth2AuthorizationServer.deviceVerificationEndpoint(it) } + clientRegistrationEndpointConfig?.also { oauth2AuthorizationServer.clientRegistrationEndpoint(it) } + oidcConfig?.also { oauth2AuthorizationServer.oidc(it) } + } + } +} + diff --git a/config/src/main/kotlin/org/springframework/security/config/annotation/web/oauth2/server/authorization/OAuth2AuthorizationServerSecurityMarker.kt b/config/src/main/kotlin/org/springframework/security/config/annotation/web/oauth2/server/authorization/OAuth2AuthorizationServerSecurityMarker.kt new file mode 100644 index 00000000000..d8baa4787d3 --- /dev/null +++ b/config/src/main/kotlin/org/springframework/security/config/annotation/web/oauth2/server/authorization/OAuth2AuthorizationServerSecurityMarker.kt @@ -0,0 +1,27 @@ +/* + * Copyright 2004-present 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.oauth2.server.authorization + +/** + * Marker annotation indicating that the annotated class is part of the OAuth2 Authorization Server Security DSL. + * + * @author Mehrdad Bozorgmehr + * @since 7.0 + */ +@DslMarker +annotation class OAuth2AuthorizationServerSecurityMarker + diff --git a/config/src/main/kotlin/org/springframework/security/config/annotation/web/oauth2/server/authorization/OidcClientRegistrationEndpointDsl.kt b/config/src/main/kotlin/org/springframework/security/config/annotation/web/oauth2/server/authorization/OidcClientRegistrationEndpointDsl.kt new file mode 100644 index 00000000000..fd84a997689 --- /dev/null +++ b/config/src/main/kotlin/org/springframework/security/config/annotation/web/oauth2/server/authorization/OidcClientRegistrationEndpointDsl.kt @@ -0,0 +1,51 @@ +/* + * Copyright 2004-present 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.oauth2.server.authorization + +import org.springframework.security.authentication.AuthenticationProvider +import org.springframework.security.config.annotation.web.configurers.oauth2.server.authorization.OidcClientRegistrationEndpointConfigurer +import org.springframework.security.web.authentication.AuthenticationConverter +import org.springframework.security.web.authentication.AuthenticationFailureHandler +import org.springframework.security.web.authentication.AuthenticationSuccessHandler +import java.util.function.Consumer + +/** + * A Kotlin DSL to configure the OpenID Connect Dynamic Client Registration 1.0 Endpoint using idiomatic Kotlin code. + * + * @author Mehrdad Bozorgmehr + * @since 7.0 + */ +@OAuth2AuthorizationServerSecurityMarker +class OidcClientRegistrationEndpointDsl { + + var clientRegistrationRequestConverter: AuthenticationConverter? = null + var clientRegistrationRequestConverters: Consumer>? = null + var authenticationProviders: Consumer>? = null + var clientRegistrationResponseHandler: AuthenticationSuccessHandler? = null + var errorResponseHandler: AuthenticationFailureHandler? = null + + internal fun get(): (OidcClientRegistrationEndpointConfigurer) -> Unit { + return { clientRegistrationEndpoint -> + clientRegistrationRequestConverter?.also { clientRegistrationEndpoint.clientRegistrationRequestConverter(it) } + clientRegistrationRequestConverters?.also { clientRegistrationEndpoint.clientRegistrationRequestConverters(it) } + authenticationProviders?.also { clientRegistrationEndpoint.authenticationProviders(it) } + clientRegistrationResponseHandler?.also { clientRegistrationEndpoint.clientRegistrationResponseHandler(it) } + errorResponseHandler?.also { clientRegistrationEndpoint.errorResponseHandler(it) } + } + } +} + diff --git a/config/src/main/kotlin/org/springframework/security/config/annotation/web/oauth2/server/authorization/OidcDsl.kt b/config/src/main/kotlin/org/springframework/security/config/annotation/web/oauth2/server/authorization/OidcDsl.kt new file mode 100644 index 00000000000..5ff2b98793b --- /dev/null +++ b/config/src/main/kotlin/org/springframework/security/config/annotation/web/oauth2/server/authorization/OidcDsl.kt @@ -0,0 +1,88 @@ +/* + * Copyright 2004-present 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.oauth2.server.authorization + +import org.springframework.security.config.annotation.web.configurers.oauth2.server.authorization.OidcConfigurer +import org.springframework.security.config.annotation.web.configurers.oauth2.server.authorization.OidcProviderConfigurationEndpointConfigurer +import org.springframework.security.config.annotation.web.configurers.oauth2.server.authorization.OidcLogoutEndpointConfigurer +import org.springframework.security.config.annotation.web.configurers.oauth2.server.authorization.OidcClientRegistrationEndpointConfigurer +import org.springframework.security.config.annotation.web.configurers.oauth2.server.authorization.OidcUserInfoEndpointConfigurer + +/** + * A Kotlin DSL to configure OpenID Connect 1.0 support using idiomatic Kotlin code. + * + * @author Mehrdad Bozorgmehr + * @since 7.0 + */ +@OAuth2AuthorizationServerSecurityMarker +class OidcDsl { + + private var providerConfigurationEndpointConfig: ((OidcProviderConfigurationEndpointConfigurer) -> Unit)? = null + private var logoutEndpointConfig: ((OidcLogoutEndpointConfigurer) -> Unit)? = null + private var clientRegistrationEndpointConfig: ((OidcClientRegistrationEndpointConfigurer) -> Unit)? = null + private var userInfoEndpointConfig: ((OidcUserInfoEndpointConfigurer) -> Unit)? = null + + /** + * Configures the OpenID Connect 1.0 Provider Configuration Endpoint. + * + * @param providerConfigurationEndpointConfiguration custom configuration to apply + */ + fun providerConfigurationEndpoint(providerConfigurationEndpointConfiguration: OidcProviderConfigurationEndpointDsl.() -> Unit) { + this.providerConfigurationEndpointConfig = OidcProviderConfigurationEndpointDsl() + .apply(providerConfigurationEndpointConfiguration).get() + } + + /** + * Configures the OpenID Connect 1.0 RP-Initiated Logout Endpoint. + * + * @param logoutEndpointConfiguration custom configuration to apply + */ + fun logoutEndpoint(logoutEndpointConfiguration: OidcLogoutEndpointDsl.() -> Unit) { + this.logoutEndpointConfig = OidcLogoutEndpointDsl() + .apply(logoutEndpointConfiguration).get() + } + + /** + * Configures the OpenID Connect Dynamic Client Registration 1.0 Endpoint. + * + * @param clientRegistrationEndpointConfiguration custom configuration to apply + */ + fun clientRegistrationEndpoint(clientRegistrationEndpointConfiguration: OidcClientRegistrationEndpointDsl.() -> Unit) { + this.clientRegistrationEndpointConfig = OidcClientRegistrationEndpointDsl() + .apply(clientRegistrationEndpointConfiguration).get() + } + + /** + * Configures the OpenID Connect 1.0 UserInfo Endpoint. + * + * @param userInfoEndpointConfiguration custom configuration to apply + */ + fun userInfoEndpoint(userInfoEndpointConfiguration: OidcUserInfoEndpointDsl.() -> Unit) { + this.userInfoEndpointConfig = OidcUserInfoEndpointDsl() + .apply(userInfoEndpointConfiguration).get() + } + + internal fun get(): (OidcConfigurer) -> Unit { + return { oidc -> + providerConfigurationEndpointConfig?.also { oidc.providerConfigurationEndpoint(it) } + logoutEndpointConfig?.also { oidc.logoutEndpoint(it) } + clientRegistrationEndpointConfig?.also { oidc.clientRegistrationEndpoint(it) } + userInfoEndpointConfig?.also { oidc.userInfoEndpoint(it) } + } + } +} + diff --git a/config/src/main/kotlin/org/springframework/security/config/annotation/web/oauth2/server/authorization/OidcLogoutEndpointDsl.kt b/config/src/main/kotlin/org/springframework/security/config/annotation/web/oauth2/server/authorization/OidcLogoutEndpointDsl.kt new file mode 100644 index 00000000000..6c0af34033a --- /dev/null +++ b/config/src/main/kotlin/org/springframework/security/config/annotation/web/oauth2/server/authorization/OidcLogoutEndpointDsl.kt @@ -0,0 +1,51 @@ +/* + * Copyright 2004-present 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.oauth2.server.authorization + +import org.springframework.security.authentication.AuthenticationProvider +import org.springframework.security.config.annotation.web.configurers.oauth2.server.authorization.OidcLogoutEndpointConfigurer +import org.springframework.security.web.authentication.AuthenticationConverter +import org.springframework.security.web.authentication.AuthenticationFailureHandler +import org.springframework.security.web.authentication.AuthenticationSuccessHandler +import java.util.function.Consumer + +/** + * A Kotlin DSL to configure the OpenID Connect 1.0 RP-Initiated Logout Endpoint using idiomatic Kotlin code. + * + * @author Mehrdad Bozorgmehr + * @since 7.0 + */ +@OAuth2AuthorizationServerSecurityMarker +class OidcLogoutEndpointDsl { + + var logoutRequestConverter: AuthenticationConverter? = null + var logoutRequestConverters: Consumer>? = null + var authenticationProviders: Consumer>? = null + var logoutResponseHandler: AuthenticationSuccessHandler? = null + var errorResponseHandler: AuthenticationFailureHandler? = null + + internal fun get(): (OidcLogoutEndpointConfigurer) -> Unit { + return { logoutEndpoint -> + logoutRequestConverter?.also { logoutEndpoint.logoutRequestConverter(it) } + logoutRequestConverters?.also { logoutEndpoint.logoutRequestConverters(it) } + authenticationProviders?.also { logoutEndpoint.authenticationProviders(it) } + logoutResponseHandler?.also { logoutEndpoint.logoutResponseHandler(it) } + errorResponseHandler?.also { logoutEndpoint.errorResponseHandler(it) } + } + } +} + diff --git a/config/src/main/kotlin/org/springframework/security/config/annotation/web/oauth2/server/authorization/OidcProviderConfigurationEndpointDsl.kt b/config/src/main/kotlin/org/springframework/security/config/annotation/web/oauth2/server/authorization/OidcProviderConfigurationEndpointDsl.kt new file mode 100644 index 00000000000..43c18d1da99 --- /dev/null +++ b/config/src/main/kotlin/org/springframework/security/config/annotation/web/oauth2/server/authorization/OidcProviderConfigurationEndpointDsl.kt @@ -0,0 +1,42 @@ +/* + * Copyright 2004-present 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.oauth2.server.authorization + +import org.springframework.security.config.annotation.web.configurers.oauth2.server.authorization.OidcProviderConfigurationEndpointConfigurer +import org.springframework.security.oauth2.server.authorization.oidc.OidcProviderConfiguration +import java.util.function.Consumer + +/** + * A Kotlin DSL to configure the OpenID Connect 1.0 Provider Configuration Endpoint using idiomatic Kotlin code. + * + * @author Mehrdad Bozorgmehr + * @since 7.0 + */ +@OAuth2AuthorizationServerSecurityMarker +class OidcProviderConfigurationEndpointDsl { + + var providerConfigurationCustomizer: Consumer? = null + + internal fun get(): (OidcProviderConfigurationEndpointConfigurer) -> Unit { + return { providerConfigurationEndpoint -> + providerConfigurationCustomizer?.also { + providerConfigurationEndpoint.providerConfigurationCustomizer(it) + } + } + } +} + diff --git a/config/src/main/kotlin/org/springframework/security/config/annotation/web/oauth2/server/authorization/OidcUserInfoEndpointDsl.kt b/config/src/main/kotlin/org/springframework/security/config/annotation/web/oauth2/server/authorization/OidcUserInfoEndpointDsl.kt new file mode 100644 index 00000000000..9efa3e2d394 --- /dev/null +++ b/config/src/main/kotlin/org/springframework/security/config/annotation/web/oauth2/server/authorization/OidcUserInfoEndpointDsl.kt @@ -0,0 +1,56 @@ +/* + * Copyright 2004-present 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.oauth2.server.authorization + +import org.springframework.security.authentication.AuthenticationProvider +import org.springframework.security.config.annotation.web.configurers.oauth2.server.authorization.OidcUserInfoEndpointConfigurer +import org.springframework.security.oauth2.core.oidc.OidcUserInfo +import org.springframework.security.oauth2.server.authorization.oidc.authentication.OidcUserInfoAuthenticationContext +import org.springframework.security.web.authentication.AuthenticationConverter +import org.springframework.security.web.authentication.AuthenticationFailureHandler +import org.springframework.security.web.authentication.AuthenticationSuccessHandler +import java.util.function.Consumer +import java.util.function.Function + +/** + * A Kotlin DSL to configure the OpenID Connect 1.0 UserInfo Endpoint using idiomatic Kotlin code. + * + * @author Mehrdad Bozorgmehr + * @since 7.0 + */ +@OAuth2AuthorizationServerSecurityMarker +class OidcUserInfoEndpointDsl { + + var userInfoRequestConverter: AuthenticationConverter? = null + var userInfoRequestConverters: Consumer>? = null + var authenticationProviders: Consumer>? = null + var userInfoResponseHandler: AuthenticationSuccessHandler? = null + var errorResponseHandler: AuthenticationFailureHandler? = null + var userInfoMapper: Function? = null + + internal fun get(): (OidcUserInfoEndpointConfigurer) -> Unit { + return { userInfoEndpoint -> + userInfoRequestConverter?.also { userInfoEndpoint.userInfoRequestConverter(it) } + userInfoRequestConverters?.also { userInfoEndpoint.userInfoRequestConverters(it) } + authenticationProviders?.also { userInfoEndpoint.authenticationProviders(it) } + userInfoResponseHandler?.also { userInfoEndpoint.userInfoResponseHandler(it) } + errorResponseHandler?.also { userInfoEndpoint.errorResponseHandler(it) } + userInfoMapper?.also { userInfoEndpoint.userInfoMapper(it) } + } + } +} + diff --git a/config/src/test/kotlin/org/springframework/security/config/annotation/web/OAuth2AuthorizationServerDslTests.kt b/config/src/test/kotlin/org/springframework/security/config/annotation/web/OAuth2AuthorizationServerDslTests.kt new file mode 100644 index 00000000000..deaa0b41190 --- /dev/null +++ b/config/src/test/kotlin/org/springframework/security/config/annotation/web/OAuth2AuthorizationServerDslTests.kt @@ -0,0 +1,161 @@ +/* + * Copyright 2004-present 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 com.nimbusds.jose.jwk.JWKSet +import com.nimbusds.jose.jwk.RSAKey +import com.nimbusds.jose.jwk.source.ImmutableJWKSet +import com.nimbusds.jose.jwk.source.JWKSource +import com.nimbusds.jose.proc.SecurityContext +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.extension.ExtendWith +import org.springframework.beans.factory.annotation.Autowired +import org.springframework.context.annotation.Bean +import org.springframework.context.annotation.Configuration +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.oauth2.core.AuthorizationGrantType +import org.springframework.security.oauth2.core.ClientAuthenticationMethod +import org.springframework.security.oauth2.jwt.JwtDecoder +import org.springframework.security.oauth2.server.authorization.client.InMemoryRegisteredClientRepository +import org.springframework.security.oauth2.server.authorization.client.RegisteredClient +import org.springframework.security.oauth2.server.authorization.client.RegisteredClientRepository +import org.springframework.security.config.annotation.web.configuration.OAuth2AuthorizationServerConfiguration +import org.springframework.security.oauth2.server.authorization.settings.AuthorizationServerSettings +import org.springframework.security.web.SecurityFilterChain +import org.springframework.test.web.servlet.MockMvc +import java.security.KeyPair +import java.security.KeyPairGenerator +import java.security.interfaces.RSAPublicKey +import java.util.UUID + +/** + * Tests for [OAuth2AuthorizationServerDsl] + * + * @author Mehrdad + */ +@ExtendWith(SpringTestContextExtension::class) +class OAuth2AuthorizationServerDslTests { + @JvmField + val spring = SpringTestContext(this) + + @Autowired + lateinit var mockMvc: MockMvc + + @Test + fun `oauth2AuthorizationServer when custom registered client repository then configuration applies`() { + this.spring.register(AuthorizationServerConfig::class.java).autowire() + } + + @Configuration + @EnableWebSecurity + open class AuthorizationServerConfig { + @Bean + open fun securityFilterChain(http: HttpSecurity): SecurityFilterChain { + http { + oauth2AuthorizationServer { + registeredClientRepository = registeredClientRepository() + } + } + return http.build() + } + + @Bean + open fun registeredClientRepository(): RegisteredClientRepository { + val registeredClient = RegisteredClient.withId("test-client") + .clientId("test-client") + .clientSecret("{noop}secret") + .clientAuthenticationMethod(ClientAuthenticationMethod.CLIENT_SECRET_BASIC) + .authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE) + .authorizationGrantType(AuthorizationGrantType.REFRESH_TOKEN) + .redirectUri("http://localhost:8080/authorized") + .build() + return InMemoryRegisteredClientRepository(registeredClient) + } + + @Bean + open fun authorizationServerSettings(): AuthorizationServerSettings { + return AuthorizationServerSettings.builder().build() + } + } + + @Test + fun `oauth2AuthorizationServer when oidc configured then oidc enabled`() { + this.spring.register(AuthorizationServerOidcConfig::class.java).autowire() + } + + @Configuration + @EnableWebSecurity + open class AuthorizationServerOidcConfig { + @Bean + open fun securityFilterChain(http: HttpSecurity): SecurityFilterChain { + http { + oauth2AuthorizationServer { + registeredClientRepository = registeredClientRepository() + oidc { + // Enable OIDC support + } + } + } + return http.build() + } + + @Bean + open fun registeredClientRepository(): RegisteredClientRepository { + val registeredClient = RegisteredClient.withId("test-client") + .clientId("test-client") + .clientSecret("{noop}secret") + .clientAuthenticationMethod(ClientAuthenticationMethod.CLIENT_SECRET_BASIC) + .authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE) + .authorizationGrantType(AuthorizationGrantType.REFRESH_TOKEN) + .redirectUri("http://localhost:8080/authorized") + .scope("openid") + .build() + return InMemoryRegisteredClientRepository(registeredClient) + } + + @Bean + open fun authorizationServerSettings(): AuthorizationServerSettings { + return AuthorizationServerSettings.builder().build() + } + + @Bean + open fun jwkSource(): JWKSource { + val keyPair = generateRsaKey() + val rsaKey = RSAKey.Builder(keyPair.public as RSAPublicKey) + .privateKey(keyPair.private) + .keyID(UUID.randomUUID().toString()) + .build() + val jwkSet = JWKSet(rsaKey) + return ImmutableJWKSet(jwkSet) + } + + @Bean + open fun jwtDecoder(jwkSource: JWKSource): JwtDecoder { + return OAuth2AuthorizationServerConfiguration.jwtDecoder(jwkSource) + } + + private fun generateRsaKey(): KeyPair { + val keyPairGenerator = KeyPairGenerator.getInstance("RSA") + keyPairGenerator.initialize(2048) + return keyPairGenerator.generateKeyPair() + } + } +} +