? = null
+ var authenticationFilter: AnonymousAuthenticationWebFilter? = null
+
+ private var disabled = false
+
+ /**
+ * Disables anonymous authentication
+ */
+ fun disable() {
+ disabled = true
+ }
+
+ internal fun get(): (ServerHttpSecurity.AnonymousSpec) -> Unit {
+ return { anonymous ->
+ key?.also { anonymous.key(key) }
+ principal?.also { anonymous.principal(principal) }
+ authorities?.also { anonymous.authorities(authorities) }
+ authenticationFilter?.also { anonymous.authenticationFilter(authenticationFilter) }
+ if (disabled) {
+ anonymous.disable()
+ }
+ }
+ }
+}
diff --git a/config/src/main/kotlin/org/springframework/security/config/web/server/ServerCacheControlDsl.kt b/config/src/main/kotlin/org/springframework/security/config/web/server/ServerCacheControlDsl.kt
new file mode 100644
index 00000000000..76899260a3e
--- /dev/null
+++ b/config/src/main/kotlin/org/springframework/security/config/web/server/ServerCacheControlDsl.kt
@@ -0,0 +1,44 @@
+/*
+ * Copyright 2002-2020 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.server
+
+/**
+ * A Kotlin DSL to configure the [ServerHttpSecurity] cache control headers using
+ * idiomatic Kotlin code.
+ *
+ * @author Eleftheria Stein
+ * @since 5.4
+ */
+@ServerSecurityMarker
+class ServerCacheControlDsl {
+ private var disabled = false
+
+ /**
+ * Disables cache control response headers
+ */
+ fun disable() {
+ disabled = true
+ }
+
+ internal fun get(): (ServerHttpSecurity.HeaderSpec.CacheSpec) -> Unit {
+ return { cacheControl ->
+ if (disabled) {
+ cacheControl.disable()
+ }
+ }
+ }
+}
diff --git a/config/src/main/kotlin/org/springframework/security/config/web/server/ServerContentSecurityPolicyDsl.kt b/config/src/main/kotlin/org/springframework/security/config/web/server/ServerContentSecurityPolicyDsl.kt
new file mode 100644
index 00000000000..53dba4d8d71
--- /dev/null
+++ b/config/src/main/kotlin/org/springframework/security/config/web/server/ServerContentSecurityPolicyDsl.kt
@@ -0,0 +1,41 @@
+/*
+ * Copyright 2002-2020 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.server
+
+/**
+ * A Kotlin DSL to configure the [ServerHttpSecurity] Content-Security-Policy header using
+ * idiomatic Kotlin code.
+ *
+ * @author Eleftheria Stein
+ * @since 5.4
+ */
+@ServerSecurityMarker
+class ServerContentSecurityPolicyDsl {
+ var policyDirectives: String? = null
+ var reportOnly: Boolean? = null
+
+ internal fun get(): (ServerHttpSecurity.HeaderSpec.ContentSecurityPolicySpec) -> Unit {
+ return { contentSecurityPolicy ->
+ policyDirectives?.also {
+ contentSecurityPolicy.policyDirectives(policyDirectives)
+ }
+ reportOnly?.also {
+ contentSecurityPolicy.reportOnly(reportOnly!!)
+ }
+ }
+ }
+}
diff --git a/config/src/main/kotlin/org/springframework/security/config/web/server/ServerContentTypeOptionsDsl.kt b/config/src/main/kotlin/org/springframework/security/config/web/server/ServerContentTypeOptionsDsl.kt
new file mode 100644
index 00000000000..3ddadb686ad
--- /dev/null
+++ b/config/src/main/kotlin/org/springframework/security/config/web/server/ServerContentTypeOptionsDsl.kt
@@ -0,0 +1,44 @@
+/*
+ * Copyright 2002-2020 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.server
+
+/**
+ * A Kotlin DSL to configure the [ServerHttpSecurity] the content type options header
+ * using idiomatic Kotlin code.
+ *
+ * @author Eleftheria Stein
+ * @since 5.4
+ */
+@ServerSecurityMarker
+class ServerContentTypeOptionsDsl {
+ private var disabled = false
+
+ /**
+ * Disables content type options response header
+ */
+ fun disable() {
+ disabled = true
+ }
+
+ internal fun get(): (ServerHttpSecurity.HeaderSpec.ContentTypeOptionsSpec) -> Unit {
+ return { contentTypeOptions ->
+ if (disabled) {
+ contentTypeOptions.disable()
+ }
+ }
+ }
+}
diff --git a/config/src/main/kotlin/org/springframework/security/config/web/server/ServerCorsDsl.kt b/config/src/main/kotlin/org/springframework/security/config/web/server/ServerCorsDsl.kt
new file mode 100644
index 00000000000..897e6a3d6cc
--- /dev/null
+++ b/config/src/main/kotlin/org/springframework/security/config/web/server/ServerCorsDsl.kt
@@ -0,0 +1,50 @@
+/*
+ * Copyright 2002-2020 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.server
+
+import org.springframework.web.cors.reactive.CorsConfigurationSource
+
+/**
+ * A Kotlin DSL to configure [ServerHttpSecurity] CORS headers using idiomatic
+ * Kotlin code.
+ *
+ * @author Eleftheria Stein
+ * @since 5.4
+ * @property configurationSource the [CorsConfigurationSource] to use.
+ */
+@ServerSecurityMarker
+class ServerCorsDsl {
+ var configurationSource: CorsConfigurationSource? = null
+
+ private var disabled = false
+
+ /**
+ * Disables CORS support within Spring Security.
+ */
+ fun disable() {
+ disabled = true
+ }
+
+ internal fun get(): (ServerHttpSecurity.CorsSpec) -> Unit {
+ return { cors ->
+ configurationSource?.also { cors.configurationSource(configurationSource) }
+ if (disabled) {
+ cors.disable()
+ }
+ }
+ }
+}
diff --git a/config/src/main/kotlin/org/springframework/security/config/web/server/ServerCsrfDsl.kt b/config/src/main/kotlin/org/springframework/security/config/web/server/ServerCsrfDsl.kt
new file mode 100644
index 00000000000..d1cdd139df2
--- /dev/null
+++ b/config/src/main/kotlin/org/springframework/security/config/web/server/ServerCsrfDsl.kt
@@ -0,0 +1,64 @@
+/*
+ * Copyright 2002-2020 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.server
+
+import org.springframework.security.web.server.authorization.ServerAccessDeniedHandler
+import org.springframework.security.web.server.csrf.CsrfWebFilter
+import org.springframework.security.web.server.csrf.ServerCsrfTokenRepository
+import org.springframework.security.web.server.util.matcher.ServerWebExchangeMatcher
+
+/**
+ * A Kotlin DSL to configure [ServerHttpSecurity] CSRF protection using idiomatic
+ * Kotlin code.
+ *
+ * @author Eleftheria Stein
+ * @since 5.4
+ * @property accessDeniedHandler the [ServerAccessDeniedHandler] used when a CSRF token is invalid.
+ * @property csrfTokenRepository the [ServerCsrfTokenRepository] used to persist the CSRF token.
+ * @property requireCsrfProtectionMatcher the [ServerWebExchangeMatcher] used to determine when CSRF protection
+ * is enabled.
+ * @property tokenFromMultipartDataEnabled if true, the [CsrfWebFilter] should try to resolve the actual CSRF
+ * token from the body of multipart data requests.
+ */
+@ServerSecurityMarker
+class ServerCsrfDsl {
+ var accessDeniedHandler: ServerAccessDeniedHandler? = null
+ var csrfTokenRepository: ServerCsrfTokenRepository? = null
+ var requireCsrfProtectionMatcher: ServerWebExchangeMatcher? = null
+ var tokenFromMultipartDataEnabled: Boolean? = null
+
+ private var disabled = false
+
+ /**
+ * Disables CSRF protection
+ */
+ fun disable() {
+ disabled = true
+ }
+
+ internal fun get(): (ServerHttpSecurity.CsrfSpec) -> Unit {
+ return { csrf ->
+ accessDeniedHandler?.also { csrf.accessDeniedHandler(accessDeniedHandler) }
+ csrfTokenRepository?.also { csrf.csrfTokenRepository(csrfTokenRepository) }
+ requireCsrfProtectionMatcher?.also { csrf.requireCsrfProtectionMatcher(requireCsrfProtectionMatcher) }
+ tokenFromMultipartDataEnabled?.also { csrf.tokenFromMultipartDataEnabled(tokenFromMultipartDataEnabled!!) }
+ if (disabled) {
+ csrf.disable()
+ }
+ }
+ }
+}
diff --git a/config/src/main/kotlin/org/springframework/security/config/web/server/ServerExceptionHandlingDsl.kt b/config/src/main/kotlin/org/springframework/security/config/web/server/ServerExceptionHandlingDsl.kt
new file mode 100644
index 00000000000..d4e4d72cd4a
--- /dev/null
+++ b/config/src/main/kotlin/org/springframework/security/config/web/server/ServerExceptionHandlingDsl.kt
@@ -0,0 +1,44 @@
+/*
+ * Copyright 2002-2020 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.server
+
+import org.springframework.security.web.server.ServerAuthenticationEntryPoint
+import org.springframework.security.web.server.authorization.ServerAccessDeniedHandler
+
+/**
+ * A Kotlin DSL to configure [ServerHttpSecurity] exception handling using idiomatic Kotlin
+ * code.
+ *
+ * @author Eleftheria Stein
+ * @since 5.4
+ * @property authenticationEntryPoint the [ServerAuthenticationEntryPoint] to use when
+ * the application request authentication
+ * @property accessDeniedHandler the [ServerAccessDeniedHandler] to use when an
+ * authenticated user does not hold a required authority
+ */
+@ServerSecurityMarker
+class ServerExceptionHandlingDsl {
+ var authenticationEntryPoint: ServerAuthenticationEntryPoint? = null
+ var accessDeniedHandler: ServerAccessDeniedHandler? = null
+
+ internal fun get(): (ServerHttpSecurity.ExceptionHandlingSpec) -> Unit {
+ return { exceptionHandling ->
+ authenticationEntryPoint?.also { exceptionHandling.authenticationEntryPoint(authenticationEntryPoint) }
+ accessDeniedHandler?.also { exceptionHandling.accessDeniedHandler(accessDeniedHandler) }
+ }
+ }
+}
diff --git a/config/src/main/kotlin/org/springframework/security/config/web/server/ServerFormLoginDsl.kt b/config/src/main/kotlin/org/springframework/security/config/web/server/ServerFormLoginDsl.kt
new file mode 100644
index 00000000000..89ccc633bd4
--- /dev/null
+++ b/config/src/main/kotlin/org/springframework/security/config/web/server/ServerFormLoginDsl.kt
@@ -0,0 +1,86 @@
+/*
+ * Copyright 2002-2020 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.server
+
+import org.springframework.security.authentication.ReactiveAuthenticationManager
+import org.springframework.security.core.Authentication
+import org.springframework.security.core.context.SecurityContext
+import org.springframework.security.web.server.ServerAuthenticationEntryPoint
+import org.springframework.security.web.server.authentication.ServerAuthenticationFailureHandler
+import org.springframework.security.web.server.authentication.ServerAuthenticationSuccessHandler
+import org.springframework.security.web.server.context.ReactorContextWebFilter
+import org.springframework.security.web.server.context.ServerSecurityContextRepository
+import org.springframework.security.web.server.util.matcher.ServerWebExchangeMatcher
+
+/**
+ * A Kotlin DSL to configure [ServerHttpSecurity] form login using idiomatic
+ * Kotlin code.
+ *
+ * @author Eleftheria Stein
+ * @since 5.4
+ * @property authenticationManager the [ReactiveAuthenticationManager] used to authenticate.
+ * @property loginPage the url to redirect to which provides a form to log in (i.e. "/login").
+ * If this is customized:
+ * - The default log in & log out page are no longer provided
+ * - The application must render a log in page at the provided URL
+ * - The application must render an authentication error page at the provided URL + "?error"
+ * - Authentication will occur for POST to the provided URL
+ * @property authenticationEntryPoint configures how to request for authentication.
+ * @property requiresAuthenticationMatcher configures when authentication is performed.
+ * @property authenticationSuccessHandler the [ServerAuthenticationSuccessHandler] used after
+ * authentication success.
+ * @property authenticationFailureHandler the [ServerAuthenticationFailureHandler] used to handle
+ * a failed authentication.
+ * @property securityContextRepository the [ServerSecurityContextRepository] used to save
+ * the [Authentication]. For the [SecurityContext] to be loaded on subsequent requests the
+ * [ReactorContextWebFilter] must be configured to be able to load the value (they are not
+ * implicitly linked).
+ */
+@ServerSecurityMarker
+class ServerFormLoginDsl {
+ var authenticationManager: ReactiveAuthenticationManager? = null
+ var loginPage: String? = null
+ var authenticationEntryPoint: ServerAuthenticationEntryPoint? = null
+ var requiresAuthenticationMatcher: ServerWebExchangeMatcher? = null
+ var authenticationSuccessHandler: ServerAuthenticationSuccessHandler? = null
+ var authenticationFailureHandler: ServerAuthenticationFailureHandler? = null
+ var securityContextRepository: ServerSecurityContextRepository? = null
+
+ private var disabled = false
+
+ /**
+ * Disables HTTP basic authentication
+ */
+ fun disable() {
+ disabled = true
+ }
+
+ internal fun get(): (ServerHttpSecurity.FormLoginSpec) -> Unit {
+ return { formLogin ->
+ authenticationManager?.also { formLogin.authenticationManager(authenticationManager) }
+ loginPage?.also { formLogin.loginPage(loginPage) }
+ authenticationEntryPoint?.also { formLogin.authenticationEntryPoint(authenticationEntryPoint) }
+ requiresAuthenticationMatcher?.also { formLogin.requiresAuthenticationMatcher(requiresAuthenticationMatcher) }
+ authenticationSuccessHandler?.also { formLogin.authenticationSuccessHandler(authenticationSuccessHandler) }
+ authenticationFailureHandler?.also { formLogin.authenticationFailureHandler(authenticationFailureHandler) }
+ securityContextRepository?.also { formLogin.securityContextRepository(securityContextRepository) }
+ if (disabled) {
+ formLogin.disable()
+ }
+ }
+ }
+}
diff --git a/config/src/main/kotlin/org/springframework/security/config/web/server/ServerFrameOptionsDsl.kt b/config/src/main/kotlin/org/springframework/security/config/web/server/ServerFrameOptionsDsl.kt
new file mode 100644
index 00000000000..cf95d55887a
--- /dev/null
+++ b/config/src/main/kotlin/org/springframework/security/config/web/server/ServerFrameOptionsDsl.kt
@@ -0,0 +1,52 @@
+/*
+ * Copyright 2002-2020 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.server
+
+import org.springframework.security.web.server.header.XFrameOptionsServerHttpHeadersWriter
+
+/**
+ * A Kotlin DSL to configure the [ServerHttpSecurity] X-Frame-Options header using
+ * idiomatic Kotlin code.
+ *
+ * @author Eleftheria Stein
+ * @since 5.4
+ * @property mode the X-Frame-Options mode to set in the response header.
+ */
+@ServerSecurityMarker
+class ServerFrameOptionsDsl {
+ var mode: XFrameOptionsServerHttpHeadersWriter.Mode? = null
+
+ private var disabled = false
+
+ /**
+ * Disables the X-Frame-Options response header
+ */
+ fun disable() {
+ disabled = true
+ }
+
+ internal fun get(): (ServerHttpSecurity.HeaderSpec.FrameOptionsSpec) -> Unit {
+ return { frameOptions ->
+ mode?.also {
+ frameOptions.mode(mode)
+ }
+ if (disabled) {
+ frameOptions.disable()
+ }
+ }
+ }
+}
diff --git a/config/src/main/kotlin/org/springframework/security/config/web/server/ServerHeadersDsl.kt b/config/src/main/kotlin/org/springframework/security/config/web/server/ServerHeadersDsl.kt
new file mode 100644
index 00000000000..b6c435c2ffe
--- /dev/null
+++ b/config/src/main/kotlin/org/springframework/security/config/web/server/ServerHeadersDsl.kt
@@ -0,0 +1,181 @@
+/*
+ * Copyright 2002-2020 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.server
+
+import org.springframework.security.web.server.header.*
+
+/**
+ * A Kotlin DSL to configure [ServerHttpSecurity] headers using idiomatic Kotlin code.
+ *
+ * @author Eleftheria Stein
+ * @since 5.4
+ */
+@ServerSecurityMarker
+class ServerHeadersDsl {
+ private var contentTypeOptions: ((ServerHttpSecurity.HeaderSpec.ContentTypeOptionsSpec) -> Unit)? = null
+ private var xssProtection: ((ServerHttpSecurity.HeaderSpec.XssProtectionSpec) -> Unit)? = null
+ private var cacheControl: ((ServerHttpSecurity.HeaderSpec.CacheSpec) -> Unit)? = null
+ private var hsts: ((ServerHttpSecurity.HeaderSpec.HstsSpec) -> Unit)? = null
+ private var frameOptions: ((ServerHttpSecurity.HeaderSpec.FrameOptionsSpec) -> Unit)? = null
+ private var contentSecurityPolicy: ((ServerHttpSecurity.HeaderSpec.ContentSecurityPolicySpec) -> Unit)? = null
+ private var referrerPolicy: ((ServerHttpSecurity.HeaderSpec.ReferrerPolicySpec) -> Unit)? = null
+ private var featurePolicyDirectives: String? = null
+
+ private var disabled = false
+
+ /**
+ * Configures the [ContentTypeOptionsServerHttpHeadersWriter] which inserts the X-Content-Type-Options header
+ *
+ * @param contentTypeOptionsConfig the customization to apply to the header
+ */
+ fun contentTypeOptions(contentTypeOptionsConfig: ServerContentTypeOptionsDsl.() -> Unit) {
+ this.contentTypeOptions = ServerContentTypeOptionsDsl().apply(contentTypeOptionsConfig).get()
+ }
+
+ /**
+ * Note this is not comprehensive XSS protection!
+ *
+ *
+ * Allows customizing the [XXssProtectionServerHttpHeadersWriter] which adds the X-XSS-Protection header
+ *
+ *
+ * @param xssProtectionConfig the customization to apply to the header
+ */
+ fun xssProtection(xssProtectionConfig: ServerXssProtectionDsl.() -> Unit) {
+ this.xssProtection = ServerXssProtectionDsl().apply(xssProtectionConfig).get()
+ }
+
+ /**
+ * Allows customizing the [CacheControlServerHttpHeadersWriter]. Specifically it adds
+ * the following headers:
+ *
+ * Cache-Control: no-cache, no-store, max-age=0, must-revalidate
+ * Pragma: no-cache
+ * Expires: 0
+ *
+ *
+ * @param cacheControlConfig the customization to apply to the headers
+ */
+ fun cache(cacheControlConfig: ServerCacheControlDsl.() -> Unit) {
+ this.cacheControl = ServerCacheControlDsl().apply(cacheControlConfig).get()
+ }
+
+ /**
+ * Allows customizing the [StrictTransportSecurityServerHttpHeadersWriter] which provides support
+ * for HTTP Strict Transport Security
+ * (HSTS) .
+ *
+ * @param hstsConfig the customization to apply to the header
+ */
+ fun hsts(hstsConfig: ServerHttpStrictTransportSecurityDsl.() -> Unit) {
+ this.hsts = ServerHttpStrictTransportSecurityDsl().apply(hstsConfig).get()
+ }
+
+ /**
+ * Allows customizing the [XFrameOptionsServerHttpHeadersWriter] which add the X-Frame-Options
+ * header.
+ *
+ * @param frameOptionsConfig the customization to apply to the header
+ */
+ fun frameOptions(frameOptionsConfig: ServerFrameOptionsDsl.() -> Unit) {
+ this.frameOptions = ServerFrameOptionsDsl().apply(frameOptionsConfig).get()
+ }
+
+ /**
+ * Allows configuration for Content Security Policy (CSP) Level 2 .
+ *
+ * @param contentSecurityPolicyConfig the customization to apply to the header
+ */
+ fun contentSecurityPolicy(contentSecurityPolicyConfig: ServerContentSecurityPolicyDsl.() -> Unit) {
+ this.contentSecurityPolicy = ServerContentSecurityPolicyDsl().apply(contentSecurityPolicyConfig).get()
+ }
+
+ /**
+ * Allows configuration for Referrer Policy .
+ *
+ *
+ * Configuration is provided to the [ReferrerPolicyServerHttpHeadersWriter] which support the writing
+ * of the header as detailed in the W3C Technical Report:
+ *
+ *
+ *
+ * @param referrerPolicyConfig the customization to apply to the header
+ */
+ fun referrerPolicy(referrerPolicyConfig: ServerReferrerPolicyDsl.() -> Unit) {
+ this.referrerPolicy = ServerReferrerPolicyDsl().apply(referrerPolicyConfig).get()
+ }
+
+ /**
+ * Allows configuration for Feature
+ * Policy .
+ *
+ *
+ * Calling this method automatically enables (includes) the Feature-Policy
+ * header in the response using the supplied policy directive(s).
+ *
+ *
+ * @param policyDirectives policyDirectives the security policy directive(s)
+ */
+ fun featurePolicy(policyDirectives: String) {
+ this.featurePolicyDirectives = policyDirectives
+ }
+
+ /**
+ * Disables HTTP response headers.
+ */
+ fun disable() {
+ disabled = true
+ }
+
+ internal fun get(): (ServerHttpSecurity.HeaderSpec) -> Unit {
+ return { headers ->
+ contentTypeOptions?.also {
+ headers.contentTypeOptions(contentTypeOptions)
+ }
+ xssProtection?.also {
+ headers.xssProtection(xssProtection)
+ }
+ cacheControl?.also {
+ headers.cache(cacheControl)
+ }
+ hsts?.also {
+ headers.hsts(hsts)
+ }
+ frameOptions?.also {
+ headers.frameOptions(frameOptions)
+ }
+ contentSecurityPolicy?.also {
+ headers.contentSecurityPolicy(contentSecurityPolicy)
+ }
+ featurePolicyDirectives?.also {
+ headers.featurePolicy(featurePolicyDirectives)
+ }
+ referrerPolicy?.also {
+ headers.referrerPolicy(referrerPolicy)
+ }
+ if (disabled) {
+ headers.disable()
+ }
+ }
+ }
+}
diff --git a/config/src/main/kotlin/org/springframework/security/config/web/server/ServerHttpBasicDsl.kt b/config/src/main/kotlin/org/springframework/security/config/web/server/ServerHttpBasicDsl.kt
new file mode 100644
index 00000000000..91b157c2644
--- /dev/null
+++ b/config/src/main/kotlin/org/springframework/security/config/web/server/ServerHttpBasicDsl.kt
@@ -0,0 +1,66 @@
+/*
+ * Copyright 2002-2020 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.server
+
+import org.springframework.security.authentication.ReactiveAuthenticationManager
+import org.springframework.security.core.Authentication
+import org.springframework.security.core.context.SecurityContext
+import org.springframework.security.web.authentication.www.BasicAuthenticationFilter
+import org.springframework.security.web.server.ServerAuthenticationEntryPoint
+import org.springframework.security.web.server.context.ReactorContextWebFilter
+import org.springframework.security.web.server.context.ServerSecurityContextRepository
+
+/**
+ * A Kotlin DSL to configure [ServerHttpSecurity] basic authorization using idiomatic
+ * Kotlin code.
+ *
+ * @author Eleftheria Stein
+ * @since 5.4
+ * @property authenticationManager the [ReactiveAuthenticationManager] used to authenticate.
+ * @property securityContextRepository the [ServerSecurityContextRepository] used to save
+ * the [Authentication]. For the [SecurityContext] to be loaded on subsequent requests the
+ * [ReactorContextWebFilter] must be configured to be able to load the value (they are not
+ * implicitly linked).
+ * @property authenticationEntryPoint the [ServerAuthenticationEntryPoint] to be
+ * populated on [BasicAuthenticationFilter] in the event that authentication fails.
+ */
+@ServerSecurityMarker
+class ServerHttpBasicDsl {
+ var authenticationManager: ReactiveAuthenticationManager? = null
+ var securityContextRepository: ServerSecurityContextRepository? = null
+ var authenticationEntryPoint: ServerAuthenticationEntryPoint? = null
+
+ private var disabled = false
+
+ /**
+ * Disables HTTP basic authentication
+ */
+ fun disable() {
+ disabled = true
+ }
+
+ internal fun get(): (ServerHttpSecurity.HttpBasicSpec) -> Unit {
+ return { httpBasic ->
+ authenticationManager?.also { httpBasic.authenticationManager(authenticationManager) }
+ securityContextRepository?.also { httpBasic.securityContextRepository(securityContextRepository) }
+ authenticationEntryPoint?.also { httpBasic.authenticationEntryPoint(authenticationEntryPoint) }
+ if (disabled) {
+ httpBasic.disable()
+ }
+ }
+ }
+}
diff --git a/config/src/main/kotlin/org/springframework/security/config/web/server/ServerHttpSecurityDsl.kt b/config/src/main/kotlin/org/springframework/security/config/web/server/ServerHttpSecurityDsl.kt
new file mode 100644
index 00000000000..8f09f5589a2
--- /dev/null
+++ b/config/src/main/kotlin/org/springframework/security/config/web/server/ServerHttpSecurityDsl.kt
@@ -0,0 +1,529 @@
+/*
+ * Copyright 2002-2020 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.server
+
+import org.springframework.security.oauth2.client.registration.ReactiveClientRegistrationRepository
+import org.springframework.security.web.server.SecurityWebFilterChain
+import org.springframework.security.web.server.util.matcher.ServerWebExchangeMatcher
+import org.springframework.web.server.ServerWebExchange
+
+/**
+ * Configures [ServerHttpSecurity] using a [ServerHttpSecurity Kotlin DSL][ServerHttpSecurityDsl].
+ *
+ * Example:
+ *
+ * ```
+ * @EnableWebFluxSecurity
+ * class SecurityConfig {
+ *
+ * @Bean
+ * fun springWebFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain {
+ * return http {
+ * authorizeExchange {
+ * exchange("/public", permitAll)
+ * exchange(anyExchange, authenticated)
+ * }
+ * }
+ * }
+ * }
+ * ```
+ *
+ * @author Eleftheria Stein
+ * @since 5.4
+ * @param httpConfiguration the configurations to apply to [ServerHttpSecurity]
+ */
+operator fun ServerHttpSecurity.invoke(httpConfiguration: ServerHttpSecurityDsl.() -> Unit): SecurityWebFilterChain =
+ ServerHttpSecurityDsl(this, httpConfiguration).build()
+
+/**
+ * A [ServerHttpSecurity] Kotlin DSL created by [`http { }`][invoke]
+ * in order to configure [ServerHttpSecurity] using idiomatic Kotlin code.
+ *
+ * @author Eleftheria Stein
+ * @since 5.4
+ * @param init the configurations to apply to the provided [ServerHttpSecurity]
+ */
+@ServerSecurityMarker
+class ServerHttpSecurityDsl(private val http: ServerHttpSecurity, private val init: ServerHttpSecurityDsl.() -> Unit) {
+
+ /**
+ * Allows configuring the [ServerHttpSecurity] to only be invoked when matching the
+ * provided [ServerWebExchangeMatcher].
+ *
+ * Example:
+ *
+ * ```
+ * @EnableWebFluxSecurity
+ * class SecurityConfig {
+ *
+ * @Bean
+ * fun springWebFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain {
+ * return http {
+ * securityMatcher(PathPatternParserServerWebExchangeMatcher("/api/**"))
+ * formLogin {
+ * loginPage = "/log-in"
+ * }
+ * }
+ * }
+ * }
+ * ```
+ *
+ * @param securityMatcher a [ServerWebExchangeMatcher] used to determine whether this
+ * configuration should be invoked.
+ */
+ fun securityMatcher(securityMatcher: ServerWebExchangeMatcher) {
+ this.http.securityMatcher(securityMatcher)
+ }
+
+ /**
+ * Enables form based authentication.
+ *
+ * Example:
+ *
+ * ```
+ * @EnableWebFluxSecurity
+ * class SecurityConfig {
+ *
+ * @Bean
+ * fun springWebFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain {
+ * return http {
+ * formLogin {
+ * loginPage = "/log-in"
+ * }
+ * }
+ * }
+ * }
+ * ```
+ *
+ * @param formLoginConfiguration custom configuration to apply to the form based
+ * authentication
+ * @see [ServerFormLoginDsl]
+ */
+ fun formLogin(formLoginConfiguration: ServerFormLoginDsl.() -> Unit) {
+ val formLoginCustomizer = ServerFormLoginDsl().apply(formLoginConfiguration).get()
+ this.http.formLogin(formLoginCustomizer)
+ }
+
+ /**
+ * Allows restricting access based upon the [ServerWebExchange]
+ *
+ * Example:
+ *
+ * ```
+ * @EnableWebFluxSecurity
+ * class SecurityConfig {
+ *
+ * @Bean
+ * fun springWebFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain {
+ * return http {
+ * authorizeExchange {
+ * exchange("/public", permitAll)
+ * exchange(anyExchange, authenticated)
+ * }
+ * }
+ * }
+ * }
+ * ```
+ *
+ * @param authorizeExchangeConfiguration custom configuration that specifies
+ * access for an exchange
+ * @see [AuthorizeExchangeDsl]
+ */
+ fun authorizeExchange(authorizeExchangeConfiguration: AuthorizeExchangeDsl.() -> Unit) {
+ val authorizeExchangeCustomizer = AuthorizeExchangeDsl().apply(authorizeExchangeConfiguration).get()
+ this.http.authorizeExchange(authorizeExchangeCustomizer)
+ }
+
+ /**
+ * Enables HTTP basic authentication.
+ *
+ * Example:
+ *
+ * ```
+ * @EnableWebFluxSecurity
+ * class SecurityConfig {
+ *
+ * @Bean
+ * fun springWebFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain {
+ * return http {
+ * httpBasic { }
+ * }
+ * }
+ * }
+ * ```
+ *
+ * @param httpBasicConfiguration custom configuration to be applied to the
+ * HTTP basic authentication
+ * @see [ServerHttpBasicDsl]
+ */
+ fun httpBasic(httpBasicConfiguration: ServerHttpBasicDsl.() -> Unit) {
+ val httpBasicCustomizer = ServerHttpBasicDsl().apply(httpBasicConfiguration).get()
+ this.http.httpBasic(httpBasicCustomizer)
+ }
+
+ /**
+ * Allows configuring response headers.
+ *
+ * Example:
+ *
+ * ```
+ * @EnableWebFluxSecurity
+ * class SecurityConfig {
+ *
+ * @Bean
+ * fun springWebFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain {
+ * return http {
+ * headers {
+ * referrerPolicy {
+ * policy = ReferrerPolicy.SAME_ORIGIN
+ * }
+ * frameOptions {
+ * mode = Mode.DENY
+ * }
+ * }
+ * }
+ * }
+ * }
+ * ```
+ *
+ * @param headersConfiguration custom configuration to be applied to the
+ * response headers
+ * @see [ServerHeadersDsl]
+ */
+ fun headers(headersConfiguration: ServerHeadersDsl.() -> Unit) {
+ val headersCustomizer = ServerHeadersDsl().apply(headersConfiguration).get()
+ this.http.headers(headersCustomizer)
+ }
+
+ /**
+ * Allows configuring CORS.
+ *
+ * Example:
+ *
+ * ```
+ * @EnableWebFluxSecurity
+ * class SecurityConfig {
+ *
+ * @Bean
+ * fun springWebFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain {
+ * return http {
+ * cors {
+ * configurationSource = customConfigurationSource
+ * }
+ * }
+ * }
+ * }
+ * ```
+ *
+ * @param corsConfiguration custom configuration to be applied to the
+ * CORS headers
+ * @see [ServerCorsDsl]
+ */
+ fun cors(corsConfiguration: ServerCorsDsl.() -> Unit) {
+ val corsCustomizer = ServerCorsDsl().apply(corsConfiguration).get()
+ this.http.cors(corsCustomizer)
+ }
+
+ /**
+ * Allows configuring HTTPS redirection rules.
+ *
+ * Example:
+ *
+ * ```
+ * @EnableWebFluxSecurity
+ * class SecurityConfig {
+ *
+ * @Bean
+ * fun springWebFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain {
+ * return http {
+ * redirectToHttps {
+ * httpsRedirectWhen {
+ * it.request.headers.containsKey("X-Requires-Https")
+ * }
+ * }
+ * }
+ * }
+ * }
+ * ```
+ *
+ * @param httpsRedirectConfiguration custom configuration for the HTTPS redirect
+ * rules.
+ * @see [ServerHttpsRedirectDsl]
+ */
+ fun redirectToHttps(httpsRedirectConfiguration: ServerHttpsRedirectDsl.() -> Unit) {
+ val httpsRedirectCustomizer = ServerHttpsRedirectDsl().apply(httpsRedirectConfiguration).get()
+ this.http.redirectToHttps(httpsRedirectCustomizer)
+ }
+
+ /**
+ * Allows configuring exception handling.
+ *
+ * Example:
+ *
+ * ```
+ * @EnableWebFluxSecurity
+ * class SecurityConfig {
+ *
+ * @Bean
+ * fun springWebFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain {
+ * return http {
+ * exceptionHandling {
+ * authenticationEntryPoint = RedirectServerAuthenticationEntryPoint("/auth")
+ * }
+ * }
+ * }
+ * }
+ * ```
+ *
+ * @param exceptionHandlingConfiguration custom configuration to apply to
+ * exception handling
+ * @see [ServerExceptionHandlingDsl]
+ */
+ fun exceptionHandling(exceptionHandlingConfiguration: ServerExceptionHandlingDsl.() -> Unit) {
+ val exceptionHandlingCustomizer = ServerExceptionHandlingDsl().apply(exceptionHandlingConfiguration).get()
+ this.http.exceptionHandling(exceptionHandlingCustomizer)
+ }
+
+ /**
+ * Adds X509 based pre authentication to an application using a certificate provided by a client.
+ *
+ * Example:
+ *
+ * ```
+ * @EnableWebFluxSecurity
+ * class SecurityConfig {
+ *
+ * @Bean
+ * fun springWebFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain {
+ * return http {
+ * x509 { }
+ * }
+ * }
+ * }
+ * ```
+ *
+ * @param x509Configuration custom configuration to apply to the X509 based pre authentication
+ * @see [ServerX509Dsl]
+ */
+ fun x509(x509Configuration: ServerX509Dsl.() -> Unit) {
+ val x509Customizer = ServerX509Dsl().apply(x509Configuration).get()
+ this.http.x509(x509Customizer)
+ }
+
+ /**
+ * Allows configuring request cache which is used when a flow is interrupted (i.e. due to requesting credentials)
+ * so that the request can be replayed after authentication.
+ *
+ * Example:
+ *
+ * ```
+ * @EnableWebFluxSecurity
+ * class SecurityConfig {
+ *
+ * @Bean
+ * fun springWebFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain {
+ * return http {
+ * requestCache { }
+ * }
+ * }
+ * }
+ * ```
+ *
+ * @param requestCacheConfiguration custom configuration to apply to the request cache
+ * @see [ServerRequestCacheDsl]
+ */
+ fun requestCache(requestCacheConfiguration: ServerRequestCacheDsl.() -> Unit) {
+ val requestCacheCustomizer = ServerRequestCacheDsl().apply(requestCacheConfiguration).get()
+ this.http.requestCache(requestCacheCustomizer)
+ }
+
+ /**
+ * Enables CSRF protection.
+ *
+ * Example:
+ *
+ * ```
+ * @EnableWebFluxSecurity
+ * class SecurityConfig {
+ *
+ * @Bean
+ * fun springWebFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain {
+ * return http {
+ * csrf { }
+ * }
+ * }
+ * }
+ * ```
+ *
+ * @param csrfConfiguration custom configuration to apply to the CSRF protection
+ * @see [ServerCsrfDsl]
+ */
+ fun csrf(csrfConfiguration: ServerCsrfDsl.() -> Unit) {
+ val csrfCustomizer = ServerCsrfDsl().apply(csrfConfiguration).get()
+ this.http.csrf(csrfCustomizer)
+ }
+
+ /**
+ * Provides logout support.
+ *
+ * Example:
+ *
+ * ```
+ * @EnableWebFluxSecurity
+ * class SecurityConfig {
+ *
+ * @Bean
+ * fun springWebFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain {
+ * return http {
+ * logout {
+ * logoutUrl = "/sign-out"
+ * }
+ * }
+ * }
+ * }
+ * ```
+ *
+ * @param logoutConfiguration custom configuration to apply to logout
+ * @see [ServerLogoutDsl]
+ */
+ fun logout(logoutConfiguration: ServerLogoutDsl.() -> Unit) {
+ val logoutCustomizer = ServerLogoutDsl().apply(logoutConfiguration).get()
+ this.http.logout(logoutCustomizer)
+ }
+
+ /**
+ * Enables and configures anonymous authentication.
+ *
+ * Example:
+ *
+ * ```
+ * @EnableWebFluxSecurity
+ * class SecurityConfig {
+ *
+ * @Bean
+ * fun springWebFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain {
+ * return http {
+ * anonymous {
+ * authorities = listOf(SimpleGrantedAuthority("ROLE_ANON"))
+ * }
+ * }
+ * }
+ * }
+ * ```
+ *
+ * @param anonymousConfiguration custom configuration to apply to anonymous authentication
+ * @see [ServerAnonymousDsl]
+ */
+ fun anonymous(anonymousConfiguration: ServerAnonymousDsl.() -> Unit) {
+ val anonymousCustomizer = ServerAnonymousDsl().apply(anonymousConfiguration).get()
+ this.http.anonymous(anonymousCustomizer)
+ }
+
+ /**
+ * Configures authentication support using an OAuth 2.0 and/or OpenID Connect 1.0 Provider.
+ * A [ReactiveClientRegistrationRepository] is required and must be registered as a Bean or
+ * configured via [ServerOAuth2LoginDsl.clientRegistrationRepository].
+ *
+ * Example:
+ *
+ * ```
+ * @EnableWebFluxSecurity
+ * class SecurityConfig {
+ *
+ * @Bean
+ * fun springWebFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain {
+ * return http {
+ * oauth2Login {
+ * clientRegistrationRepository = getClientRegistrationRepository()
+ * }
+ * }
+ * }
+ * }
+ * ```
+ *
+ * @param oauth2LoginConfiguration custom configuration to configure the OAuth 2.0 Login
+ * @see [ServerOAuth2LoginDsl]
+ */
+ fun oauth2Login(oauth2LoginConfiguration: ServerOAuth2LoginDsl.() -> Unit) {
+ val oauth2LoginCustomizer = ServerOAuth2LoginDsl().apply(oauth2LoginConfiguration).get()
+ this.http.oauth2Login(oauth2LoginCustomizer)
+ }
+
+ /**
+ * Configures OAuth2 client support.
+ *
+ * Example:
+ *
+ * ```
+ * @EnableWebFluxSecurity
+ * class SecurityConfig {
+ *
+ * @Bean
+ * fun springWebFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain {
+ * return http {
+ * oauth2Client {
+ * clientRegistrationRepository = getClientRegistrationRepository()
+ * }
+ * }
+ * }
+ * }
+ * ```
+ *
+ * @param oauth2ClientConfiguration custom configuration to configure the OAuth 2.0 client
+ * @see [ServerOAuth2ClientDsl]
+ */
+ fun oauth2Client(oauth2ClientConfiguration: ServerOAuth2ClientDsl.() -> Unit) {
+ val oauth2ClientCustomizer = ServerOAuth2ClientDsl().apply(oauth2ClientConfiguration).get()
+ this.http.oauth2Client(oauth2ClientCustomizer)
+ }
+
+ /**
+ * Configures OAuth2 resource server support.
+ *
+ * Example:
+ *
+ * ```
+ * @EnableWebFluxSecurity
+ * class SecurityConfig {
+ *
+ * @Bean
+ * fun springWebFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain {
+ * return http {
+ * oauth2ResourceServer {
+ * jwt { }
+ * }
+ * }
+ * }
+ * }
+ * ```
+ *
+ * @param oauth2ResourceServerConfiguration custom configuration to configure the OAuth 2.0 resource server
+ * @see [ServerOAuth2ResourceServerDsl]
+ */
+ fun oauth2ResourceServer(oauth2ResourceServerConfiguration: ServerOAuth2ResourceServerDsl.() -> Unit) {
+ val oauth2ResourceServerCustomizer = ServerOAuth2ResourceServerDsl().apply(oauth2ResourceServerConfiguration).get()
+ this.http.oauth2ResourceServer(oauth2ResourceServerCustomizer)
+ }
+
+ /**
+ * Apply all configurations to the provided [ServerHttpSecurity]
+ */
+ internal fun build(): SecurityWebFilterChain {
+ init()
+ return this.http.build()
+ }
+}
diff --git a/config/src/main/kotlin/org/springframework/security/config/web/server/ServerHttpStrictTransportSecurityDsl.kt b/config/src/main/kotlin/org/springframework/security/config/web/server/ServerHttpStrictTransportSecurityDsl.kt
new file mode 100644
index 00000000000..cb77fded423
--- /dev/null
+++ b/config/src/main/kotlin/org/springframework/security/config/web/server/ServerHttpStrictTransportSecurityDsl.kt
@@ -0,0 +1,57 @@
+/*
+ * Copyright 2002-2020 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.server
+
+import java.time.Duration
+
+/**
+ * A Kotlin DSL to configure the [ServerHttpSecurity] HTTP Strict Transport Security
+ * header using idiomatic Kotlin code.
+ *
+ * @author Eleftheria Stein
+ * @since 5.4
+ * @property maxAge he value for the max-age directive of the Strict-Transport-Security
+ * header.
+ * @property includeSubdomains if true, subdomains should be considered HSTS Hosts too.
+ * @property preload if true, preload will be included in HSTS Header.
+ */
+@ServerSecurityMarker
+class ServerHttpStrictTransportSecurityDsl {
+ var maxAge: Duration? = null
+ var includeSubdomains: Boolean? = null
+ var preload: Boolean? = null
+
+ private var disabled = false
+
+ /**
+ * Disables the X-Frame-Options response header
+ */
+ fun disable() {
+ disabled = true
+ }
+
+ internal fun get(): (ServerHttpSecurity.HeaderSpec.HstsSpec) -> Unit {
+ return { hsts ->
+ maxAge?.also { hsts.maxAge(maxAge) }
+ includeSubdomains?.also { hsts.includeSubdomains(includeSubdomains!!) }
+ preload?.also { hsts.preload(preload!!) }
+ if (disabled) {
+ hsts.disable()
+ }
+ }
+ }
+}
diff --git a/config/src/main/kotlin/org/springframework/security/config/web/server/ServerHttpsRedirectDsl.kt b/config/src/main/kotlin/org/springframework/security/config/web/server/ServerHttpsRedirectDsl.kt
new file mode 100644
index 00000000000..4cebc72fe34
--- /dev/null
+++ b/config/src/main/kotlin/org/springframework/security/config/web/server/ServerHttpsRedirectDsl.kt
@@ -0,0 +1,71 @@
+/*
+ * Copyright 2002-2020 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.server
+
+import org.springframework.security.web.PortMapper
+import org.springframework.security.web.server.util.matcher.ServerWebExchangeMatcher
+import org.springframework.web.server.ServerWebExchange
+
+/**
+ * A Kotlin DSL to configure [ServerHttpSecurity] HTTPS redirection rules using idiomatic
+ * Kotlin code.
+ *
+ * @author Eleftheria Stein
+ * @since 5.4
+ * @property portMapper the [PortMapper] that specifies a custom HTTPS port to redirect to.
+ */
+@ServerSecurityMarker
+class ServerHttpsRedirectDsl {
+ var portMapper: PortMapper? = null
+
+ private var redirectMatchers: Array? = null
+ private var redirectMatcherFunction: ((ServerWebExchange) -> Boolean)? = null
+
+ /**
+ * Configures when this filter should redirect to https.
+ * If invoked multiple times, whether a matcher or a function is provided, only the
+ * last redirect rule will apply and all previous rules will be overridden.
+ *
+ * @param redirectMatchers the list of conditions that, when any are met, the
+ * filter should redirect to https.
+ */
+ fun httpsRedirectWhen(vararg redirectMatchers: ServerWebExchangeMatcher) {
+ this.redirectMatcherFunction = null
+ this.redirectMatchers = redirectMatchers
+ }
+
+ /**
+ * Configures when this filter should redirect to https.
+ * If invoked multiple times, whether a matcher or a function is provided, only the
+ * last redirect rule will apply and all previous rules will be overridden.
+ *
+ * @param redirectMatcherFunction the condition in which the filter should redirect to
+ * https.
+ */
+ fun httpsRedirectWhen(redirectMatcherFunction: (ServerWebExchange) -> Boolean) {
+ this.redirectMatchers = null
+ this.redirectMatcherFunction = redirectMatcherFunction
+ }
+
+ internal fun get(): (ServerHttpSecurity.HttpsRedirectSpec) -> Unit {
+ return { httpsRedirect ->
+ portMapper?.also { httpsRedirect.portMapper(portMapper) }
+ redirectMatchers?.also { httpsRedirect.httpsRedirectWhen(*redirectMatchers!!) }
+ redirectMatcherFunction?.also { httpsRedirect.httpsRedirectWhen(redirectMatcherFunction) }
+ }
+ }
+}
diff --git a/config/src/main/kotlin/org/springframework/security/config/web/server/ServerJwtDsl.kt b/config/src/main/kotlin/org/springframework/security/config/web/server/ServerJwtDsl.kt
new file mode 100644
index 00000000000..0ba0501c66a
--- /dev/null
+++ b/config/src/main/kotlin/org/springframework/security/config/web/server/ServerJwtDsl.kt
@@ -0,0 +1,82 @@
+/*
+ * Copyright 2002-2020 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.server
+
+import org.springframework.core.convert.converter.Converter
+import org.springframework.security.authentication.AbstractAuthenticationToken
+import org.springframework.security.authentication.ReactiveAuthenticationManager
+import org.springframework.security.core.Authentication
+import org.springframework.security.oauth2.jwt.Jwt
+import org.springframework.security.oauth2.jwt.ReactiveJwtDecoder
+import reactor.core.publisher.Mono
+import java.security.interfaces.RSAPublicKey
+
+/**
+ * A Kotlin DSL to configure [ServerHttpSecurity] JWT Resource Server support using idiomatic Kotlin code.
+ *
+ * @author Eleftheria Stein
+ * @since 5.4
+ * @property authenticationManager the [ReactiveAuthenticationManager] used to determine if the provided
+ * [Authentication] can be authenticated.
+ * @property jwtAuthenticationConverter the [Converter] to use for converting a [Jwt] into an
+ * [AbstractAuthenticationToken].
+ * @property jwtDecoder the [ReactiveJwtDecoder] to use.
+ * @property publicKey configures a [ReactiveJwtDecoder] that leverages the provided [RSAPublicKey]
+ * @property jwkSetUri configures a [ReactiveJwtDecoder] using a
+ * JSON Web Key (JWK) URL
+ */
+@ServerSecurityMarker
+class ServerJwtDsl {
+ private var _jwtDecoder: ReactiveJwtDecoder? = null
+ private var _publicKey: RSAPublicKey? = null
+ private var _jwkSetUri: String? = null
+
+ var authenticationManager: ReactiveAuthenticationManager? = null
+ var jwtAuthenticationConverter: Converter>? = null
+
+ var jwtDecoder: ReactiveJwtDecoder?
+ get() = _jwtDecoder
+ set(value) {
+ _jwtDecoder = value
+ _publicKey = null
+ _jwkSetUri = null
+ }
+ var publicKey: RSAPublicKey?
+ get() = _publicKey
+ set(value) {
+ _publicKey = value
+ _jwtDecoder = null
+ _jwkSetUri = null
+ }
+ var jwkSetUri: String?
+ get() = _jwkSetUri
+ set(value) {
+ _jwkSetUri = value
+ _jwtDecoder = null
+ _publicKey = null
+ }
+
+ internal fun get(): (ServerHttpSecurity.OAuth2ResourceServerSpec.JwtSpec) -> Unit {
+ return { jwt ->
+ authenticationManager?.also { jwt.authenticationManager(authenticationManager) }
+ jwtAuthenticationConverter?.also { jwt.jwtAuthenticationConverter(jwtAuthenticationConverter) }
+ jwtDecoder?.also { jwt.jwtDecoder(jwtDecoder) }
+ publicKey?.also { jwt.publicKey(publicKey) }
+ jwkSetUri?.also { jwt.jwkSetUri(jwkSetUri) }
+ }
+ }
+}
diff --git a/config/src/main/kotlin/org/springframework/security/config/web/server/ServerLogoutDsl.kt b/config/src/main/kotlin/org/springframework/security/config/web/server/ServerLogoutDsl.kt
new file mode 100644
index 00000000000..021fb770d7f
--- /dev/null
+++ b/config/src/main/kotlin/org/springframework/security/config/web/server/ServerLogoutDsl.kt
@@ -0,0 +1,62 @@
+/*
+ * Copyright 2002-2020 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.server
+
+import org.springframework.security.web.server.authentication.logout.ServerLogoutHandler
+import org.springframework.security.web.server.authentication.logout.ServerLogoutSuccessHandler
+import org.springframework.security.web.server.util.matcher.ServerWebExchangeMatcher
+
+/**
+ * A Kotlin DSL to configure [ServerHttpSecurity] logout support using idiomatic Kotlin
+ * code.
+ *
+ * @author Eleftheria Stein
+ * @since 5.4
+ * @property logoutHandler a [ServerLogoutHandler] that is invoked when logout occurs.
+ * @property logoutUrl the URL that triggers logout to occur.
+ * @property requiresLogout the [ServerWebExchangeMatcher] that triggers logout to occur.
+ * @property logoutSuccessHandler the [ServerLogoutSuccessHandler] to use after logout has
+ * occurred.
+ */
+@ServerSecurityMarker
+class ServerLogoutDsl {
+ var logoutHandler: ServerLogoutHandler? = null
+ var logoutUrl: String? = null
+ var requiresLogout: ServerWebExchangeMatcher? = null
+ var logoutSuccessHandler: ServerLogoutSuccessHandler? = null
+
+ private var disabled = false
+
+ /**
+ * Disables logout
+ */
+ fun disable() {
+ disabled = true
+ }
+
+ internal fun get(): (ServerHttpSecurity.LogoutSpec) -> Unit {
+ return { logout ->
+ logoutHandler?.also { logout.logoutHandler(logoutHandler) }
+ logoutUrl?.also { logout.logoutUrl(logoutUrl) }
+ requiresLogout?.also { logout.requiresLogout(requiresLogout) }
+ logoutSuccessHandler?.also { logout.logoutSuccessHandler(logoutSuccessHandler) }
+ if (disabled) {
+ logout.disable()
+ }
+ }
+ }
+}
diff --git a/config/src/main/kotlin/org/springframework/security/config/web/server/ServerOAuth2ClientDsl.kt b/config/src/main/kotlin/org/springframework/security/config/web/server/ServerOAuth2ClientDsl.kt
new file mode 100644
index 00000000000..6751d242963
--- /dev/null
+++ b/config/src/main/kotlin/org/springframework/security/config/web/server/ServerOAuth2ClientDsl.kt
@@ -0,0 +1,58 @@
+/*
+ * Copyright 2002-2020 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.server
+
+import org.springframework.security.authentication.ReactiveAuthenticationManager
+import org.springframework.security.core.Authentication
+import org.springframework.security.oauth2.client.registration.ReactiveClientRegistrationRepository
+import org.springframework.security.oauth2.client.web.server.ServerAuthorizationRequestRepository
+import org.springframework.security.oauth2.client.web.server.ServerOAuth2AuthorizedClientRepository
+import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationRequest
+import org.springframework.security.web.server.authentication.ServerAuthenticationConverter
+import org.springframework.web.server.ServerWebExchange
+
+/**
+ * A Kotlin DSL to configure the [ServerHttpSecurity] OAuth 2.0 client using idiomatic Kotlin code.
+ *
+ * @author Eleftheria Stein
+ * @since 5.4
+ * @property authenticationManager the [ReactiveAuthenticationManager] used to determine if the provided
+ * [Authentication] can be authenticated.
+ * @property authenticationConverter the [ServerAuthenticationConverter] used for converting from a [ServerWebExchange]
+ * to an [Authentication].
+ * @property clientRegistrationRepository the repository of client registrations.
+ * @property authorizedClientRepository the repository for authorized client(s).
+ * @property authorizationRequestRepository the repository to use for storing [OAuth2AuthorizationRequest]s.
+ */
+@ServerSecurityMarker
+class ServerOAuth2ClientDsl {
+ var authenticationManager: ReactiveAuthenticationManager? = null
+ var authenticationConverter: ServerAuthenticationConverter? = null
+ var clientRegistrationRepository: ReactiveClientRegistrationRepository? = null
+ var authorizedClientRepository: ServerOAuth2AuthorizedClientRepository? = null
+ var authorizationRequestRepository: ServerAuthorizationRequestRepository? = null
+
+ internal fun get(): (ServerHttpSecurity.OAuth2ClientSpec) -> Unit {
+ return { oauth2Client ->
+ authenticationManager?.also { oauth2Client.authenticationManager(authenticationManager) }
+ authenticationConverter?.also { oauth2Client.authenticationConverter(authenticationConverter) }
+ clientRegistrationRepository?.also { oauth2Client.clientRegistrationRepository(clientRegistrationRepository) }
+ authorizedClientRepository?.also { oauth2Client.authorizedClientRepository(authorizedClientRepository) }
+ authorizationRequestRepository?.also { oauth2Client.authorizationRequestRepository(authorizationRequestRepository) }
+ }
+ }
+}
diff --git a/config/src/main/kotlin/org/springframework/security/config/web/server/ServerOAuth2LoginDsl.kt b/config/src/main/kotlin/org/springframework/security/config/web/server/ServerOAuth2LoginDsl.kt
new file mode 100644
index 00000000000..0c24340fbb3
--- /dev/null
+++ b/config/src/main/kotlin/org/springframework/security/config/web/server/ServerOAuth2LoginDsl.kt
@@ -0,0 +1,84 @@
+/*
+ * Copyright 2002-2020 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.server
+
+import org.springframework.security.authentication.ReactiveAuthenticationManager
+import org.springframework.security.core.Authentication
+import org.springframework.security.oauth2.client.ReactiveOAuth2AuthorizedClientService
+import org.springframework.security.oauth2.client.registration.ReactiveClientRegistrationRepository
+import org.springframework.security.oauth2.client.web.server.ServerAuthorizationRequestRepository
+import org.springframework.security.oauth2.client.web.server.ServerOAuth2AuthorizationRequestResolver
+import org.springframework.security.oauth2.client.web.server.ServerOAuth2AuthorizedClientRepository
+import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationRequest
+import org.springframework.security.web.server.authentication.ServerAuthenticationConverter
+import org.springframework.security.web.server.authentication.ServerAuthenticationFailureHandler
+import org.springframework.security.web.server.authentication.ServerAuthenticationSuccessHandler
+import org.springframework.security.web.server.context.ServerSecurityContextRepository
+import org.springframework.security.web.server.util.matcher.ServerWebExchangeMatcher
+import org.springframework.web.server.ServerWebExchange
+
+/**
+ * A Kotlin DSL to configure [ServerHttpSecurity] OAuth 2.0 login using idiomatic Kotlin code.
+ *
+ * @author Eleftheria Stein
+ * @since 5.4
+ * @property authenticationManager the [ReactiveAuthenticationManager] used to determine if the provided
+ * [Authentication] can be authenticated.
+ * @property securityContextRepository the [ServerSecurityContextRepository] used to save the [Authentication].
+ * @property authenticationSuccessHandler the [ServerAuthenticationSuccessHandler] used after authentication success.
+ * @property authenticationFailureHandler the [ServerAuthenticationFailureHandler] used after authentication failure.
+ * @property authenticationConverter the [ServerAuthenticationConverter] used for converting from a [ServerWebExchange]
+ * to an [Authentication].
+ * @property clientRegistrationRepository the repository of client registrations.
+ * @property authorizedClientService the service responsible for associating an access token to a client and resource
+ * owner.
+ * @property authorizedClientRepository the repository for authorized client(s).
+ * @property authorizationRequestRepository the repository to use for storing [OAuth2AuthorizationRequest]s.
+ * @property authorizationRequestResolver the resolver used for resolving [OAuth2AuthorizationRequest]s.
+ * @property authenticationMatcher the [ServerWebExchangeMatcher] used for determining if the request is an
+ * authentication request.
+ */
+@ServerSecurityMarker
+class ServerOAuth2LoginDsl {
+ var authenticationManager: ReactiveAuthenticationManager? = null
+ var securityContextRepository: ServerSecurityContextRepository? = null
+ var authenticationSuccessHandler: ServerAuthenticationSuccessHandler? = null
+ var authenticationFailureHandler: ServerAuthenticationFailureHandler? = null
+ var authenticationConverter: ServerAuthenticationConverter? = null
+ var clientRegistrationRepository: ReactiveClientRegistrationRepository? = null
+ var authorizedClientService: ReactiveOAuth2AuthorizedClientService? = null
+ var authorizedClientRepository: ServerOAuth2AuthorizedClientRepository? = null
+ var authorizationRequestRepository: ServerAuthorizationRequestRepository? = null
+ var authorizationRequestResolver: ServerOAuth2AuthorizationRequestResolver? = null
+ var authenticationMatcher: ServerWebExchangeMatcher? = null
+
+ internal fun get(): (ServerHttpSecurity.OAuth2LoginSpec) -> Unit {
+ return { oauth2Login ->
+ authenticationManager?.also { oauth2Login.authenticationManager(authenticationManager) }
+ securityContextRepository?.also { oauth2Login.securityContextRepository(securityContextRepository) }
+ authenticationSuccessHandler?.also { oauth2Login.authenticationSuccessHandler(authenticationSuccessHandler) }
+ authenticationFailureHandler?.also { oauth2Login.authenticationFailureHandler(authenticationFailureHandler) }
+ authenticationConverter?.also { oauth2Login.authenticationConverter(authenticationConverter) }
+ clientRegistrationRepository?.also { oauth2Login.clientRegistrationRepository(clientRegistrationRepository) }
+ authorizedClientService?.also { oauth2Login.authorizedClientService(authorizedClientService) }
+ authorizedClientRepository?.also { oauth2Login.authorizedClientRepository(authorizedClientRepository) }
+ authorizationRequestRepository?.also { oauth2Login.authorizationRequestRepository(authorizationRequestRepository) }
+ authorizationRequestResolver?.also { oauth2Login.authorizationRequestResolver(authorizationRequestResolver) }
+ authenticationMatcher?.also { oauth2Login.authenticationMatcher(authenticationMatcher) }
+ }
+ }
+}
diff --git a/config/src/main/kotlin/org/springframework/security/config/web/server/ServerOAuth2ResourceServerDsl.kt b/config/src/main/kotlin/org/springframework/security/config/web/server/ServerOAuth2ResourceServerDsl.kt
new file mode 100644
index 00000000000..ee48923469b
--- /dev/null
+++ b/config/src/main/kotlin/org/springframework/security/config/web/server/ServerOAuth2ResourceServerDsl.kt
@@ -0,0 +1,117 @@
+/*
+ * Copyright 2002-2020 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.server
+
+import org.springframework.security.authentication.ReactiveAuthenticationManagerResolver
+import org.springframework.security.web.server.ServerAuthenticationEntryPoint
+import org.springframework.security.web.server.authentication.ServerAuthenticationConverter
+import org.springframework.security.web.server.authorization.ServerAccessDeniedHandler
+import org.springframework.web.server.ServerWebExchange
+
+/**
+ * A Kotlin DSL to configure [ServerHttpSecurity] OAuth 2.0 resource server using idiomatic Kotlin code.
+ *
+ * @author Eleftheria Stein
+ * @since 5.4
+ * @property accessDeniedHandler the [ServerAccessDeniedHandler] to use for requests authenticating with
+ * Bearer Tokens.
+ * @property authenticationEntryPoint the [ServerAuthenticationEntryPoint] to use for requests authenticating with
+ * Bearer Tokens.
+ * @property bearerTokenConverter the [ServerAuthenticationConverter] to use for requests authenticating with
+ * Bearer Tokens.
+ * @property authenticationManagerResolver the [ReactiveAuthenticationManagerResolver] to use.
+ */
+@ServerSecurityMarker
+class ServerOAuth2ResourceServerDsl {
+ var accessDeniedHandler: ServerAccessDeniedHandler? = null
+ var authenticationEntryPoint: ServerAuthenticationEntryPoint? = null
+ var bearerTokenConverter: ServerAuthenticationConverter? = null
+ var authenticationManagerResolver: ReactiveAuthenticationManagerResolver? = null
+
+ private var jwt: ((ServerHttpSecurity.OAuth2ResourceServerSpec.JwtSpec) -> Unit)? = null
+ private var opaqueToken: ((ServerHttpSecurity.OAuth2ResourceServerSpec.OpaqueTokenSpec) -> Unit)? = null
+
+ /**
+ * Enables JWT-encoded bearer token support.
+ *
+ * Example:
+ *
+ * ```
+ * @EnableWebFluxSecurity
+ * class SecurityConfig {
+ *
+ * @Bean
+ * fun springWebFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain {
+ * return http {
+ * oauth2ResourceServer {
+ * jwt {
+ * jwkSetUri = "https://example.com/oauth2/jwk"
+ * }
+ * }
+ * }
+ * }
+ * }
+ * ```
+ *
+ * @param jwtConfig custom configurations to configure JWT resource server support
+ * @see [ServerJwtDsl]
+ */
+ fun jwt(jwtConfig: ServerJwtDsl.() -> Unit) {
+ this.jwt = ServerJwtDsl().apply(jwtConfig).get()
+ }
+
+ /**
+ * Enables opaque token support.
+ *
+ * Example:
+ *
+ * ```
+ * @EnableWebFluxSecurity
+ * class SecurityConfig {
+ *
+ * @Bean
+ * fun springWebFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain {
+ * return http {
+ * oauth2ResourceServer {
+ * opaqueToken {
+ * introspectionUri = "https://example.com/introspect"
+ * introspectionClientCredentials("client", "secret")
+ * }
+ * }
+ * }
+ * }
+ * }
+ * ```
+ *
+ * @param opaqueTokenConfig custom configurations to configure JWT resource server support
+ * @see [ServerOpaqueTokenDsl]
+ */
+ fun opaqueToken(opaqueTokenConfig: ServerOpaqueTokenDsl.() -> Unit) {
+ this.opaqueToken = ServerOpaqueTokenDsl().apply(opaqueTokenConfig).get()
+ }
+
+ internal fun get(): (ServerHttpSecurity.OAuth2ResourceServerSpec) -> Unit {
+ return { oauth2ResourceServer ->
+ accessDeniedHandler?.also { oauth2ResourceServer.accessDeniedHandler(accessDeniedHandler) }
+ authenticationEntryPoint?.also { oauth2ResourceServer.authenticationEntryPoint(authenticationEntryPoint) }
+ bearerTokenConverter?.also { oauth2ResourceServer.bearerTokenConverter(bearerTokenConverter) }
+ authenticationManagerResolver?.also { oauth2ResourceServer.authenticationManagerResolver(authenticationManagerResolver!!) }
+ jwt?.also { oauth2ResourceServer.jwt(jwt) }
+ opaqueToken?.also { oauth2ResourceServer.opaqueToken(opaqueToken) }
+ }
+ }
+}
diff --git a/config/src/main/kotlin/org/springframework/security/config/web/server/ServerOpaqueTokenDsl.kt b/config/src/main/kotlin/org/springframework/security/config/web/server/ServerOpaqueTokenDsl.kt
new file mode 100644
index 00000000000..72d9bf103fb
--- /dev/null
+++ b/config/src/main/kotlin/org/springframework/security/config/web/server/ServerOpaqueTokenDsl.kt
@@ -0,0 +1,67 @@
+/*
+ * Copyright 2002-2020 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.server
+
+import org.springframework.security.oauth2.server.resource.introspection.ReactiveOpaqueTokenIntrospector
+
+/**
+ * A Kotlin DSL to configure [ServerHttpSecurity] Opaque Token Resource Server support using idiomatic Kotlin code.
+ *
+ * @author Eleftheria Stein
+ * @since 5.4
+ * @property introspectionUri the URI of the Introspection endpoint.
+ * @property introspector the [ReactiveOpaqueTokenIntrospector] to use.
+ */
+@ServerSecurityMarker
+class ServerOpaqueTokenDsl {
+ private var _introspectionUri: String? = null
+ private var _introspector: ReactiveOpaqueTokenIntrospector? = null
+ private var clientCredentials: Pair? = null
+
+ var introspectionUri: String?
+ get() = _introspectionUri
+ set(value) {
+ _introspectionUri = value
+ _introspector = null
+ }
+ var introspector: ReactiveOpaqueTokenIntrospector?
+ get() = _introspector
+ set(value) {
+ _introspector = value
+ _introspectionUri = null
+ clientCredentials = null
+ }
+
+ /**
+ * Configures the credentials for Introspection endpoint.
+ *
+ * @param clientId the clientId part of the credentials.
+ * @param clientSecret the clientSecret part of the credentials.
+ */
+ fun introspectionClientCredentials(clientId: String, clientSecret: String) {
+ clientCredentials = Pair(clientId, clientSecret)
+ _introspector = null
+ }
+
+ internal fun get(): (ServerHttpSecurity.OAuth2ResourceServerSpec.OpaqueTokenSpec) -> Unit {
+ return { opaqueToken ->
+ introspectionUri?.also { opaqueToken.introspectionUri(introspectionUri) }
+ clientCredentials?.also { opaqueToken.introspectionClientCredentials(clientCredentials!!.first, clientCredentials!!.second) }
+ introspector?.also { opaqueToken.introspector(introspector) }
+ }
+ }
+}
diff --git a/config/src/main/kotlin/org/springframework/security/config/web/server/ServerReferrerPolicyDsl.kt b/config/src/main/kotlin/org/springframework/security/config/web/server/ServerReferrerPolicyDsl.kt
new file mode 100644
index 00000000000..2e247378def
--- /dev/null
+++ b/config/src/main/kotlin/org/springframework/security/config/web/server/ServerReferrerPolicyDsl.kt
@@ -0,0 +1,40 @@
+/*
+ * Copyright 2002-2020 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.server
+
+import org.springframework.security.web.server.header.ReferrerPolicyServerHttpHeadersWriter
+
+/**
+ * A Kotlin DSL to configure the [ServerHttpSecurity] referrer policy header using
+ * idiomatic Kotlin code.
+ *
+ * @author Eleftheria Stein
+ * @since 5.4
+ * @property policy the policy to be used in the response header.
+ */
+@ServerSecurityMarker
+class ServerReferrerPolicyDsl {
+ var policy: ReferrerPolicyServerHttpHeadersWriter.ReferrerPolicy? = null
+
+ internal fun get(): (ServerHttpSecurity.HeaderSpec.ReferrerPolicySpec) -> Unit {
+ return { referrerPolicy ->
+ policy?.also {
+ referrerPolicy.policy(policy)
+ }
+ }
+ }
+}
diff --git a/config/src/main/kotlin/org/springframework/security/config/web/server/ServerRequestCacheDsl.kt b/config/src/main/kotlin/org/springframework/security/config/web/server/ServerRequestCacheDsl.kt
new file mode 100644
index 00000000000..59d25ad054a
--- /dev/null
+++ b/config/src/main/kotlin/org/springframework/security/config/web/server/ServerRequestCacheDsl.kt
@@ -0,0 +1,51 @@
+/*
+ * Copyright 2002-2020 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.server
+
+import org.springframework.security.web.server.savedrequest.ServerRequestCache
+
+/**
+ * A Kotlin DSL to configure the request cache using idiomatic Kotlin code.
+ *
+ * @author Eleftheria Stein
+ * @since 5.4
+ * @property requestCache allows explicit configuration of the [ServerRequestCache] to be used.
+ */
+@ServerSecurityMarker
+class ServerRequestCacheDsl {
+ var requestCache: ServerRequestCache? = null
+
+ private var disabled = false
+
+ /**
+ * Disables the request cache.
+ */
+ fun disable() {
+ disabled = true
+ }
+
+ internal fun get(): (ServerHttpSecurity.RequestCacheSpec) -> Unit {
+ return { requestCacheConfig ->
+ requestCache?.also {
+ requestCacheConfig.requestCache(requestCache)
+ if (disabled) {
+ requestCacheConfig.disable()
+ }
+ }
+ }
+ }
+}
diff --git a/config/src/main/kotlin/org/springframework/security/config/web/server/ServerSecurityMarker.kt b/config/src/main/kotlin/org/springframework/security/config/web/server/ServerSecurityMarker.kt
new file mode 100644
index 00000000000..29fbdde03f6
--- /dev/null
+++ b/config/src/main/kotlin/org/springframework/security/config/web/server/ServerSecurityMarker.kt
@@ -0,0 +1,26 @@
+/*
+ * Copyright 2002-2020 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.server
+
+/**
+ * Marker annotation indicating that the annotated class is part of the security DSL for server configuration.
+ *
+ * @author Loïc Labagnara
+ * @since 5.4
+ */
+@DslMarker
+annotation class ServerSecurityMarker
diff --git a/config/src/main/kotlin/org/springframework/security/config/web/server/ServerX509Dsl.kt b/config/src/main/kotlin/org/springframework/security/config/web/server/ServerX509Dsl.kt
new file mode 100644
index 00000000000..a970bd1b51d
--- /dev/null
+++ b/config/src/main/kotlin/org/springframework/security/config/web/server/ServerX509Dsl.kt
@@ -0,0 +1,43 @@
+/*
+ * Copyright 2002-2020 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.server
+
+import org.springframework.security.authentication.ReactiveAuthenticationManager
+import org.springframework.security.core.Authentication
+import org.springframework.security.web.authentication.preauth.x509.X509PrincipalExtractor
+
+/**
+ * A Kotlin DSL to configure [ServerHttpSecurity] X509 based pre authentication using idiomatic Kotlin code.
+ *
+ * @author Eleftheria Stein
+ * @since 5.4
+ * @property principalExtractor the [X509PrincipalExtractor] used to obtain the principal for use within the framework.
+ * @property authenticationManager the [ReactiveAuthenticationManager] used to determine if the provided
+ * [Authentication] can be authenticated.
+ */
+@ServerSecurityMarker
+class ServerX509Dsl {
+ var principalExtractor: X509PrincipalExtractor? = null
+ var authenticationManager: ReactiveAuthenticationManager? = null
+
+ internal fun get(): (ServerHttpSecurity.X509Spec) -> Unit {
+ return { x509 ->
+ authenticationManager?.also { x509.authenticationManager(authenticationManager) }
+ principalExtractor?.also { x509.principalExtractor(principalExtractor) }
+ }
+ }
+}
diff --git a/config/src/main/kotlin/org/springframework/security/config/web/server/ServerXssProtectionDsl.kt b/config/src/main/kotlin/org/springframework/security/config/web/server/ServerXssProtectionDsl.kt
new file mode 100644
index 00000000000..9166acf4fe7
--- /dev/null
+++ b/config/src/main/kotlin/org/springframework/security/config/web/server/ServerXssProtectionDsl.kt
@@ -0,0 +1,44 @@
+/*
+ * Copyright 2002-2020 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.server
+
+/**
+ * A Kotlin DSL to configure the [ServerHttpSecurity] XSS protection header using
+ * idiomatic Kotlin code.
+ *
+ * @author Eleftheria Stein
+ * @since 5.4
+ */
+@ServerSecurityMarker
+class ServerXssProtectionDsl {
+ private var disabled = false
+
+ /**
+ * Disables cache control response headers
+ */
+ fun disable() {
+ disabled = true
+ }
+
+ internal fun get(): (ServerHttpSecurity.HeaderSpec.XssProtectionSpec) -> Unit {
+ return { xss ->
+ if (disabled) {
+ xss.disable()
+ }
+ }
+ }
+}
diff --git a/config/src/main/kotlin/org/springframework/security/config/web/servlet/AbstractRequestMatcherDsl.kt b/config/src/main/kotlin/org/springframework/security/config/web/servlet/AbstractRequestMatcherDsl.kt
index 3fcf38fa383..9e35287b5f5 100644
--- a/config/src/main/kotlin/org/springframework/security/config/web/servlet/AbstractRequestMatcherDsl.kt
+++ b/config/src/main/kotlin/org/springframework/security/config/web/servlet/AbstractRequestMatcherDsl.kt
@@ -16,6 +16,7 @@
package org.springframework.security.config.web.servlet
+import org.springframework.http.HttpMethod
import org.springframework.security.web.util.matcher.AnyRequestMatcher
import org.springframework.security.web.util.matcher.RequestMatcher
@@ -37,7 +38,8 @@ abstract class AbstractRequestMatcherDsl {
protected data class PatternAuthorizationRule(val pattern: String,
val patternType: PatternType,
- val servletPath: String?,
+ val servletPath: String? = null,
+ val httpMethod: HttpMethod? = null,
override val rule: String) : AuthorizationRule(rule)
protected abstract class AuthorizationRule(open val rule: String)
diff --git a/config/src/main/kotlin/org/springframework/security/config/web/servlet/AuthorizeRequestsDsl.kt b/config/src/main/kotlin/org/springframework/security/config/web/servlet/AuthorizeRequestsDsl.kt
index 5734c2caf68..eb0887d7bf5 100644
--- a/config/src/main/kotlin/org/springframework/security/config/web/servlet/AuthorizeRequestsDsl.kt
+++ b/config/src/main/kotlin/org/springframework/security/config/web/servlet/AuthorizeRequestsDsl.kt
@@ -16,6 +16,7 @@
package org.springframework.security.config.web.servlet
+import org.springframework.http.HttpMethod
import org.springframework.security.config.annotation.web.builders.HttpSecurity
import org.springframework.security.config.annotation.web.configurers.ExpressionUrlAuthorizationConfigurer
import org.springframework.security.web.util.matcher.AnyRequestMatcher
@@ -35,6 +36,7 @@ class AuthorizeRequestsDsl : AbstractRequestMatcherDsl() {
private val MVC_PRESENT = ClassUtils.isPresent(
HANDLER_MAPPING_INTROSPECTOR,
AuthorizeRequestsDsl::class.java.classLoader)
+ private val PATTERN_TYPE = if (MVC_PRESENT) PatternType.MVC else PatternType.ANT
/**
* Adds a request authorization rule.
@@ -64,11 +66,32 @@ class AuthorizeRequestsDsl : AbstractRequestMatcherDsl() {
* (i.e. "hasAuthority('ROLE_USER') and hasAuthority('ROLE_SUPER')")
*/
fun authorize(pattern: String, access: String = "authenticated") {
- if (MVC_PRESENT) {
- authorizationRules.add(PatternAuthorizationRule(pattern, PatternType.MVC, null, access))
- } else {
- authorizationRules.add(PatternAuthorizationRule(pattern, PatternType.ANT, null, access))
- }
+ authorizationRules.add(PatternAuthorizationRule(pattern = pattern,
+ patternType = PATTERN_TYPE,
+ rule = access))
+ }
+
+ /**
+ * Adds a request authorization rule for an endpoint matching the provided
+ * pattern.
+ * If Spring MVC is on the classpath, it will use an MVC matcher.
+ * If Spring MVC is not an the classpath, it will use an ant matcher.
+ * The MVC will use the same rules that Spring MVC uses for matching.
+ * For example, often times a mapping of the path "/path" will match on
+ * "/path", "/path/", "/path.html", etc.
+ * If the current request will not be processed by Spring MVC, a reasonable default
+ * using the pattern as an ant pattern will be used.
+ *
+ * @param method the HTTP method to match the income requests against.
+ * @param pattern the pattern to match incoming requests against.
+ * @param access the SpEL expression to secure the matching request
+ * (i.e. "hasAuthority('ROLE_USER') and hasAuthority('ROLE_SUPER')")
+ */
+ fun authorize(method: HttpMethod, pattern: String, access: String = "authenticated") {
+ authorizationRules.add(PatternAuthorizationRule(pattern = pattern,
+ patternType = PATTERN_TYPE,
+ httpMethod = method,
+ rule = access))
}
/**
@@ -89,11 +112,36 @@ class AuthorizeRequestsDsl : AbstractRequestMatcherDsl() {
* (i.e. "hasAuthority('ROLE_USER') and hasAuthority('ROLE_SUPER')")
*/
fun authorize(pattern: String, servletPath: String, access: String = "authenticated") {
- if (MVC_PRESENT) {
- authorizationRules.add(PatternAuthorizationRule(pattern, PatternType.MVC, servletPath, access))
- } else {
- authorizationRules.add(PatternAuthorizationRule(pattern, PatternType.ANT, servletPath, access))
- }
+ authorizationRules.add(PatternAuthorizationRule(pattern = pattern,
+ patternType = PATTERN_TYPE,
+ servletPath = servletPath,
+ rule = access))
+ }
+
+ /**
+ * Adds a request authorization rule for an endpoint matching the provided
+ * pattern.
+ * If Spring MVC is on the classpath, it will use an MVC matcher.
+ * If Spring MVC is not an the classpath, it will use an ant matcher.
+ * The MVC will use the same rules that Spring MVC uses for matching.
+ * For example, often times a mapping of the path "/path" will match on
+ * "/path", "/path/", "/path.html", etc.
+ * If the current request will not be processed by Spring MVC, a reasonable default
+ * using the pattern as an ant pattern will be used.
+ *
+ * @param method the HTTP method to match the income requests against.
+ * @param pattern the pattern to match incoming requests against.
+ * @param servletPath the servlet path to match incoming requests against. This
+ * only applies when using an MVC pattern matcher.
+ * @param access the SpEL expression to secure the matching request
+ * (i.e. "hasAuthority('ROLE_USER') and hasAuthority('ROLE_SUPER')")
+ */
+ fun authorize(method: HttpMethod, pattern: String, servletPath: String, access: String = "authenticated") {
+ authorizationRules.add(PatternAuthorizationRule(pattern = pattern,
+ patternType = PATTERN_TYPE,
+ servletPath = servletPath,
+ httpMethod = method,
+ rule = access))
}
/**
@@ -152,12 +200,10 @@ class AuthorizeRequestsDsl : AbstractRequestMatcherDsl() {
is MatcherAuthorizationRule -> requests.requestMatchers(rule.matcher).access(rule.rule)
is PatternAuthorizationRule -> {
when (rule.patternType) {
- PatternType.ANT -> requests.antMatchers(rule.pattern).access(rule.rule)
- PatternType.MVC -> {
- val mvcMatchersAuthorizeUrl = requests.mvcMatchers(rule.pattern)
- rule.servletPath?.also { mvcMatchersAuthorizeUrl.servletPath(rule.servletPath) }
- mvcMatchersAuthorizeUrl.access(rule.rule)
- }
+ PatternType.ANT -> requests.antMatchers(rule.httpMethod, rule.pattern).access(rule.rule)
+ PatternType.MVC -> requests.mvcMatchers(rule.httpMethod, rule.pattern)
+ .apply { if(rule.servletPath != null) servletPath(rule.servletPath) }
+ .access(rule.rule)
}
}
}
diff --git a/config/src/main/kotlin/org/springframework/security/config/web/servlet/HeadersDsl.kt b/config/src/main/kotlin/org/springframework/security/config/web/servlet/HeadersDsl.kt
index 2b3abfe8ee3..ae1d9d945e6 100644
--- a/config/src/main/kotlin/org/springframework/security/config/web/servlet/HeadersDsl.kt
+++ b/config/src/main/kotlin/org/springframework/security/config/web/servlet/HeadersDsl.kt
@@ -40,6 +40,7 @@ class HeadersDsl {
private var contentSecurityPolicy: ((HeadersConfigurer.ContentSecurityPolicyConfig) -> Unit)? = null
private var referrerPolicy: ((HeadersConfigurer.ReferrerPolicyConfig) -> Unit)? = null
private var featurePolicyDirectives: String? = null
+ private var disabled = false
var defaultsDisabled: Boolean? = null
@@ -161,6 +162,15 @@ class HeadersDsl {
this.featurePolicyDirectives = policyDirectives
}
+ /**
+ * Disable all HTTP security headers.
+ *
+ * @since 5.4
+ */
+ fun disable() {
+ disabled = true
+ }
+
internal fun get(): (HeadersConfigurer) -> Unit {
return { headers ->
defaultsDisabled?.also {
@@ -195,6 +205,9 @@ class HeadersDsl {
featurePolicyDirectives?.also {
headers.featurePolicy(featurePolicyDirectives)
}
+ if (disabled) {
+ headers.disable()
+ }
}
}
}
diff --git a/config/src/main/kotlin/org/springframework/security/config/web/servlet/HttpSecurityDsl.kt b/config/src/main/kotlin/org/springframework/security/config/web/servlet/HttpSecurityDsl.kt
index 6c672a8d1f2..137d459ce1c 100644
--- a/config/src/main/kotlin/org/springframework/security/config/web/servlet/HttpSecurityDsl.kt
+++ b/config/src/main/kotlin/org/springframework/security/config/web/servlet/HttpSecurityDsl.kt
@@ -669,6 +669,134 @@ class HttpSecurityDsl(private val http: HttpSecurity, private val init: HttpSecu
this.http.addFilterAt(filter, atFilter)
}
+ /**
+ * Adds the [Filter] at the location of the specified [Filter] class.
+ * Variant that is leveraging Kotlin reified type parameters.
+ *
+ * Example:
+ *
+ * ```
+ * @EnableWebSecurity
+ * class SecurityConfig : WebSecurityConfigurerAdapter() {
+ *
+ * override fun configure(http: HttpSecurity) {
+ * http {
+ * addFilterAt(CustomFilter())
+ * }
+ * }
+ * }
+ * ```
+ *
+ * @param filter the [Filter] to register
+ * @param T the location of another [Filter] that is already registered
+ * (i.e. known) with Spring Security.
+ */
+ inline fun addFilterAt(filter: Filter) {
+ this.addFilterAt(filter, T::class.java)
+ }
+
+ /**
+ * Adds the [Filter] after the location of the specified [Filter] class.
+ *
+ * Example:
+ *
+ * ```
+ * @EnableWebSecurity
+ * class SecurityConfig : WebSecurityConfigurerAdapter() {
+ *
+ * override fun configure(http: HttpSecurity) {
+ * http {
+ * addFilterAfter(CustomFilter(), UsernamePasswordAuthenticationFilter::class.java)
+ * }
+ * }
+ * }
+ * ```
+ *
+ * @param filter the [Filter] to register
+ * @param afterFilter the location of another [Filter] that is already registered
+ * (i.e. known) with Spring Security.
+ */
+ fun addFilterAfter(filter: Filter, afterFilter: Class) {
+ this.http.addFilterAfter(filter, afterFilter)
+ }
+
+ /**
+ * Adds the [Filter] after the location of the specified [Filter] class.
+ * Variant that is leveraging Kotlin reified type parameters.
+ *
+ * Example:
+ *
+ * ```
+ * @EnableWebSecurity
+ * class SecurityConfig : WebSecurityConfigurerAdapter() {
+ *
+ * override fun configure(http: HttpSecurity) {
+ * http {
+ * addFilterAfter(CustomFilter())
+ * }
+ * }
+ * }
+ * ```
+ *
+ * @param filter the [Filter] to register
+ * @param T the location of another [Filter] that is already registered
+ * (i.e. known) with Spring Security.
+ */
+ inline fun addFilterAfter(filter: Filter) {
+ this.addFilterAfter(filter, T::class.java)
+ }
+
+ /**
+ * Adds the [Filter] before the location of the specified [Filter] class.
+ *
+ * Example:
+ *
+ * ```
+ * @EnableWebSecurity
+ * class SecurityConfig : WebSecurityConfigurerAdapter() {
+ *
+ * override fun configure(http: HttpSecurity) {
+ * http {
+ * addFilterBefore(CustomFilter(), UsernamePasswordAuthenticationFilter::class.java)
+ * }
+ * }
+ * }
+ * ```
+ *
+ * @param filter the [Filter] to register
+ * @param beforeFilter the location of another [Filter] that is already registered
+ * (i.e. known) with Spring Security.
+ */
+ fun addFilterBefore(filter: Filter, beforeFilter: Class) {
+ this.http.addFilterBefore(filter, beforeFilter)
+ }
+
+ /**
+ * Adds the [Filter] before the location of the specified [Filter] class.
+ * Variant that is leveraging Kotlin reified type parameters.
+ *
+ * Example:
+ *
+ * ```
+ * @EnableWebSecurity
+ * class SecurityConfig : WebSecurityConfigurerAdapter() {
+ *
+ * override fun configure(http: HttpSecurity) {
+ * http {
+ * addFilterBefore(CustomFilter())
+ * }
+ * }
+ * }
+ * ```
+ *
+ * @param filter the [Filter] to register
+ * @param T the location of another [Filter] that is already registered
+ * (i.e. known) with Spring Security.
+ */
+ inline fun addFilterBefore(filter: Filter) {
+ this.addFilterBefore(filter, T::class.java)
+ }
+
/**
* Apply all configurations to the provided [HttpSecurity]
*/
diff --git a/config/src/main/kotlin/org/springframework/security/config/web/servlet/RequiresChannelDsl.kt b/config/src/main/kotlin/org/springframework/security/config/web/servlet/RequiresChannelDsl.kt
index 2febca5e679..a7149014c2e 100644
--- a/config/src/main/kotlin/org/springframework/security/config/web/servlet/RequiresChannelDsl.kt
+++ b/config/src/main/kotlin/org/springframework/security/config/web/servlet/RequiresChannelDsl.kt
@@ -40,6 +40,7 @@ class RequiresChannelDsl : AbstractRequestMatcherDsl() {
private val MVC_PRESENT = ClassUtils.isPresent(
HANDLER_MAPPING_INTROSPECTOR,
RequiresChannelDsl::class.java.classLoader)
+ private val PATTERN_TYPE = if (MVC_PRESENT) PatternType.MVC else PatternType.ANT
var channelProcessors: List? = null
@@ -71,11 +72,9 @@ class RequiresChannelDsl : AbstractRequestMatcherDsl() {
* (i.e. "REQUIRES_SECURE_CHANNEL")
*/
fun secure(pattern: String, attribute: String = "REQUIRES_SECURE_CHANNEL") {
- if (MVC_PRESENT) {
- channelSecurityRules.add(PatternAuthorizationRule(pattern, PatternType.MVC, null, attribute))
- } else {
- channelSecurityRules.add(PatternAuthorizationRule(pattern, PatternType.ANT, null, attribute))
- }
+ channelSecurityRules.add(PatternAuthorizationRule(pattern = pattern,
+ patternType = PATTERN_TYPE,
+ rule = attribute))
}
/**
@@ -96,11 +95,10 @@ class RequiresChannelDsl : AbstractRequestMatcherDsl() {
* (i.e. "REQUIRES_SECURE_CHANNEL")
*/
fun secure(pattern: String, servletPath: String, attribute: String = "REQUIRES_SECURE_CHANNEL") {
- if (MVC_PRESENT) {
- channelSecurityRules.add(PatternAuthorizationRule(pattern, PatternType.MVC, servletPath, attribute))
- } else {
- channelSecurityRules.add(PatternAuthorizationRule(pattern, PatternType.ANT, servletPath, attribute))
- }
+ channelSecurityRules.add(PatternAuthorizationRule(pattern = pattern,
+ patternType = PATTERN_TYPE,
+ servletPath = servletPath,
+ rule = attribute))
}
/**
diff --git a/config/src/main/kotlin/org/springframework/security/config/web/servlet/headers/CacheControlDsl.kt b/config/src/main/kotlin/org/springframework/security/config/web/servlet/headers/CacheControlDsl.kt
index 7525348bd00..316015b0160 100644
--- a/config/src/main/kotlin/org/springframework/security/config/web/servlet/headers/CacheControlDsl.kt
+++ b/config/src/main/kotlin/org/springframework/security/config/web/servlet/headers/CacheControlDsl.kt
@@ -18,7 +18,6 @@ package org.springframework.security.config.web.servlet.headers
import org.springframework.security.config.annotation.web.builders.HttpSecurity
import org.springframework.security.config.annotation.web.configurers.HeadersConfigurer
-import org.springframework.security.config.web.servlet.SecurityMarker
/**
* A Kotlin DSL to configure the [HttpSecurity] cache control headers using idiomatic
@@ -27,7 +26,7 @@ import org.springframework.security.config.web.servlet.SecurityMarker
* @author Eleftheria Stein
* @since 5.3
*/
-@SecurityMarker
+@HeadersSecurityMarker
class CacheControlDsl {
private var disabled = false
diff --git a/config/src/main/kotlin/org/springframework/security/config/web/servlet/headers/ContentSecurityPolicyDsl.kt b/config/src/main/kotlin/org/springframework/security/config/web/servlet/headers/ContentSecurityPolicyDsl.kt
index 1a27e5c197c..270b1d14b4d 100644
--- a/config/src/main/kotlin/org/springframework/security/config/web/servlet/headers/ContentSecurityPolicyDsl.kt
+++ b/config/src/main/kotlin/org/springframework/security/config/web/servlet/headers/ContentSecurityPolicyDsl.kt
@@ -18,7 +18,6 @@ package org.springframework.security.config.web.servlet.headers
import org.springframework.security.config.annotation.web.builders.HttpSecurity
import org.springframework.security.config.annotation.web.configurers.HeadersConfigurer
-import org.springframework.security.config.web.servlet.SecurityMarker
/**
* A Kotlin DSL to configure the [HttpSecurity] Content-Security-Policy header using
@@ -29,7 +28,7 @@ import org.springframework.security.config.web.servlet.SecurityMarker
* @property policyDirectives the security policy directive(s) to be used in the response header.
* @property reportOnly includes the Content-Security-Policy-Report-Only header in the response.
*/
-@SecurityMarker
+@HeadersSecurityMarker
class ContentSecurityPolicyDsl {
var policyDirectives: String? = null
var reportOnly: Boolean? = null
diff --git a/config/src/main/kotlin/org/springframework/security/config/web/servlet/headers/ContentTypeOptionsDsl.kt b/config/src/main/kotlin/org/springframework/security/config/web/servlet/headers/ContentTypeOptionsDsl.kt
index 5ef495a2c57..92014ae4063 100644
--- a/config/src/main/kotlin/org/springframework/security/config/web/servlet/headers/ContentTypeOptionsDsl.kt
+++ b/config/src/main/kotlin/org/springframework/security/config/web/servlet/headers/ContentTypeOptionsDsl.kt
@@ -18,7 +18,6 @@ package org.springframework.security.config.web.servlet.headers
import org.springframework.security.config.annotation.web.builders.HttpSecurity
import org.springframework.security.config.annotation.web.configurers.HeadersConfigurer
-import org.springframework.security.config.web.servlet.SecurityMarker
/**
* A Kotlin DSL to configure [HttpSecurity] X-Content-Type-Options header using idiomatic
@@ -27,7 +26,7 @@ import org.springframework.security.config.web.servlet.SecurityMarker
* @author Eleftheria Stein
* @since 5.3
*/
-@SecurityMarker
+@HeadersSecurityMarker
class ContentTypeOptionsDsl {
private var disabled = false
diff --git a/config/src/main/kotlin/org/springframework/security/config/web/servlet/headers/FrameOptionsDsl.kt b/config/src/main/kotlin/org/springframework/security/config/web/servlet/headers/FrameOptionsDsl.kt
index b16f2d0b233..3bf766ca98a 100644
--- a/config/src/main/kotlin/org/springframework/security/config/web/servlet/headers/FrameOptionsDsl.kt
+++ b/config/src/main/kotlin/org/springframework/security/config/web/servlet/headers/FrameOptionsDsl.kt
@@ -18,7 +18,6 @@ package org.springframework.security.config.web.servlet.headers
import org.springframework.security.config.annotation.web.builders.HttpSecurity
import org.springframework.security.config.annotation.web.configurers.HeadersConfigurer
-import org.springframework.security.config.web.servlet.SecurityMarker
/**
* A Kotlin DSL to configure the [HttpSecurity] X-Frame-Options header using
@@ -30,7 +29,7 @@ import org.springframework.security.config.web.servlet.SecurityMarker
* application.
* @property deny deny framing any content from this application.
*/
-@SecurityMarker
+@HeadersSecurityMarker
class FrameOptionsDsl {
var sameOrigin: Boolean? = null
var deny: Boolean? = null
diff --git a/config/src/main/kotlin/org/springframework/security/config/web/servlet/headers/HeadersSecurityMarker.kt b/config/src/main/kotlin/org/springframework/security/config/web/servlet/headers/HeadersSecurityMarker.kt
new file mode 100644
index 00000000000..67a97f56c05
--- /dev/null
+++ b/config/src/main/kotlin/org/springframework/security/config/web/servlet/headers/HeadersSecurityMarker.kt
@@ -0,0 +1,26 @@
+/*
+ * Copyright 2002-2020 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.headers
+
+/**
+ * Marker annotation indicating that the annotated class is part of the headers security DSL.
+ *
+ * @author Eleftheria Stein
+ * @since 5.4
+ */
+@DslMarker
+annotation class HeadersSecurityMarker
diff --git a/config/src/main/kotlin/org/springframework/security/config/web/servlet/headers/HttpPublicKeyPinningDsl.kt b/config/src/main/kotlin/org/springframework/security/config/web/servlet/headers/HttpPublicKeyPinningDsl.kt
index 5307351781e..74fbb6272a7 100644
--- a/config/src/main/kotlin/org/springframework/security/config/web/servlet/headers/HttpPublicKeyPinningDsl.kt
+++ b/config/src/main/kotlin/org/springframework/security/config/web/servlet/headers/HttpPublicKeyPinningDsl.kt
@@ -18,7 +18,6 @@ package org.springframework.security.config.web.servlet.headers
import org.springframework.security.config.annotation.web.builders.HttpSecurity
import org.springframework.security.config.annotation.web.configurers.HeadersConfigurer
-import org.springframework.security.config.web.servlet.SecurityMarker
/**
* A Kotlin DSL to configure the [HttpSecurity] HTTP Public Key Pinning header using
@@ -35,7 +34,7 @@ import org.springframework.security.config.web.servlet.SecurityMarker
* the server.
* @property reportUri the URI to which the browser should report pin validation failures.
*/
-@SecurityMarker
+@HeadersSecurityMarker
class HttpPublicKeyPinningDsl {
var pins: Map? = null
var maxAgeInSeconds: Long? = null
diff --git a/config/src/main/kotlin/org/springframework/security/config/web/servlet/headers/HttpStrictTransportSecurityDsl.kt b/config/src/main/kotlin/org/springframework/security/config/web/servlet/headers/HttpStrictTransportSecurityDsl.kt
index a1e109f94e4..e23e6d36b85 100644
--- a/config/src/main/kotlin/org/springframework/security/config/web/servlet/headers/HttpStrictTransportSecurityDsl.kt
+++ b/config/src/main/kotlin/org/springframework/security/config/web/servlet/headers/HttpStrictTransportSecurityDsl.kt
@@ -18,7 +18,6 @@ package org.springframework.security.config.web.servlet.headers
import org.springframework.security.config.annotation.web.builders.HttpSecurity
import org.springframework.security.config.annotation.web.configurers.HeadersConfigurer
-import org.springframework.security.config.web.servlet.SecurityMarker
import org.springframework.security.web.util.matcher.RequestMatcher
/**
@@ -35,7 +34,7 @@ import org.springframework.security.web.util.matcher.RequestMatcher
* @property includeSubDomains if true, subdomains should be considered HSTS Hosts too.
* @property preload if true, preload will be included in HSTS Header.
*/
-@SecurityMarker
+@HeadersSecurityMarker
class HttpStrictTransportSecurityDsl {
var maxAgeInSeconds: Long? = null
var requestMatcher: RequestMatcher? = null
diff --git a/config/src/main/kotlin/org/springframework/security/config/web/servlet/headers/ReferrerPolicyDsl.kt b/config/src/main/kotlin/org/springframework/security/config/web/servlet/headers/ReferrerPolicyDsl.kt
index 944407dc129..1ac54d94c02 100644
--- a/config/src/main/kotlin/org/springframework/security/config/web/servlet/headers/ReferrerPolicyDsl.kt
+++ b/config/src/main/kotlin/org/springframework/security/config/web/servlet/headers/ReferrerPolicyDsl.kt
@@ -18,7 +18,6 @@ package org.springframework.security.config.web.servlet.headers
import org.springframework.security.config.annotation.web.builders.HttpSecurity
import org.springframework.security.config.annotation.web.configurers.HeadersConfigurer
-import org.springframework.security.config.web.servlet.SecurityMarker
import org.springframework.security.web.header.writers.ReferrerPolicyHeaderWriter
/**
@@ -29,7 +28,7 @@ import org.springframework.security.web.header.writers.ReferrerPolicyHeaderWrite
* @since 5.3
* @property policy the policy to be used in the response header.
*/
-@SecurityMarker
+@HeadersSecurityMarker
class ReferrerPolicyDsl {
var policy: ReferrerPolicyHeaderWriter.ReferrerPolicy? = null
diff --git a/config/src/main/kotlin/org/springframework/security/config/web/servlet/headers/XssProtectionConfigDsl.kt b/config/src/main/kotlin/org/springframework/security/config/web/servlet/headers/XssProtectionConfigDsl.kt
index b023e8db354..a48a30af10a 100644
--- a/config/src/main/kotlin/org/springframework/security/config/web/servlet/headers/XssProtectionConfigDsl.kt
+++ b/config/src/main/kotlin/org/springframework/security/config/web/servlet/headers/XssProtectionConfigDsl.kt
@@ -18,7 +18,6 @@ package org.springframework.security.config.web.servlet.headers
import org.springframework.security.config.annotation.web.builders.HttpSecurity
import org.springframework.security.config.annotation.web.configurers.HeadersConfigurer
-import org.springframework.security.config.web.servlet.SecurityMarker
/**
* A Kotlin DSL to configure the [HttpSecurity] XSS protection header using
@@ -30,7 +29,7 @@ import org.springframework.security.config.web.servlet.SecurityMarker
* @property xssProtectionEnabled if true, the header value will contain a value of 1.
* If false, will explicitly disable specify that X-XSS-Protection is disabled.
*/
-@SecurityMarker
+@HeadersSecurityMarker
class XssProtectionConfigDsl {
var block: Boolean? = null
var xssProtectionEnabled: Boolean? = null
diff --git a/config/src/main/kotlin/org/springframework/security/config/web/servlet/oauth2/client/AuthorizationCodeGrantDsl.kt b/config/src/main/kotlin/org/springframework/security/config/web/servlet/oauth2/client/AuthorizationCodeGrantDsl.kt
index 6e04aae818b..b1ab6eca61f 100644
--- a/config/src/main/kotlin/org/springframework/security/config/web/servlet/oauth2/client/AuthorizationCodeGrantDsl.kt
+++ b/config/src/main/kotlin/org/springframework/security/config/web/servlet/oauth2/client/AuthorizationCodeGrantDsl.kt
@@ -18,7 +18,6 @@ package org.springframework.security.config.web.servlet.oauth2.client
import org.springframework.security.config.annotation.web.builders.HttpSecurity
import org.springframework.security.config.annotation.web.configurers.oauth2.client.OAuth2ClientConfigurer
-import org.springframework.security.config.web.servlet.SecurityMarker
import org.springframework.security.oauth2.client.endpoint.OAuth2AccessTokenResponseClient
import org.springframework.security.oauth2.client.endpoint.OAuth2AuthorizationCodeGrantRequest
import org.springframework.security.oauth2.client.web.AuthorizationRequestRepository
@@ -35,7 +34,7 @@ import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationRequ
* @property accessTokenResponseClient the client used for requesting the access token credential
* from the Token Endpoint.
*/
-@SecurityMarker
+@OAuth2ClientSecurityMarker
class AuthorizationCodeGrantDsl {
var authorizationRequestResolver: OAuth2AuthorizationRequestResolver? = null
var authorizationRequestRepository: AuthorizationRequestRepository? = null
diff --git a/config/src/main/kotlin/org/springframework/security/config/web/servlet/oauth2/client/OAuth2ClientSecurityMarker.kt b/config/src/main/kotlin/org/springframework/security/config/web/servlet/oauth2/client/OAuth2ClientSecurityMarker.kt
new file mode 100644
index 00000000000..3b6722a2590
--- /dev/null
+++ b/config/src/main/kotlin/org/springframework/security/config/web/servlet/oauth2/client/OAuth2ClientSecurityMarker.kt
@@ -0,0 +1,26 @@
+/*
+ * Copyright 2002-2020 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.oauth2.client
+
+/**
+ * Marker annotation indicating that the annotated class is part of the OAuth 2.0 client security DSL.
+ *
+ * @author Eleftheria Stein
+ * @since 5.4
+ */
+@DslMarker
+annotation class OAuth2ClientSecurityMarker
diff --git a/config/src/main/kotlin/org/springframework/security/config/web/servlet/oauth2/login/AuthorizationEndpointDsl.kt b/config/src/main/kotlin/org/springframework/security/config/web/servlet/oauth2/login/AuthorizationEndpointDsl.kt
index c416adea912..27c7982c6db 100644
--- a/config/src/main/kotlin/org/springframework/security/config/web/servlet/oauth2/login/AuthorizationEndpointDsl.kt
+++ b/config/src/main/kotlin/org/springframework/security/config/web/servlet/oauth2/login/AuthorizationEndpointDsl.kt
@@ -18,7 +18,6 @@ package org.springframework.security.config.web.servlet.oauth2.login
import org.springframework.security.config.annotation.web.builders.HttpSecurity
import org.springframework.security.config.annotation.web.configurers.oauth2.client.OAuth2LoginConfigurer
-import org.springframework.security.config.web.servlet.SecurityMarker
import org.springframework.security.oauth2.client.web.AuthorizationRequestRepository
import org.springframework.security.oauth2.client.web.OAuth2AuthorizationRequestResolver
import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationRequest
@@ -33,7 +32,7 @@ import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationRequ
* @property authorizationRequestResolver the resolver used for resolving [OAuth2AuthorizationRequest]'s.
* @property authorizationRequestRepository the repository used for storing [OAuth2AuthorizationRequest]'s.
*/
-@SecurityMarker
+@OAuth2LoginSecurityMarker
class AuthorizationEndpointDsl {
var baseUri: String? = null
var authorizationRequestResolver: OAuth2AuthorizationRequestResolver? = null
diff --git a/config/src/main/kotlin/org/springframework/security/config/web/servlet/oauth2/login/OAuth2LoginSecurityMarker.kt b/config/src/main/kotlin/org/springframework/security/config/web/servlet/oauth2/login/OAuth2LoginSecurityMarker.kt
new file mode 100644
index 00000000000..24ab0807d9d
--- /dev/null
+++ b/config/src/main/kotlin/org/springframework/security/config/web/servlet/oauth2/login/OAuth2LoginSecurityMarker.kt
@@ -0,0 +1,26 @@
+/*
+ * Copyright 2002-2020 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.oauth2.login
+
+/**
+ * Marker annotation indicating that the annotated class is part of the OAuth 2.0 login security DSL.
+ *
+ * @author Eleftheria Stein
+ * @since 5.4
+ */
+@DslMarker
+annotation class OAuth2LoginSecurityMarker
diff --git a/config/src/main/kotlin/org/springframework/security/config/web/servlet/oauth2/login/RedirectionEndpointDsl.kt b/config/src/main/kotlin/org/springframework/security/config/web/servlet/oauth2/login/RedirectionEndpointDsl.kt
index a15d6e419ef..ac63d88c9c1 100644
--- a/config/src/main/kotlin/org/springframework/security/config/web/servlet/oauth2/login/RedirectionEndpointDsl.kt
+++ b/config/src/main/kotlin/org/springframework/security/config/web/servlet/oauth2/login/RedirectionEndpointDsl.kt
@@ -18,7 +18,6 @@ package org.springframework.security.config.web.servlet.oauth2.login
import org.springframework.security.config.annotation.web.builders.HttpSecurity
import org.springframework.security.config.annotation.web.configurers.oauth2.client.OAuth2LoginConfigurer
-import org.springframework.security.config.web.servlet.SecurityMarker
/**
* A Kotlin DSL to configure the Authorization Server's Redirection Endpoint using
@@ -28,7 +27,7 @@ import org.springframework.security.config.web.servlet.SecurityMarker
* @since 5.3
* @property baseUri the URI where the authorization response will be processed.
*/
-@SecurityMarker
+@OAuth2LoginSecurityMarker
class RedirectionEndpointDsl {
var baseUri: String? = null
diff --git a/config/src/main/kotlin/org/springframework/security/config/web/servlet/oauth2/login/TokenEndpointDsl.kt b/config/src/main/kotlin/org/springframework/security/config/web/servlet/oauth2/login/TokenEndpointDsl.kt
index 0997c15e878..ddba776d551 100644
--- a/config/src/main/kotlin/org/springframework/security/config/web/servlet/oauth2/login/TokenEndpointDsl.kt
+++ b/config/src/main/kotlin/org/springframework/security/config/web/servlet/oauth2/login/TokenEndpointDsl.kt
@@ -18,7 +18,6 @@ package org.springframework.security.config.web.servlet.oauth2.login
import org.springframework.security.config.annotation.web.builders.HttpSecurity
import org.springframework.security.config.annotation.web.configurers.oauth2.client.OAuth2LoginConfigurer
-import org.springframework.security.config.web.servlet.SecurityMarker
import org.springframework.security.oauth2.client.endpoint.OAuth2AccessTokenResponseClient
import org.springframework.security.oauth2.client.endpoint.OAuth2AuthorizationCodeGrantRequest
@@ -31,7 +30,7 @@ import org.springframework.security.oauth2.client.endpoint.OAuth2AuthorizationCo
* @property accessTokenResponseClient the client used for requesting the access token credential
* from the Token Endpoint.
*/
-@SecurityMarker
+@OAuth2LoginSecurityMarker
class TokenEndpointDsl {
var accessTokenResponseClient: OAuth2AccessTokenResponseClient? = null
diff --git a/config/src/main/kotlin/org/springframework/security/config/web/servlet/oauth2/login/UserInfoEndpointDsl.kt b/config/src/main/kotlin/org/springframework/security/config/web/servlet/oauth2/login/UserInfoEndpointDsl.kt
index f2e30083d6d..3ef981e9584 100644
--- a/config/src/main/kotlin/org/springframework/security/config/web/servlet/oauth2/login/UserInfoEndpointDsl.kt
+++ b/config/src/main/kotlin/org/springframework/security/config/web/servlet/oauth2/login/UserInfoEndpointDsl.kt
@@ -18,7 +18,6 @@ package org.springframework.security.config.web.servlet.oauth2.login
import org.springframework.security.config.annotation.web.builders.HttpSecurity
import org.springframework.security.config.annotation.web.configurers.oauth2.client.OAuth2LoginConfigurer
-import org.springframework.security.config.web.servlet.SecurityMarker
import org.springframework.security.core.authority.mapping.GrantedAuthoritiesMapper
import org.springframework.security.oauth2.client.oidc.userinfo.OidcUserRequest
import org.springframework.security.oauth2.client.registration.ClientRegistration
@@ -39,7 +38,7 @@ import org.springframework.security.oauth2.core.user.OAuth2User
* End-User from the UserInfo Endpoint.
* @property userAuthoritiesMapper the [GrantedAuthoritiesMapper] used for mapping [OAuth2User.getAuthorities]
*/
-@SecurityMarker
+@OAuth2LoginSecurityMarker
class UserInfoEndpointDsl {
var userService: OAuth2UserService? = null
var oidcUserService: OAuth2UserService? = null
@@ -58,6 +57,18 @@ class UserInfoEndpointDsl {
customUserTypePair = Pair(customUserType, clientRegistrationId)
}
+ /**
+ * Sets a custom [OAuth2User] type and associates it to the provided
+ * client [ClientRegistration.getRegistrationId] registration identifier.
+ * Variant that is leveraging Kotlin reified type parameters.
+ *
+ * @param T a custom [OAuth2User] type
+ * @param clientRegistrationId the client registration identifier
+ */
+ inline fun customUserType(clientRegistrationId: String) {
+ customUserType(T::class.java, clientRegistrationId)
+ }
+
internal fun get(): (OAuth2LoginConfigurer.UserInfoEndpointConfig) -> Unit {
return { userInfoEndpoint ->
userService?.also { userInfoEndpoint.userService(userService) }
diff --git a/config/src/main/kotlin/org/springframework/security/config/web/servlet/oauth2/resourceserver/JwtDsl.kt b/config/src/main/kotlin/org/springframework/security/config/web/servlet/oauth2/resourceserver/JwtDsl.kt
index 2236aa4d101..e8d8008a974 100644
--- a/config/src/main/kotlin/org/springframework/security/config/web/servlet/oauth2/resourceserver/JwtDsl.kt
+++ b/config/src/main/kotlin/org/springframework/security/config/web/servlet/oauth2/resourceserver/JwtDsl.kt
@@ -20,7 +20,6 @@ import org.springframework.core.convert.converter.Converter
import org.springframework.security.authentication.AbstractAuthenticationToken
import org.springframework.security.config.annotation.web.builders.HttpSecurity
import org.springframework.security.config.annotation.web.configurers.oauth2.server.resource.OAuth2ResourceServerConfigurer
-import org.springframework.security.config.web.servlet.SecurityMarker
import org.springframework.security.oauth2.jwt.Jwt
import org.springframework.security.oauth2.jwt.JwtDecoder
@@ -35,7 +34,7 @@ import org.springframework.security.oauth2.jwt.JwtDecoder
* @property jwkSetUri configures a [JwtDecoder] using a
* JSON Web Key (JWK) URL
*/
-@SecurityMarker
+@OAuth2ResourceServerSecurityMarker
class JwtDsl {
private var _jwtDecoder: JwtDecoder? = null
private var _jwkSetUri: String? = null
diff --git a/config/src/main/kotlin/org/springframework/security/config/web/servlet/oauth2/resourceserver/OAuth2ResourceServerSecurityMarker.kt b/config/src/main/kotlin/org/springframework/security/config/web/servlet/oauth2/resourceserver/OAuth2ResourceServerSecurityMarker.kt
new file mode 100644
index 00000000000..c561531ae97
--- /dev/null
+++ b/config/src/main/kotlin/org/springframework/security/config/web/servlet/oauth2/resourceserver/OAuth2ResourceServerSecurityMarker.kt
@@ -0,0 +1,26 @@
+/*
+ * Copyright 2002-2020 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.oauth2.resourceserver
+
+/**
+ * Marker annotation indicating that the annotated class is part of the OAuth 2.0 resource server security DSL.
+ *
+ * @author Eleftheria Stein
+ * @since 5.4
+ */
+@DslMarker
+annotation class OAuth2ResourceServerSecurityMarker
diff --git a/config/src/main/kotlin/org/springframework/security/config/web/servlet/oauth2/resourceserver/OpaqueTokenDsl.kt b/config/src/main/kotlin/org/springframework/security/config/web/servlet/oauth2/resourceserver/OpaqueTokenDsl.kt
index 062509cb0ec..f9123796548 100644
--- a/config/src/main/kotlin/org/springframework/security/config/web/servlet/oauth2/resourceserver/OpaqueTokenDsl.kt
+++ b/config/src/main/kotlin/org/springframework/security/config/web/servlet/oauth2/resourceserver/OpaqueTokenDsl.kt
@@ -18,7 +18,6 @@ package org.springframework.security.config.web.servlet.oauth2.resourceserver
import org.springframework.security.config.annotation.web.builders.HttpSecurity
import org.springframework.security.config.annotation.web.configurers.oauth2.server.resource.OAuth2ResourceServerConfigurer
-import org.springframework.security.config.web.servlet.SecurityMarker
import org.springframework.security.oauth2.server.resource.introspection.OpaqueTokenIntrospector
/**
@@ -29,7 +28,7 @@ import org.springframework.security.oauth2.server.resource.introspection.OpaqueT
* @property introspectionUri the URI of the Introspection endpoint.
* @property introspector the [OpaqueTokenIntrospector] to use.
*/
-@SecurityMarker
+@OAuth2ResourceServerSecurityMarker
class OpaqueTokenDsl {
private var _introspectionUri: String? = null
private var _introspector: OpaqueTokenIntrospector? = null
diff --git a/config/src/main/kotlin/org/springframework/security/config/web/servlet/session/SessionConcurrencyDsl.kt b/config/src/main/kotlin/org/springframework/security/config/web/servlet/session/SessionConcurrencyDsl.kt
index d673cccb69d..e0af442a9c0 100644
--- a/config/src/main/kotlin/org/springframework/security/config/web/servlet/session/SessionConcurrencyDsl.kt
+++ b/config/src/main/kotlin/org/springframework/security/config/web/servlet/session/SessionConcurrencyDsl.kt
@@ -18,7 +18,6 @@ package org.springframework.security.config.web.servlet.session
import org.springframework.security.config.annotation.web.builders.HttpSecurity
import org.springframework.security.config.annotation.web.configurers.SessionManagementConfigurer
-import org.springframework.security.config.web.servlet.SecurityMarker
import org.springframework.security.core.session.SessionRegistry
import org.springframework.security.web.session.SessionInformationExpiredStrategy
@@ -38,7 +37,7 @@ import org.springframework.security.web.session.SessionInformationExpiredStrateg
* is allowed access and an existing user's session is expired.
* @property sessionRegistry the [SessionRegistry] implementation used.
*/
-@SecurityMarker
+@SessionSecurityMarker
class SessionConcurrencyDsl {
var maximumSessions: Int? = null
var expiredUrl: String? = null
diff --git a/config/src/main/kotlin/org/springframework/security/config/web/servlet/session/SessionFixationDsl.kt b/config/src/main/kotlin/org/springframework/security/config/web/servlet/session/SessionFixationDsl.kt
index a5ee7188a00..b02a7d52746 100644
--- a/config/src/main/kotlin/org/springframework/security/config/web/servlet/session/SessionFixationDsl.kt
+++ b/config/src/main/kotlin/org/springframework/security/config/web/servlet/session/SessionFixationDsl.kt
@@ -18,7 +18,6 @@ package org.springframework.security.config.web.servlet.session
import org.springframework.security.config.annotation.web.builders.HttpSecurity
import org.springframework.security.config.annotation.web.configurers.SessionManagementConfigurer
-import org.springframework.security.config.web.servlet.SecurityMarker
import javax.servlet.http.HttpServletRequest
import javax.servlet.http.HttpSession
@@ -29,7 +28,7 @@ import javax.servlet.http.HttpSession
* @author Eleftheria Stein
* @since 5.3
*/
-@SecurityMarker
+@SessionSecurityMarker
class SessionFixationDsl {
private var strategy: SessionFixationStrategy? = null
diff --git a/config/src/main/kotlin/org/springframework/security/config/web/servlet/session/SessionSecurityMarker.kt b/config/src/main/kotlin/org/springframework/security/config/web/servlet/session/SessionSecurityMarker.kt
new file mode 100644
index 00000000000..6e5ef671b7a
--- /dev/null
+++ b/config/src/main/kotlin/org/springframework/security/config/web/servlet/session/SessionSecurityMarker.kt
@@ -0,0 +1,26 @@
+/*
+ * Copyright 2002-2020 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.session
+
+/**
+ * Marker annotation indicating that the annotated class is part of the session security DSL.
+ *
+ * @author Eleftheria Stein
+ * @since 5.4
+ */
+@DslMarker
+annotation class SessionSecurityMarker
diff --git a/config/src/main/resources/META-INF/spring.schemas b/config/src/main/resources/META-INF/spring.schemas
index 910737e6ae0..3ab72af7124 100644
--- a/config/src/main/resources/META-INF/spring.schemas
+++ b/config/src/main/resources/META-INF/spring.schemas
@@ -1,4 +1,5 @@
-http\://www.springframework.org/schema/security/spring-security.xsd=org/springframework/security/config/spring-security-5.3.xsd
+http\://www.springframework.org/schema/security/spring-security.xsd=org/springframework/security/config/spring-security-5.4.xsd
+http\://www.springframework.org/schema/security/spring-security-5.4.xsd=org/springframework/security/config/spring-security-5.4.xsd
http\://www.springframework.org/schema/security/spring-security-5.3.xsd=org/springframework/security/config/spring-security-5.3.xsd
http\://www.springframework.org/schema/security/spring-security-5.2.xsd=org/springframework/security/config/spring-security-5.2.xsd
http\://www.springframework.org/schema/security/spring-security-5.1.xsd=org/springframework/security/config/spring-security-5.1.xsd
@@ -14,7 +15,8 @@ http\://www.springframework.org/schema/security/spring-security-2.0.xsd=org/spri
http\://www.springframework.org/schema/security/spring-security-2.0.1.xsd=org/springframework/security/config/spring-security-2.0.1.xsd
http\://www.springframework.org/schema/security/spring-security-2.0.2.xsd=org/springframework/security/config/spring-security-2.0.2.xsd
http\://www.springframework.org/schema/security/spring-security-2.0.4.xsd=org/springframework/security/config/spring-security-2.0.4.xsd
-https\://www.springframework.org/schema/security/spring-security.xsd=org/springframework/security/config/spring-security-5.3.xsd
+https\://www.springframework.org/schema/security/spring-security.xsd=org/springframework/security/config/spring-security-5.4.xsd
+https\://www.springframework.org/schema/security/spring-security-5.4.xsd=org/springframework/security/config/spring-security-5.4.xsd
https\://www.springframework.org/schema/security/spring-security-5.3.xsd=org/springframework/security/config/spring-security-5.3.xsd
https\://www.springframework.org/schema/security/spring-security-5.2.xsd=org/springframework/security/config/spring-security-5.2.xsd
https\://www.springframework.org/schema/security/spring-security-5.1.xsd=org/springframework/security/config/spring-security-5.1.xsd
diff --git a/config/src/main/resources/org/springframework/security/config/spring-security-5.4.rnc b/config/src/main/resources/org/springframework/security/config/spring-security-5.4.rnc
new file mode 100644
index 00000000000..8307f525200
--- /dev/null
+++ b/config/src/main/resources/org/springframework/security/config/spring-security-5.4.rnc
@@ -0,0 +1,1099 @@
+namespace a = "https://relaxng.org/ns/compatibility/annotations/1.0"
+datatypes xsd = "http://www.w3.org/2001/XMLSchema-datatypes"
+
+default namespace = "http://www.springframework.org/schema/security"
+
+start = http | ldap-server | authentication-provider | ldap-authentication-provider | any-user-service | ldap-server | ldap-authentication-provider
+
+hash =
+ ## Defines the hashing algorithm used on user passwords. Bcrypt is recommended.
+ attribute hash {"bcrypt"}
+base64 =
+ ## Whether a string should be base64 encoded
+ attribute base64 {xsd:boolean}
+request-matcher =
+ ## Defines the strategy use for matching incoming requests. Currently the options are 'mvc' (for Spring MVC matcher), 'ant' (for ant path patterns), 'regex' for regular expressions and 'ciRegex' for case-insensitive regular expressions.
+ attribute request-matcher {"mvc" | "ant" | "regex" | "ciRegex"}
+port =
+ ## Specifies an IP port number. Used to configure an embedded LDAP server, for example.
+ attribute port { xsd:nonNegativeInteger }
+url =
+ ## Specifies a URL.
+ attribute url { xsd:token }
+id =
+ ## A bean identifier, used for referring to the bean elsewhere in the context.
+ attribute id {xsd:token}
+name =
+ ## A bean identifier, used for referring to the bean elsewhere in the context.
+ attribute name {xsd:token}
+ref =
+ ## Defines a reference to a Spring bean Id.
+ attribute ref {xsd:token}
+
+cache-ref =
+ ## Defines a reference to a cache for use with a UserDetailsService.
+ attribute cache-ref {xsd:token}
+
+user-service-ref =
+ ## A reference to a user-service (or UserDetailsService bean) Id
+ attribute user-service-ref {xsd:token}
+
+authentication-manager-ref =
+ ## A reference to an AuthenticationManager bean
+ attribute authentication-manager-ref {xsd:token}
+
+data-source-ref =
+ ## A reference to a DataSource bean
+ attribute data-source-ref {xsd:token}
+
+
+
+debug =
+ ## Enables Spring Security debugging infrastructure. This will provide human-readable (multi-line) debugging information to monitor requests coming into the security filters. This may include sensitive information, such as request parameters or headers, and should only be used in a development environment.
+ element debug {empty}
+
+password-encoder =
+ ## element which defines a password encoding strategy. Used by an authentication provider to convert submitted passwords to hashed versions, for example.
+ element password-encoder {password-encoder.attlist}
+password-encoder.attlist &=
+ ref | (hash)
+
+role-prefix =
+ ## A non-empty string prefix that will be added to role strings loaded from persistent storage (e.g. "ROLE_"). Use the value "none" for no prefix in cases where the default is non-empty.
+ attribute role-prefix {xsd:token}
+
+use-expressions =
+ ## Enables the use of expressions in the 'access' attributes in elements rather than the traditional list of configuration attributes. Defaults to 'true'. If enabled, each attribute should contain a single boolean expression. If the expression evaluates to 'true', access will be granted.
+ attribute use-expressions {xsd:boolean}
+
+ldap-server =
+ ## Defines an LDAP server location or starts an embedded server. The url indicates the location of a remote server. If no url is given, an embedded server will be started, listening on the supplied port number. The port is optional and defaults to 33389. A Spring LDAP ContextSource bean will be registered for the server with the id supplied.
+ element ldap-server {ldap-server.attlist}
+ldap-server.attlist &= id?
+ldap-server.attlist &= (url | port)?
+ldap-server.attlist &=
+ ## Username (DN) of the "manager" user identity which will be used to authenticate to a (non-embedded) LDAP server. If omitted, anonymous access will be used.
+ attribute manager-dn {xsd:string}?
+ldap-server.attlist &=
+ ## The password for the manager DN. This is required if the manager-dn is specified.
+ attribute manager-password {xsd:string}?
+ldap-server.attlist &=
+ ## Explicitly specifies an ldif file resource to load into an embedded LDAP server. The default is classpath*:*.ldiff
+ attribute ldif { xsd:string }?
+ldap-server.attlist &=
+ ## Optional root suffix for the embedded LDAP server. Default is "dc=springframework,dc=org"
+ attribute root { xsd:string }?
+ldap-server.attlist &=
+ ## Explicitly specifies which embedded ldap server should use. Values are 'apacheds' and 'unboundid'. By default, it will depends if the library is available in the classpath.
+ attribute mode { "apacheds" | "unboundid" }?
+
+ldap-server-ref-attribute =
+ ## The optional server to use. If omitted, and a default LDAP server is registered (using with no Id), that server will be used.
+ attribute server-ref {xsd:token}
+
+
+group-search-filter-attribute =
+ ## Group search filter. Defaults to (uniqueMember={0}). The substituted parameter is the DN of the user.
+ attribute group-search-filter {xsd:token}
+group-search-base-attribute =
+ ## Search base for group membership searches. Defaults to "" (searching from the root).
+ attribute group-search-base {xsd:token}
+user-search-filter-attribute =
+ ## The LDAP filter used to search for users (optional). For example "(uid={0})". The substituted parameter is the user's login name.
+ attribute user-search-filter {xsd:token}
+user-search-base-attribute =
+ ## Search base for user searches. Defaults to "". Only used with a 'user-search-filter'.
+ attribute user-search-base {xsd:token}
+group-role-attribute-attribute =
+ ## The LDAP attribute name which contains the role name which will be used within Spring Security. Defaults to "cn".
+ attribute group-role-attribute {xsd:token}
+user-details-class-attribute =
+ ## Allows the objectClass of the user entry to be specified. If set, the framework will attempt to load standard attributes for the defined class into the returned UserDetails object
+ attribute user-details-class {"person" | "inetOrgPerson"}
+user-context-mapper-attribute =
+ ## Allows explicit customization of the loaded user object by specifying a UserDetailsContextMapper bean which will be called with the context information from the user's directory entry
+ attribute user-context-mapper-ref {xsd:token}
+
+
+ldap-user-service =
+ ## This element configures a LdapUserDetailsService which is a combination of a FilterBasedLdapUserSearch and a DefaultLdapAuthoritiesPopulator.
+ element ldap-user-service {ldap-us.attlist}
+ldap-us.attlist &= id?
+ldap-us.attlist &=
+ ldap-server-ref-attribute?
+ldap-us.attlist &=
+ user-search-filter-attribute?
+ldap-us.attlist &=
+ user-search-base-attribute?
+ldap-us.attlist &=
+ group-search-filter-attribute?
+ldap-us.attlist &=
+ group-search-base-attribute?
+ldap-us.attlist &=
+ group-role-attribute-attribute?
+ldap-us.attlist &=
+ cache-ref?
+ldap-us.attlist &=
+ role-prefix?
+ldap-us.attlist &=
+ (user-details-class-attribute | user-context-mapper-attribute)?
+
+ldap-authentication-provider =
+ ## Sets up an ldap authentication provider
+ element ldap-authentication-provider {ldap-ap.attlist, password-compare-element?}
+ldap-ap.attlist &=
+ ldap-server-ref-attribute?
+ldap-ap.attlist &=
+ user-search-base-attribute?
+ldap-ap.attlist &=
+ user-search-filter-attribute?
+ldap-ap.attlist &=
+ group-search-base-attribute?
+ldap-ap.attlist &=
+ group-search-filter-attribute?
+ldap-ap.attlist &=
+ group-role-attribute-attribute?
+ldap-ap.attlist &=
+ ## A specific pattern used to build the user's DN, for example "uid={0},ou=people". The key "{0}" must be present and will be substituted with the username.
+ attribute user-dn-pattern {xsd:token}?
+ldap-ap.attlist &=
+ role-prefix?
+ldap-ap.attlist &=
+ (user-details-class-attribute | user-context-mapper-attribute)?
+
+password-compare-element =
+ ## Specifies that an LDAP provider should use an LDAP compare operation of the user's password to authenticate the user
+ element password-compare {password-compare.attlist, password-encoder?}
+
+password-compare.attlist &=
+ ## The attribute in the directory which contains the user password. Defaults to "userPassword".
+ attribute password-attribute {xsd:token}?
+password-compare.attlist &=
+ hash?
+
+intercept-methods =
+ ## Can be used inside a bean definition to add a security interceptor to the bean and set up access configuration attributes for the bean's methods
+ element intercept-methods {intercept-methods.attlist, protect+}
+intercept-methods.attlist &=
+ ## Optional AccessDecisionManager bean ID to be used by the created method security interceptor.
+ attribute access-decision-manager-ref {xsd:token}?
+
+
+protect =
+ ## Defines a protected method and the access control configuration attributes that apply to it. We strongly advise you NOT to mix "protect" declarations with any services provided "global-method-security".
+ element protect {protect.attlist, empty}
+protect.attlist &=
+ ## A method name
+ attribute method {xsd:token}
+protect.attlist &=
+ ## Access configuration attributes list that applies to the method, e.g. "ROLE_A,ROLE_B".
+ attribute access {xsd:token}
+
+method-security-metadata-source =
+ ## Creates a MethodSecurityMetadataSource instance
+ element method-security-metadata-source {msmds.attlist, protect+}
+msmds.attlist &= id?
+
+msmds.attlist &= use-expressions?
+
+global-method-security =
+ ## Provides method security for all beans registered in the Spring application context. Specifically, beans will be scanned for matches with the ordered list of "protect-pointcut" sub-elements, Spring Security annotations and/or. Where there is a match, the beans will automatically be proxied and security authorization applied to the methods accordingly. If you use and enable all four sources of method security metadata (ie "protect-pointcut" declarations, expression annotations, @Secured and also JSR250 security annotations), the metadata sources will be queried in that order. In practical terms, this enables you to use XML to override method security metadata expressed in annotations. If using annotations, the order of precedence is EL-based (@PreAuthorize etc.), @Secured and finally JSR-250.
+ element global-method-security {global-method-security.attlist, (pre-post-annotation-handling | expression-handler)?, protect-pointcut*, after-invocation-provider*}
+global-method-security.attlist &=
+ ## Specifies whether the use of Spring Security's pre and post invocation annotations (@PreFilter, @PreAuthorize, @PostFilter, @PostAuthorize) should be enabled for this application context. Defaults to "disabled".
+ attribute pre-post-annotations {"disabled" | "enabled" }?
+global-method-security.attlist &=
+ ## Specifies whether the use of Spring Security's @Secured annotations should be enabled for this application context. Defaults to "disabled".
+ attribute secured-annotations {"disabled" | "enabled" }?
+global-method-security.attlist &=
+ ## Specifies whether JSR-250 style attributes are to be used (for example "RolesAllowed"). This will require the javax.annotation.security classes on the classpath. Defaults to "disabled".
+ attribute jsr250-annotations {"disabled" | "enabled" }?
+global-method-security.attlist &=
+ ## Optional AccessDecisionManager bean ID to override the default used for method security.
+ attribute access-decision-manager-ref {xsd:token}?
+global-method-security.attlist &=
+ ## Optional RunAsmanager implementation which will be used by the configured MethodSecurityInterceptor
+ attribute run-as-manager-ref {xsd:token}?
+global-method-security.attlist &=
+ ## Allows the advice "order" to be set for the method security interceptor.
+ attribute order {xsd:token}?
+global-method-security.attlist &=
+ ## If true, class based proxying will be used instead of interface based proxying.
+ attribute proxy-target-class {xsd:boolean}?
+global-method-security.attlist &=
+ ## Can be used to specify that AspectJ should be used instead of the default Spring AOP. If set, secured classes must be woven with the AnnotationSecurityAspect from the spring-security-aspects module.
+ attribute mode {"aspectj"}?
+global-method-security.attlist &=
+ ## An external MethodSecurityMetadataSource instance can be supplied which will take priority over other sources (such as the default annotations).
+ attribute metadata-source-ref {xsd:token}?
+global-method-security.attlist &=
+ authentication-manager-ref?
+
+
+after-invocation-provider =
+ ## Allows addition of extra AfterInvocationProvider beans which should be called by the MethodSecurityInterceptor created by global-method-security.
+ element after-invocation-provider {ref}
+
+pre-post-annotation-handling =
+ ## Allows the default expression-based mechanism for handling Spring Security's pre and post invocation annotations (@PreFilter, @PreAuthorize, @PostFilter, @PostAuthorize) to be replace entirely. Only applies if these annotations are enabled.
+ element pre-post-annotation-handling {invocation-attribute-factory, pre-invocation-advice, post-invocation-advice}
+
+invocation-attribute-factory =
+ ## Defines the PrePostInvocationAttributeFactory instance which is used to generate pre and post invocation metadata from the annotated methods.
+ element invocation-attribute-factory {ref}
+
+pre-invocation-advice =
+ ## Customizes the PreInvocationAuthorizationAdviceVoter with the ref as the PreInvocationAuthorizationAdviceVoter for the element.
+ element pre-invocation-advice {ref}
+
+post-invocation-advice =
+ ## Customizes the PostInvocationAdviceProvider with the ref as the PostInvocationAuthorizationAdvice for the element.
+ element post-invocation-advice {ref}
+
+
+expression-handler =
+ ## Defines the SecurityExpressionHandler instance which will be used if expression-based access-control is enabled. A default implementation (with no ACL support) will be used if not supplied.
+ element expression-handler {ref}
+
+protect-pointcut =
+ ## Defines a protected pointcut and the access control configuration attributes that apply to it. Every bean registered in the Spring application context that provides a method that matches the pointcut will receive security authorization.
+ element protect-pointcut {protect-pointcut.attlist, empty}
+protect-pointcut.attlist &=
+ ## An AspectJ expression, including the 'execution' keyword. For example, 'execution(int com.foo.TargetObject.countLength(String))' (without the quotes).
+ attribute expression {xsd:string}
+protect-pointcut.attlist &=
+ ## Access configuration attributes list that applies to all methods matching the pointcut, e.g. "ROLE_A,ROLE_B"
+ attribute access {xsd:token}
+
+websocket-message-broker =
+ ## Allows securing a Message Broker. There are two modes. If no id is specified: ensures that any SimpAnnotationMethodMessageHandler has the AuthenticationPrincipalArgumentResolver registered as a custom argument resolver; ensures that the SecurityContextChannelInterceptor is automatically registered for the clientInboundChannel; and that a ChannelSecurityInterceptor is registered with the clientInboundChannel. If the id is specified, creates a ChannelSecurityInterceptor that can be manually registered with the clientInboundChannel.
+ element websocket-message-broker { websocket-message-broker.attrlist, (intercept-message* & expression-handler?) }
+
+websocket-message-broker.attrlist &=
+ ## A bean identifier, used for referring to the bean elsewhere in the context. If specified, explicit configuration within clientInboundChannel is required. If not specified, ensures that any SimpAnnotationMethodMessageHandler has the AuthenticationPrincipalArgumentResolver registered as a custom argument resolver; ensures that the SecurityContextChannelInterceptor is automatically registered for the clientInboundChannel; and that a ChannelSecurityInterceptor is registered with the clientInboundChannel.
+ attribute id {xsd:token}?
+websocket-message-broker.attrlist &=
+ ## Disables the requirement for CSRF token to be present in the Stomp headers (default false). Changing the default is useful if it is necessary to allow other origins to make SockJS connections.
+ attribute same-origin-disabled {xsd:boolean}?
+
+intercept-message =
+ ## Creates an authorization rule for a websocket message.
+ element intercept-message {intercept-message.attrlist}
+
+intercept-message.attrlist &=
+ ## The destination ant pattern which will be mapped to the access attribute. For example, /** matches any message with a destination, /admin/** matches any message that has a destination that starts with admin.
+ attribute pattern {xsd:token}?
+intercept-message.attrlist &=
+ ## The access configuration attributes that apply for the configured message. For example, permitAll grants access to anyone, hasRole('ROLE_ADMIN') requires the user have the role 'ROLE_ADMIN'.
+ attribute access {xsd:token}?
+intercept-message.attrlist &=
+ ## The type of message to match on. Valid values are defined in SimpMessageType (i.e. CONNECT, CONNECT_ACK, HEARTBEAT, MESSAGE, SUBSCRIBE, UNSUBSCRIBE, DISCONNECT, DISCONNECT_ACK, OTHER).
+ attribute type {"CONNECT" | "CONNECT_ACK" | "HEARTBEAT" | "MESSAGE" | "SUBSCRIBE"| "UNSUBSCRIBE" | "DISCONNECT" | "DISCONNECT_ACK" | "OTHER"}?
+
+http-firewall =
+ ## Allows a custom instance of HttpFirewall to be injected into the FilterChainProxy created by the namespace.
+ element http-firewall {ref}
+
+http =
+ ## Container element for HTTP security configuration. Multiple elements can now be defined, each with a specific pattern to which the enclosed security configuration applies. A pattern can also be configured to bypass Spring Security's filters completely by setting the "security" attribute to "none".
+ element http {http.attlist, (intercept-url* & access-denied-handler? & form-login? & oauth2-login? & oauth2-client? & oauth2-resource-server? & openid-login? & x509? & jee? & http-basic? & logout? & session-management & remember-me? & anonymous? & port-mappings & custom-filter* & request-cache? & expression-handler? & headers? & csrf? & cors?) }
+http.attlist &=
+ ## The request URL pattern which will be mapped to the filter chain created by this element. If omitted, the filter chain will match all requests.
+ attribute pattern {xsd:token}?
+http.attlist &=
+ ## When set to 'none', requests matching the pattern attribute will be ignored by Spring Security. No security filters will be applied and no SecurityContext will be available. If set, the element must be empty, with no children.
+ attribute security {"none"}?
+http.attlist &=
+ ## Allows a RequestMatcher instance to be used, as an alternative to pattern-matching.
+ attribute request-matcher-ref { xsd:token }?
+http.attlist &=
+ ## A legacy attribute which automatically registers a login form, BASIC authentication and a logout URL and logout services. If unspecified, defaults to "false". We'd recommend you avoid using this and instead explicitly configure the services you require.
+ attribute auto-config {xsd:boolean}?
+http.attlist &=
+ use-expressions?
+http.attlist &=
+ ## Controls the eagerness with which an HTTP session is created by Spring Security classes. If not set, defaults to "ifRequired". If "stateless" is used, this implies that the application guarantees that it will not create a session. This differs from the use of "never" which means that Spring Security will not create a session, but will make use of one if the application does.
+ attribute create-session {"ifRequired" | "always" | "never" | "stateless"}?
+http.attlist &=
+ ## A reference to a SecurityContextRepository bean. This can be used to customize how the SecurityContext is stored between requests.
+ attribute security-context-repository-ref {xsd:token}?
+http.attlist &=
+ request-matcher?
+http.attlist &=
+ ## Provides versions of HttpServletRequest security methods such as isUserInRole() and getPrincipal() which are implemented by accessing the Spring SecurityContext. Defaults to "true".
+ attribute servlet-api-provision {xsd:boolean}?
+http.attlist &=
+ ## If available, runs the request as the Subject acquired from the JaasAuthenticationToken. Defaults to "false".
+ attribute jaas-api-provision {xsd:boolean}?
+http.attlist &=
+ ## Optional attribute specifying the ID of the AccessDecisionManager implementation which should be used for authorizing HTTP requests.
+ attribute access-decision-manager-ref {xsd:token}?
+http.attlist &=
+ ## Optional attribute specifying the realm name that will be used for all authentication features that require a realm name (eg BASIC and Digest authentication). If unspecified, defaults to "Spring Security Application".
+ attribute realm {xsd:token}?
+http.attlist &=
+ ## Allows a customized AuthenticationEntryPoint to be set on the ExceptionTranslationFilter.
+ attribute entry-point-ref {xsd:token}?
+http.attlist &=
+ ## Corresponds to the observeOncePerRequest property of FilterSecurityInterceptor. Defaults to "true"
+ attribute once-per-request {xsd:boolean}?
+http.attlist &=
+ ## Prevents the jsessionid parameter from being added to rendered URLs. Defaults to "true" (rewriting is disabled).
+ attribute disable-url-rewriting {xsd:boolean}?
+http.attlist &=
+ ## Exposes the list of filters defined by this configuration under this bean name in the application context.
+ name?
+http.attlist &=
+ authentication-manager-ref?
+
+access-denied-handler =
+ ## Defines the access-denied strategy that should be used. An access denied page can be defined or a reference to an AccessDeniedHandler instance.
+ element access-denied-handler {access-denied-handler.attlist, empty}
+access-denied-handler.attlist &= (ref | access-denied-handler-page)
+
+access-denied-handler-page =
+ ## The access denied page that an authenticated user will be redirected to if they request a page which they don't have the authority to access.
+ attribute error-page {xsd:token}
+
+intercept-url =
+ ## Specifies the access attributes and/or filter list for a particular set of URLs.
+ element intercept-url {intercept-url.attlist, empty}
+intercept-url.attlist &=
+ (pattern | request-matcher-ref)
+intercept-url.attlist &=
+ ## The access configuration attributes that apply for the configured path.
+ attribute access {xsd:token}?
+intercept-url.attlist &=
+ ## The HTTP Method for which the access configuration attributes should apply. If not specified, the attributes will apply to any method.
+ attribute method {"GET" | "DELETE" | "HEAD" | "OPTIONS" | "POST" | "PUT" | "PATCH" | "TRACE"}?
+
+intercept-url.attlist &=
+ ## Used to specify that a URL must be accessed over http or https, or that there is no preference. The value should be "http", "https" or "any", respectively.
+ attribute requires-channel {xsd:token}?
+intercept-url.attlist &=
+ ## The path to the servlet. This attribute is only applicable when 'request-matcher' is 'mvc'. In addition, the value is only required in the following 2 use cases: 1) There are 2 or more HttpServlet's registered in the ServletContext that have mappings starting with '/' and are different; 2) The pattern starts with the same value of a registered HttpServlet path, excluding the default (root) HttpServlet '/'.
+ attribute servlet-path {xsd:token}?
+
+logout =
+ ## Incorporates a logout processing filter. Most web applications require a logout filter, although you may not require one if you write a controller to provider similar logic.
+ element logout {logout.attlist, empty}
+logout.attlist &=
+ ## Specifies the URL that will cause a logout. Spring Security will initialize a filter that responds to this particular URL. Defaults to /logout if unspecified.
+ attribute logout-url {xsd:token}?
+logout.attlist &=
+ ## Specifies the URL to display once the user has logged out. If not specified, defaults to /?logout (i.e. /login?logout).
+ attribute logout-success-url {xsd:token}?
+logout.attlist &=
+ ## Specifies whether a logout also causes HttpSession invalidation, which is generally desirable. If unspecified, defaults to true.
+ attribute invalidate-session {xsd:boolean}?
+logout.attlist &=
+ ## A reference to a LogoutSuccessHandler implementation which will be used to determine the destination to which the user is taken after logging out.
+ attribute success-handler-ref {xsd:token}?
+logout.attlist &=
+ ## A comma-separated list of the names of cookies which should be deleted when the user logs out
+ attribute delete-cookies {xsd:token}?
+
+request-cache =
+ ## Allow the RequestCache used for saving requests during the login process to be set
+ element request-cache {ref}
+
+form-login =
+ ## Sets up a form login configuration for authentication with a username and password
+ element form-login {form-login.attlist, empty}
+form-login.attlist &=
+ ## The URL that the login form is posted to. If unspecified, it defaults to /login.
+ attribute login-processing-url {xsd:token}?
+form-login.attlist &=
+ ## The name of the request parameter which contains the username. Defaults to 'username'.
+ attribute username-parameter {xsd:token}?
+form-login.attlist &=
+ ## The name of the request parameter which contains the password. Defaults to 'password'.
+ attribute password-parameter {xsd:token}?
+form-login.attlist &=
+ ## The URL that will be redirected to after successful authentication, if the user's previous action could not be resumed. This generally happens if the user visits a login page without having first requested a secured operation that triggers authentication. If unspecified, defaults to the root of the application.
+ attribute default-target-url {xsd:token}?
+form-login.attlist &=
+ ## Whether the user should always be redirected to the default-target-url after login.
+ attribute always-use-default-target {xsd:boolean}?
+form-login.attlist &=
+ ## The URL for the login page. If no login URL is specified, Spring Security will automatically create a login URL at GET /login and a corresponding filter to render that login URL when requested.
+ attribute login-page {xsd:token}?
+form-login.attlist &=
+ ## The URL for the login failure page. If no login failure URL is specified, Spring Security will automatically create a failure login URL at /login?error and a corresponding filter to render that login failure URL when requested.
+ attribute authentication-failure-url {xsd:token}?
+form-login.attlist &=
+ ## Reference to an AuthenticationSuccessHandler bean which should be used to handle a successful authentication request. Should not be used in combination with default-target-url (or always-use-default-target-url) as the implementation should always deal with navigation to the subsequent destination
+ attribute authentication-success-handler-ref {xsd:token}?
+form-login.attlist &=
+ ## Reference to an AuthenticationFailureHandler bean which should be used to handle a failed authentication request. Should not be used in combination with authentication-failure-url as the implementation should always deal with navigation to the subsequent destination
+ attribute authentication-failure-handler-ref {xsd:token}?
+form-login.attlist &=
+ ## Reference to an AuthenticationDetailsSource which will be used by the authentication filter
+ attribute authentication-details-source-ref {xsd:token}?
+form-login.attlist &=
+ ## The URL for the ForwardAuthenticationFailureHandler
+ attribute authentication-failure-forward-url {xsd:token}?
+form-login.attlist &=
+ ## The URL for the ForwardAuthenticationSuccessHandler
+ attribute authentication-success-forward-url {xsd:token}?
+
+oauth2-login =
+ ## Configures authentication support using an OAuth 2.0 and/or OpenID Connect 1.0 Provider.
+ element oauth2-login {oauth2-login.attlist}
+oauth2-login.attlist &=
+ ## Reference to the ClientRegistrationRepository
+ attribute client-registration-repository-ref {xsd:token}?
+oauth2-login.attlist &=
+ ## Reference to the OAuth2AuthorizedClientRepository
+ attribute authorized-client-repository-ref {xsd:token}?
+oauth2-login.attlist &=
+ ## Reference to the OAuth2AuthorizedClientService
+ attribute authorized-client-service-ref {xsd:token}?
+oauth2-login.attlist &=
+ ## Reference to the AuthorizationRequestRepository
+ attribute authorization-request-repository-ref {xsd:token}?
+oauth2-login.attlist &=
+ ## Reference to the OAuth2AuthorizationRequestResolver
+ attribute authorization-request-resolver-ref {xsd:token}?
+oauth2-login.attlist &=
+ ## Reference to the OAuth2AccessTokenResponseClient
+ attribute access-token-response-client-ref {xsd:token}?
+oauth2-login.attlist &=
+ ## Reference to the GrantedAuthoritiesMapper
+ attribute user-authorities-mapper-ref {xsd:token}?
+oauth2-login.attlist &=
+ ## Reference to the OAuth2UserService
+ attribute user-service-ref {xsd:token}?
+oauth2-login.attlist &=
+ ## Reference to the OpenID Connect OAuth2UserService
+ attribute oidc-user-service-ref {xsd:token}?
+oauth2-login.attlist &=
+ ## The URI where the filter processes authentication requests
+ attribute login-processing-url {xsd:token}?
+oauth2-login.attlist &=
+ ## The URI to send users to login
+ attribute login-page {xsd:token}?
+oauth2-login.attlist &=
+ ## Reference to the AuthenticationSuccessHandler
+ attribute authentication-success-handler-ref {xsd:token}?
+oauth2-login.attlist &=
+ ## Reference to the AuthenticationFailureHandler
+ attribute authentication-failure-handler-ref {xsd:token}?
+oauth2-login.attlist &=
+ ## Reference to the JwtDecoderFactory used by OidcAuthorizationCodeAuthenticationProvider
+ attribute jwt-decoder-factory-ref {xsd:token}?
+
+oauth2-client =
+ ## Configures OAuth 2.0 Client support.
+ element oauth2-client {oauth2-client.attlist, (authorization-code-grant?) }
+oauth2-client.attlist &=
+ ## Reference to the ClientRegistrationRepository
+ attribute client-registration-repository-ref {xsd:token}?
+oauth2-client.attlist &=
+ ## Reference to the OAuth2AuthorizedClientRepository
+ attribute authorized-client-repository-ref {xsd:token}?
+oauth2-client.attlist &=
+ ## Reference to the OAuth2AuthorizedClientService
+ attribute authorized-client-service-ref {xsd:token}?
+
+authorization-code-grant =
+ ## Configures OAuth 2.0 Authorization Code Grant.
+ element authorization-code-grant {authorization-code-grant.attlist, empty}
+authorization-code-grant.attlist &=
+ ## Reference to the AuthorizationRequestRepository
+ attribute authorization-request-repository-ref {xsd:token}?
+authorization-code-grant.attlist &=
+ ## Reference to the OAuth2AuthorizationRequestResolver
+ attribute authorization-request-resolver-ref {xsd:token}?
+authorization-code-grant.attlist &=
+ ## Reference to the OAuth2AccessTokenResponseClient
+ attribute access-token-response-client-ref {xsd:token}?
+
+client-registrations =
+ ## Container element for client(s) registered with an OAuth 2.0 or OpenID Connect 1.0 Provider.
+ element client-registrations {client-registration+, provider*}
+
+client-registration =
+ ## Represents a client registered with an OAuth 2.0 or OpenID Connect 1.0 Provider.
+ element client-registration {client-registration.attlist}
+client-registration.attlist &=
+ ## The ID that uniquely identifies the client registration.
+ attribute registration-id {xsd:token}
+client-registration.attlist &=
+ ## The client identifier.
+ attribute client-id {xsd:token}
+client-registration.attlist &=
+ ## The client secret.
+ attribute client-secret {xsd:token}?
+client-registration.attlist &=
+ ## The method used to authenticate the client with the provider. The supported values are basic, post and none (public clients).
+ attribute client-authentication-method {"basic" | "post" | "none"}?
+client-registration.attlist &=
+ ## The OAuth 2.0 Authorization Framework defines four Authorization Grant types. The supported values are authorization_code, client_credentials, password and implicit.
+ attribute authorization-grant-type {"authorization_code" | "client_credentials" | "password" | "implicit"}?
+client-registration.attlist &=
+ ## The client’s registered redirect URI that the Authorization Server redirects the end-user’s user-agent to after the end-user has authenticated and authorized access to the client.
+ attribute redirect-uri {xsd:token}?
+client-registration.attlist &=
+ ## A comma-separated list of scope(s) requested by the client during the Authorization Request flow, such as openid, email, or profile.
+ attribute scope {xsd:token}?
+client-registration.attlist &=
+ ## A descriptive name used for the client. The name may be used in certain scenarios, such as when displaying the name of the client in the auto-generated login page.
+ attribute client-name {xsd:token}?
+client-registration.attlist &=
+ ## A reference to the associated provider. May reference a 'provider' element or use one of the common providers (google, github, facebook, okta).
+ attribute provider-id {xsd:token}
+
+provider =
+ ## The configuration information for an OAuth 2.0 or OpenID Connect 1.0 Provider.
+ element provider {provider.attlist}
+provider.attlist &=
+ ## The ID that uniquely identifies the provider.
+ attribute provider-id {xsd:token}
+provider.attlist &=
+ ## The Authorization Endpoint URI for the Authorization Server.
+ attribute authorization-uri {xsd:token}?
+provider.attlist &=
+ ## The Token Endpoint URI for the Authorization Server.
+ attribute token-uri {xsd:token}?
+provider.attlist &=
+ ## The UserInfo Endpoint URI used to access the claims/attributes of the authenticated end-user.
+ attribute user-info-uri {xsd:token}?
+provider.attlist &=
+ ## The authentication method used when sending the access token to the UserInfo Endpoint. The supported values are header, form and query.
+ attribute user-info-authentication-method {"header" | "form" | "query"}?
+provider.attlist &=
+ ## The name of the attribute returned in the UserInfo Response that references the Name or Identifier of the end-user.
+ attribute user-info-user-name-attribute {xsd:token}?
+provider.attlist &=
+ ## The URI used to retrieve the JSON Web Key (JWK) Set from the Authorization Server, which contains the cryptographic key(s) used to verify the JSON Web Signature (JWS) of the ID Token and optionally the UserInfo Response.
+ attribute jwk-set-uri {xsd:token}?
+provider.attlist &=
+ ## The URI used to discover the configuration information for an OAuth 2.0 or OpenID Connect 1.0 Provider.
+ attribute issuer-uri {xsd:token}?
+
+oauth2-resource-server =
+ ## Configures authentication support as an OAuth 2.0 Resource Server.
+ element oauth2-resource-server {oauth2-resource-server.attlist, (jwt? & opaque-token?)}
+oauth2-resource-server.attlist &=
+ ## Reference to an AuthenticationManagerResolver
+ attribute authentication-manager-resolver-ref {xsd:token}?
+oauth2-resource-server.attlist &=
+ ## Reference to a BearerTokenResolver
+ attribute bearer-token-resolver-ref {xsd:token}?
+oauth2-resource-server.attlist &=
+ ## Reference to a AuthenticationEntryPoint
+ attribute entry-point-ref {xsd:token}?
+
+jwt =
+ ## Configures JWT authentication
+ element jwt {jwt.attlist}
+jwt.attlist &=
+ ## The URI to use to collect the JWK Set for verifying JWTs
+ attribute jwk-set-uri {xsd:token}?
+jwt.attlist &=
+ ## Reference to a JwtDecoder
+ attribute decoder-ref {xsd:token}?
+jwt.attlist &=
+ ## Reference to a Converter
+ attribute jwt-authentication-converter-ref {xsd:token}?
+
+opaque-token =
+ ## Configuration Opaque Token authentication
+ element opaque-token {opaque-token.attlist}
+opaque-token.attlist &=
+ ## The URI to use to introspect opaque token attributes
+ attribute introspection-uri {xsd:token}?
+opaque-token.attlist &=
+ ## The Client ID to use to authenticate the introspection request
+ attribute client-id {xsd:token}?
+opaque-token.attlist &=
+ ## The Client secret to use to authenticate the introspection request
+ attribute client-secret {xsd:token}?
+opaque-token.attlist &=
+ ## Reference to an OpaqueTokenIntrospector
+ attribute introspector-ref {xsd:token}?
+
+openid-login =
+ ## Sets up form login for authentication with an Open ID identity. NOTE: The OpenID 1.0 and 2.0 protocols have been deprecated and users are encouraged to migrate to OpenID Connect , which is supported by spring-security-oauth2
.
+ element openid-login {form-login.attlist, user-service-ref?, attribute-exchange*}
+
+attribute-exchange =
+ ## Sets up an attribute exchange configuration to request specified attributes from the OpenID identity provider. When multiple elements are used, each must have an identifier-attribute attribute. Each configuration will be matched in turn against the supplied login identifier until a match is found.
+ element attribute-exchange {attribute-exchange.attlist, openid-attribute+}
+
+attribute-exchange.attlist &=
+ ## A regular expression which will be compared against the claimed identity, when deciding which attribute-exchange configuration to use during authentication.
+ attribute identifier-match {xsd:token}?
+
+openid-attribute =
+ ## Attributes used when making an OpenID AX Fetch Request. NOTE: The OpenID 1.0 and 2.0 protocols have been deprecated and users are encouraged to migrate to OpenID Connect , which is supported by spring-security-oauth2
.
+ element openid-attribute {openid-attribute.attlist}
+
+openid-attribute.attlist &=
+ ## Specifies the name of the attribute that you wish to get back. For example, email.
+ attribute name {xsd:token}
+openid-attribute.attlist &=
+ ## Specifies the attribute type. For example, https://axschema.org/contact/email. See your OP's documentation for valid attribute types.
+ attribute type {xsd:token}
+openid-attribute.attlist &=
+ ## Specifies if this attribute is required to the OP, but does not error out if the OP does not return the attribute. Default is false.
+ attribute required {xsd:boolean}?
+openid-attribute.attlist &=
+ ## Specifies the number of attributes that you wish to get back. For example, return 3 emails. The default value is 1.
+ attribute count {xsd:int}?
+
+
+filter-chain-map =
+ ## Used to explicitly configure a FilterChainProxy instance with a FilterChainMap
+ element filter-chain-map {filter-chain-map.attlist, filter-chain+}
+filter-chain-map.attlist &=
+ request-matcher?
+
+filter-chain =
+ ## Used within to define a specific URL pattern and the list of filters which apply to the URLs matching that pattern. When multiple filter-chain elements are assembled in a list in order to configure a FilterChainProxy, the most specific patterns must be placed at the top of the list, with most general ones at the bottom.
+ element filter-chain {filter-chain.attlist, empty}
+filter-chain.attlist &=
+ (pattern | request-matcher-ref)
+filter-chain.attlist &=
+ ## A comma separated list of bean names that implement Filter that should be processed for this FilterChain. If the value is none, then no Filters will be used for this FilterChain.
+ attribute filters {xsd:token}
+
+pattern =
+ ## The request URL pattern which will be mapped to the FilterChain.
+ attribute pattern {xsd:token}
+request-matcher-ref =
+ ## Allows a RequestMatcher instance to be used, as an alternative to pattern-matching.
+ attribute request-matcher-ref {xsd:token}
+
+filter-security-metadata-source =
+ ## Used to explicitly configure a FilterSecurityMetadataSource bean for use with a FilterSecurityInterceptor. Usually only needed if you are configuring a FilterChainProxy explicitly, rather than using the element. The intercept-url elements used should only contain pattern, method and access attributes. Any others will result in a configuration error.
+ element filter-security-metadata-source {fsmds.attlist, intercept-url+}
+fsmds.attlist &=
+ use-expressions?
+fsmds.attlist &=
+ id?
+fsmds.attlist &=
+ request-matcher?
+
+http-basic =
+ ## Adds support for basic authentication
+ element http-basic {http-basic.attlist, empty}
+
+http-basic.attlist &=
+ ## Sets the AuthenticationEntryPoint which is used by the BasicAuthenticationFilter.
+ attribute entry-point-ref {xsd:token}?
+http-basic.attlist &=
+ ## Reference to an AuthenticationDetailsSource which will be used by the authentication filter
+ attribute authentication-details-source-ref {xsd:token}?
+
+session-management =
+ ## Session-management related functionality is implemented by the addition of a SessionManagementFilter to the filter stack.
+ element session-management {session-management.attlist, concurrency-control?}
+
+session-management.attlist &=
+ ## Indicates how session fixation protection will be applied when a user authenticates. If set to "none", no protection will be applied. "newSession" will create a new empty session, with only Spring Security-related attributes migrated. "migrateSession" will create a new session and copy all session attributes to the new session. In Servlet 3.1 (Java EE 7) and newer containers, specifying "changeSessionId" will keep the existing session and use the container-supplied session fixation protection (HttpServletRequest#changeSessionId()). Defaults to "changeSessionId" in Servlet 3.1 and newer containers, "migrateSession" in older containers. Throws an exception if "changeSessionId" is used in older containers.
+ attribute session-fixation-protection {"none" | "newSession" | "migrateSession" | "changeSessionId" }?
+session-management.attlist &=
+ ## The URL to which a user will be redirected if they submit an invalid session indentifier. Typically used to detect session timeouts.
+ attribute invalid-session-url {xsd:token}?
+session-management.attlist &=
+ ## Allows injection of the InvalidSessionStrategy instance used by the SessionManagementFilter
+ attribute invalid-session-strategy-ref {xsd:token}?
+session-management.attlist &=
+ ## Allows injection of the SessionAuthenticationStrategy instance used by the SessionManagementFilter
+ attribute session-authentication-strategy-ref {xsd:token}?
+session-management.attlist &=
+ ## Defines the URL of the error page which should be shown when the SessionAuthenticationStrategy raises an exception. If not set, an unauthorized (401) error code will be returned to the client. Note that this attribute doesn't apply if the error occurs during a form-based login, where the URL for authentication failure will take precedence.
+ attribute session-authentication-error-url {xsd:token}?
+
+
+concurrency-control =
+ ## Enables concurrent session control, limiting the number of authenticated sessions a user may have at the same time.
+ element concurrency-control {concurrency-control.attlist, empty}
+
+concurrency-control.attlist &=
+ ## The maximum number of sessions a single authenticated user can have open at the same time. Defaults to "1". A negative value denotes unlimited sessions.
+ attribute max-sessions {xsd:integer}?
+concurrency-control.attlist &=
+ ## The URL a user will be redirected to if they attempt to use a session which has been "expired" because they have logged in again.
+ attribute expired-url {xsd:token}?
+concurrency-control.attlist &=
+ ## Allows injection of the SessionInformationExpiredStrategy instance used by the ConcurrentSessionFilter
+ attribute expired-session-strategy-ref {xsd:token}?
+concurrency-control.attlist &=
+ ## Specifies that an unauthorized error should be reported when a user attempts to login when they already have the maximum configured sessions open. The default behaviour is to expire the original session. If the session-authentication-error-url attribute is set on the session-management URL, the user will be redirected to this URL.
+ attribute error-if-maximum-exceeded {xsd:boolean}?
+concurrency-control.attlist &=
+ ## Allows you to define an alias for the SessionRegistry bean in order to access it in your own configuration.
+ attribute session-registry-alias {xsd:token}?
+concurrency-control.attlist &=
+ ## Allows you to define an external SessionRegistry bean to be used by the concurrency control setup.
+ attribute session-registry-ref {xsd:token}?
+
+
+remember-me =
+ ## Sets up remember-me authentication. If used with the "key" attribute (or no attributes) the cookie-only implementation will be used. Specifying "token-repository-ref" or "remember-me-data-source-ref" will use the more secure, persisten token approach.
+ element remember-me {remember-me.attlist}
+remember-me.attlist &=
+ ## The "key" used to identify cookies from a specific token-based remember-me application. You should set this to a unique value for your application. If unset, it will default to a random value generated by SecureRandom.
+ attribute key {xsd:token}?
+
+remember-me.attlist &=
+ (token-repository-ref | remember-me-data-source-ref | remember-me-services-ref)
+
+remember-me.attlist &=
+ user-service-ref?
+
+remember-me.attlist &=
+ ## Exports the internally defined RememberMeServices as a bean alias, allowing it to be used by other beans in the application context.
+ attribute services-alias {xsd:token}?
+
+remember-me.attlist &=
+ ## Determines whether the "secure" flag will be set on the remember-me cookie. If set to true, the cookie will only be submitted over HTTPS (recommended). By default, secure cookies will be used if the request is made on a secure connection.
+ attribute use-secure-cookie {xsd:boolean}?
+
+remember-me.attlist &=
+ ## The period (in seconds) for which the remember-me cookie should be valid.
+ attribute token-validity-seconds {xsd:string}?
+
+remember-me.attlist &=
+ ## Reference to an AuthenticationSuccessHandler bean which should be used to handle a successful remember-me authentication.
+ attribute authentication-success-handler-ref {xsd:token}?
+remember-me.attlist &=
+ ## The name of the request parameter which toggles remember-me authentication. Defaults to 'remember-me'.
+ attribute remember-me-parameter {xsd:token}?
+remember-me.attlist &=
+ ## The name of cookie which store the token for remember-me authentication. Defaults to 'remember-me'.
+ attribute remember-me-cookie {xsd:token}?
+
+token-repository-ref =
+ ## Reference to a PersistentTokenRepository bean for use with the persistent token remember-me implementation.
+ attribute token-repository-ref {xsd:token}
+remember-me-services-ref =
+ ## Allows a custom implementation of RememberMeServices to be used. Note that this implementation should return RememberMeAuthenticationToken instances with the same "key" value as specified in the remember-me element. Alternatively it should register its own AuthenticationProvider. It should also implement the LogoutHandler interface, which will be invoked when a user logs out. Typically the remember-me cookie would be removed on logout.
+ attribute services-ref {xsd:token}?
+remember-me-data-source-ref =
+ ## DataSource bean for the database that contains the token repository schema.
+ data-source-ref
+
+anonymous =
+ ## Adds support for automatically granting all anonymous web requests a particular principal identity and a corresponding granted authority.
+ element anonymous {anonymous.attlist}
+anonymous.attlist &=
+ ## The key shared between the provider and filter. This generally does not need to be set. If unset, it will default to a random value generated by SecureRandom.
+ attribute key {xsd:token}?
+anonymous.attlist &=
+ ## The username that should be assigned to the anonymous request. This allows the principal to be identified, which may be important for logging and auditing. if unset, defaults to "anonymousUser".
+ attribute username {xsd:token}?
+anonymous.attlist &=
+ ## The granted authority that should be assigned to the anonymous request. Commonly this is used to assign the anonymous request particular roles, which can subsequently be used in authorization decisions. If unset, defaults to "ROLE_ANONYMOUS".
+ attribute granted-authority {xsd:token}?
+anonymous.attlist &=
+ ## With the default namespace setup, the anonymous "authentication" facility is automatically enabled. You can disable it using this property.
+ attribute enabled {xsd:boolean}?
+
+
+port-mappings =
+ ## Defines the list of mappings between http and https ports for use in redirects
+ element port-mappings {port-mappings.attlist, port-mapping+}
+
+port-mappings.attlist &= empty
+
+port-mapping =
+ ## Provides a method to map http ports to https ports when forcing a redirect.
+ element port-mapping {http-port, https-port}
+
+http-port =
+ ## The http port to use.
+ attribute http {xsd:token}
+
+https-port =
+ ## The https port to use.
+ attribute https {xsd:token}
+
+
+x509 =
+ ## Adds support for X.509 client authentication.
+ element x509 {x509.attlist}
+x509.attlist &=
+ ## The regular expression used to obtain the username from the certificate's subject. Defaults to matching on the common name using the pattern "CN=(.*?),".
+ attribute subject-principal-regex {xsd:token}?
+x509.attlist &=
+ ## Explicitly specifies which user-service should be used to load user data for X.509 authenticated clients. If ommitted, the default user-service will be used.
+ user-service-ref?
+x509.attlist &=
+ ## Reference to an AuthenticationDetailsSource which will be used by the authentication filter
+ attribute authentication-details-source-ref {xsd:token}?
+
+jee =
+ ## Adds a J2eePreAuthenticatedProcessingFilter to the filter chain to provide integration with container authentication.
+ element jee {jee.attlist}
+jee.attlist &=
+ ## A comma-separate list of roles to look for in the incoming HttpServletRequest.
+ attribute mappable-roles {xsd:token}
+jee.attlist &=
+ ## Explicitly specifies which user-service should be used to load user data for container authenticated clients. If ommitted, the set of mappable-roles will be used to construct the authorities for the user.
+ user-service-ref?
+
+authentication-manager =
+ ## Registers the AuthenticationManager instance and allows its list of AuthenticationProviders to be defined. Also allows you to define an alias to allow you to reference the AuthenticationManager in your own beans.
+ element authentication-manager {authman.attlist & authentication-provider* & ldap-authentication-provider*}
+authman.attlist &=
+ id?
+authman.attlist &=
+ ## An alias you wish to use for the AuthenticationManager bean (not required it you are using a specific id)
+ attribute alias {xsd:token}?
+authman.attlist &=
+ ## If set to true, the AuthenticationManger will attempt to clear any credentials data in the returned Authentication object, once the user has been authenticated.
+ attribute erase-credentials {xsd:boolean}?
+
+authentication-provider =
+ ## Indicates that the contained user-service should be used as an authentication source.
+ element authentication-provider {ap.attlist & any-user-service & password-encoder?}
+ap.attlist &=
+ ## Specifies a reference to a separately configured AuthenticationProvider instance which should be registered within the AuthenticationManager.
+ ref?
+ap.attlist &=
+ ## Specifies a reference to a separately configured UserDetailsService from which to obtain authentication data.
+ user-service-ref?
+
+user-service =
+ ## Creates an in-memory UserDetailsService from a properties file or a list of "user" child elements. Usernames are converted to lower-case internally to allow for case-insensitive lookups, so this should not be used if case-sensitivity is required.
+ element user-service {id? & (properties-file | (user*))}
+properties-file =
+ ## The location of a Properties file where each line is in the format of username=password,grantedAuthority[,grantedAuthority][,enabled|disabled]
+ attribute properties {xsd:token}?
+
+user =
+ ## Represents a user in the application.
+ element user {user.attlist, empty}
+user.attlist &=
+ ## The username assigned to the user.
+ attribute name {xsd:token}
+user.attlist &=
+ ## The password assigned to the user. This may be hashed if the corresponding authentication provider supports hashing (remember to set the "hash" attribute of the "user-service" element). This attribute be omitted in the case where the data will not be used for authentication, but only for accessing authorities. If omitted, the namespace will generate a random value, preventing its accidental use for authentication. Cannot be empty.
+ attribute password {xsd:string}?
+user.attlist &=
+ ## One of more authorities granted to the user. Separate authorities with a comma (but no space). For example, "ROLE_USER,ROLE_ADMINISTRATOR"
+ attribute authorities {xsd:token}
+user.attlist &=
+ ## Can be set to "true" to mark an account as locked and unusable.
+ attribute locked {xsd:boolean}?
+user.attlist &=
+ ## Can be set to "true" to mark an account as disabled and unusable.
+ attribute disabled {xsd:boolean}?
+
+jdbc-user-service =
+ ## Causes creation of a JDBC-based UserDetailsService.
+ element jdbc-user-service {id? & jdbc-user-service.attlist}
+jdbc-user-service.attlist &=
+ ## The bean ID of the DataSource which provides the required tables.
+ attribute data-source-ref {xsd:token}
+jdbc-user-service.attlist &=
+ cache-ref?
+jdbc-user-service.attlist &=
+ ## An SQL statement to query a username, password, and enabled status given a username. Default is "select username,password,enabled from users where username = ?"
+ attribute users-by-username-query {xsd:token}?
+jdbc-user-service.attlist &=
+ ## An SQL statement to query for a user's granted authorities given a username. The default is "select username, authority from authorities where username = ?"
+ attribute authorities-by-username-query {xsd:token}?
+jdbc-user-service.attlist &=
+ ## An SQL statement to query user's group authorities given a username. The default is "select g.id, g.group_name, ga.authority from groups g, group_members gm, group_authorities ga where gm.username = ? and g.id = ga.group_id and g.id = gm.group_id"
+ attribute group-authorities-by-username-query {xsd:token}?
+jdbc-user-service.attlist &=
+ role-prefix?
+
+csrf =
+## Element for configuration of the CsrfFilter for protection against CSRF. It also updates the default RequestCache to only replay "GET" requests.
+ element csrf {csrf-options.attlist}
+csrf-options.attlist &=
+ ## Specifies if csrf protection should be disabled. Default false (i.e. CSRF protection is enabled).
+ attribute disabled {xsd:boolean}?
+csrf-options.attlist &=
+ ## The RequestMatcher instance to be used to determine if CSRF should be applied. Default is any HTTP method except "GET", "TRACE", "HEAD", "OPTIONS"
+ attribute request-matcher-ref { xsd:token }?
+csrf-options.attlist &=
+ ## The CsrfTokenRepository to use. The default is HttpSessionCsrfTokenRepository wrapped by LazyCsrfTokenRepository.
+ attribute token-repository-ref { xsd:token }?
+
+headers =
+## Element for configuration of the HeaderWritersFilter. Enables easy setting for the X-Frame-Options, X-XSS-Protection and X-Content-Type-Options headers.
+element headers { headers-options.attlist, (cache-control? & xss-protection? & hsts? & frame-options? & content-type-options? & hpkp? & content-security-policy? & referrer-policy? & feature-policy? & header*)}
+headers-options.attlist &=
+ ## Specifies if the default headers should be disabled. Default false.
+ attribute defaults-disabled {xsd:token}?
+headers-options.attlist &=
+ ## Specifies if headers should be disabled. Default false.
+ attribute disabled {xsd:token}?
+hsts =
+ ## Adds support for HTTP Strict Transport Security (HSTS)
+ element hsts {hsts-options.attlist}
+hsts-options.attlist &=
+ ## Specifies if HTTP Strict Transport Security (HSTS) should be disabled. Default false.
+ attribute disabled {xsd:boolean}?
+hsts-options.attlist &=
+ ## Specifies if subdomains should be included. Default true.
+ attribute include-subdomains {xsd:boolean}?
+hsts-options.attlist &=
+ ## Specifies the maximum amount of time the host should be considered a Known HSTS Host. Default one year.
+ attribute max-age-seconds {xsd:integer}?
+hsts-options.attlist &=
+ ## The RequestMatcher instance to be used to determine if the header should be set. Default is if HttpServletRequest.isSecure() is true.
+ attribute request-matcher-ref { xsd:token }?
+hsts-options.attlist &=
+ ## Specifies if preload should be included. Default false.
+ attribute preload {xsd:boolean}?
+
+cors =
+## Element for configuration of CorsFilter. If no CorsFilter or CorsConfigurationSource is specified a HandlerMappingIntrospector is used as the CorsConfigurationSource
+element cors { cors-options.attlist }
+cors-options.attlist &=
+ ref?
+cors-options.attlist &=
+ ## Specifies a bean id that is a CorsConfigurationSource used to construct the CorsFilter to use
+ attribute configuration-source-ref {xsd:token}?
+
+hpkp =
+ ## Adds support for HTTP Public Key Pinning (HPKP).
+ element hpkp {hpkp.pins,hpkp.attlist}
+hpkp.pins =
+ ## The list with pins
+ element pins {hpkp.pin+}
+hpkp.pin =
+ ## A pin is specified using the base64-encoded SPKI fingerprint as value and the cryptographic hash algorithm as attribute
+ element pin {
+ ## The cryptographic hash algorithm
+ attribute algorithm { xsd:string }?,
+ text
+ }
+hpkp.attlist &=
+ ## Specifies if HTTP Public Key Pinning (HPKP) should be disabled. Default false.
+ attribute disabled {xsd:boolean}?
+hpkp.attlist &=
+ ## Specifies if subdomains should be included. Default false.
+ attribute include-subdomains {xsd:boolean}?
+hpkp.attlist &=
+ ## Sets the value for the max-age directive of the Public-Key-Pins header. Default 60 days.
+ attribute max-age-seconds {xsd:integer}?
+hpkp.attlist &=
+ ## Specifies if the browser should only report pin validation failures. Default true.
+ attribute report-only {xsd:boolean}?
+hpkp.attlist &=
+ ## Specifies the URI to which the browser should report pin validation failures.
+ attribute report-uri {xsd:string}?
+
+content-security-policy =
+ ## Adds support for Content Security Policy (CSP)
+ element content-security-policy {csp-options.attlist}
+csp-options.attlist &=
+ ## The security policy directive(s) for the Content-Security-Policy header or if report-only is set to true, then the Content-Security-Policy-Report-Only header is used.
+ attribute policy-directives {xsd:token}?
+csp-options.attlist &=
+ ## Set to true, to enable the Content-Security-Policy-Report-Only header for reporting policy violations only. Defaults to false.
+ attribute report-only {xsd:boolean}?
+
+referrer-policy =
+ ## Adds support for Referrer Policy
+ element referrer-policy {referrer-options.attlist}
+referrer-options.attlist &=
+ ## The policies for the Referrer-Policy header.
+ attribute policy {"no-referrer","no-referrer-when-downgrade","same-origin","origin","strict-origin","origin-when-cross-origin","strict-origin-when-cross-origin","unsafe-url"}?
+
+feature-policy =
+ ## Adds support for Feature Policy
+ element feature-policy {feature-options.attlist}
+feature-options.attlist &=
+ ## The security policy directive(s) for the Feature-Policy header.
+ attribute policy-directives {xsd:token}?
+
+cache-control =
+ ## Adds Cache-Control no-cache, no-store, must-revalidate, Pragma no-cache, and Expires 0 for every request
+ element cache-control {cache-control.attlist}
+cache-control.attlist &=
+ ## Specifies if Cache Control should be disabled. Default false.
+ attribute disabled {xsd:boolean}?
+
+frame-options =
+ ## Enable basic clickjacking support for newer browsers (IE8+), will set the X-Frame-Options header.
+ element frame-options {frame-options.attlist,empty}
+frame-options.attlist &=
+ ## If disabled, the X-Frame-Options header will not be included. Default false.
+ attribute disabled {xsd:boolean}?
+frame-options.attlist &=
+ ## Specify the policy to use for the X-Frame-Options-Header.
+ attribute policy {"DENY","SAMEORIGIN","ALLOW-FROM"}?
+frame-options.attlist &=
+ ## Specify the strategy to use when ALLOW-FROM is chosen.
+ attribute strategy {"static","whitelist","regexp"}?
+frame-options.attlist &=
+ ## Specify a reference to the custom AllowFromStrategy to use when ALLOW-FROM is chosen.
+ ref?
+frame-options.attlist &=
+ ## Specify a value to use for the chosen strategy.
+ attribute value {xsd:string}?
+frame-options.attlist &=
+ ## Specify the request parameter to use for the origin when using a 'whitelist' or 'regexp' based strategy. Default is 'from'.
+ ## Deprecated ALLOW-FROM is an obsolete directive that no longer works in modern browsers. Instead use
+ ## Content-Security-Policy with the
+ ## frame-ancestors
+ ## directive.
+ attribute from-parameter {xsd:string}?
+
+
+xss-protection =
+ ## Enable basic XSS browser protection, supported by newer browsers (IE8+), will set the X-XSS-Protection header.
+ element xss-protection {xss-protection.attlist,empty}
+xss-protection.attlist &=
+ ## disable the X-XSS-Protection header. Default is 'false' meaning it is enabled.
+ attribute disabled {xsd:boolean}?
+xss-protection.attlist &=
+ ## specify that XSS Protection should be explicitly enabled or disabled. Default is 'true' meaning it is enabled.
+ attribute enabled {xsd:boolean}?
+xss-protection.attlist &=
+ ## Add mode=block to the header or not, default is on.
+ attribute block {xsd:boolean}?
+
+content-type-options =
+ ## Add a X-Content-Type-Options header to the resopnse. Value is always 'nosniff'.
+ element content-type-options {content-type-options.attlist, empty}
+content-type-options.attlist &=
+ ## If disabled, the X-Content-Type-Options header will not be included. Default false.
+ attribute disabled {xsd:boolean}?
+
+header=
+ ## Add additional headers to the response.
+ element header {header.attlist}
+header.attlist &=
+ ## The name of the header to add.
+ attribute name {xsd:token}?
+header.attlist &=
+ ## The value for the header.
+ attribute value {xsd:token}?
+header.attlist &=
+ ## Reference to a custom HeaderWriter implementation.
+ ref?
+
+any-user-service = user-service | jdbc-user-service | ldap-user-service
+
+custom-filter =
+ ## Used to indicate that a filter bean declaration should be incorporated into the security filter chain.
+ element custom-filter {custom-filter.attlist}
+
+custom-filter.attlist &=
+ ref
+
+custom-filter.attlist &=
+ (after | before | position)
+
+after =
+ ## The filter immediately after which the custom-filter should be placed in the chain. This feature will only be needed by advanced users who wish to mix their own filters into the security filter chain and have some knowledge of the standard Spring Security filters. The filter names map to specific Spring Security implementation filters.
+ attribute after {named-security-filter}
+before =
+ ## The filter immediately before which the custom-filter should be placed in the chain
+ attribute before {named-security-filter}
+position =
+ ## The explicit position at which the custom-filter should be placed in the chain. Use if you are replacing a standard filter.
+ attribute position {named-security-filter}
+
+named-security-filter = "FIRST" | "CHANNEL_FILTER" | "SECURITY_CONTEXT_FILTER" | "CONCURRENT_SESSION_FILTER" | "WEB_ASYNC_MANAGER_FILTER" | "HEADERS_FILTER" | "CORS_FILTER" | "CSRF_FILTER" | "LOGOUT_FILTER" | "OAUTH2_AUTHORIZATION_REQUEST_FILTER" | "X509_FILTER" | "PRE_AUTH_FILTER" | "CAS_FILTER" | "OAUTH2_LOGIN_FILTER" | "FORM_LOGIN_FILTER" | "OPENID_FILTER" | "LOGIN_PAGE_FILTER" |"LOGOUT_PAGE_FILTER" | "DIGEST_AUTH_FILTER" | "BEARER_TOKEN_AUTH_FILTER" | "BASIC_AUTH_FILTER" | "REQUEST_CACHE_FILTER" | "SERVLET_API_SUPPORT_FILTER" | "JAAS_API_SUPPORT_FILTER" | "REMEMBER_ME_FILTER" | "ANONYMOUS_FILTER" | "OAUTH2_AUTHORIZATION_CODE_GRANT_FILTER" | "SESSION_MANAGEMENT_FILTER" | "EXCEPTION_TRANSLATION_FILTER" | "FILTER_SECURITY_INTERCEPTOR" | "SWITCH_USER_FILTER" | "LAST"
diff --git a/config/src/main/resources/org/springframework/security/config/spring-security-5.4.xsd b/config/src/main/resources/org/springframework/security/config/spring-security-5.4.xsd
new file mode 100644
index 00000000000..436820de820
--- /dev/null
+++ b/config/src/main/resources/org/springframework/security/config/spring-security-5.4.xsd
@@ -0,0 +1,3188 @@
+
+
+
+
+
+ Defines the hashing algorithm used on user passwords. Bcrypt is recommended.
+
+
+
+
+
+
+
+
+
+
+
+
+ Whether a string should be base64 encoded
+
+
+
+
+
+
+
+ Defines the strategy use for matching incoming requests. Currently the options are 'mvc'
+ (for Spring MVC matcher), 'ant' (for ant path patterns), 'regex' for regular expressions
+ and 'ciRegex' for case-insensitive regular expressions.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Specifies an IP port number. Used to configure an embedded LDAP server, for example.
+
+
+
+
+
+
+
+ Specifies a URL.
+
+
+
+
+
+
+
+ A bean identifier, used for referring to the bean elsewhere in the context.
+
+
+
+
+
+
+
+ A bean identifier, used for referring to the bean elsewhere in the context.
+
+
+
+
+
+
+
+ Defines a reference to a Spring bean Id.
+
+
+
+
+
+
+
+ Defines a reference to a cache for use with a UserDetailsService.
+
+
+
+
+
+
+
+ A reference to a user-service (or UserDetailsService bean) Id
+
+
+
+
+
+
+
+ A reference to an AuthenticationManager bean
+
+
+
+
+
+
+
+ A reference to a DataSource bean
+
+
+
+
+
+
+ Enables Spring Security debugging infrastructure. This will provide human-readable
+ (multi-line) debugging information to monitor requests coming into the security filters.
+ This may include sensitive information, such as request parameters or headers, and should
+ only be used in a development environment.
+
+
+
+
+
+
+
+
+ Defines a reference to a Spring bean Id.
+
+
+
+
+
+ Defines the hashing algorithm used on user passwords. Bcrypt is recommended.
+
+
+
+
+
+
+
+
+
+
+
+
+ A non-empty string prefix that will be added to role strings loaded from persistent
+ storage (e.g. "ROLE_"). Use the value "none" for no prefix in cases where the default is
+ non-empty.
+
+
+
+
+
+
+
+ Enables the use of expressions in the 'access' attributes in <intercept-url> elements
+ rather than the traditional list of configuration attributes. Defaults to 'true'. If
+ enabled, each attribute should contain a single boolean expression. If the expression
+ evaluates to 'true', access will be granted.
+
+
+
+
+
+
+ Defines an LDAP server location or starts an embedded server. The url indicates the
+ location of a remote server. If no url is given, an embedded server will be started,
+ listening on the supplied port number. The port is optional and defaults to 33389. A
+ Spring LDAP ContextSource bean will be registered for the server with the id supplied.
+
+
+
+
+
+
+
+
+
+ A bean identifier, used for referring to the bean elsewhere in the context.
+
+
+
+
+
+ Specifies a URL.
+
+
+
+
+
+ Specifies an IP port number. Used to configure an embedded LDAP server, for example.
+
+
+
+
+
+ Username (DN) of the "manager" user identity which will be used to authenticate to a
+ (non-embedded) LDAP server. If omitted, anonymous access will be used.
+
+
+
+
+
+ The password for the manager DN. This is required if the manager-dn is specified.
+
+
+
+
+
+ Explicitly specifies an ldif file resource to load into an embedded LDAP server. The
+ default is classpath*:*.ldiff
+
+
+
+
+
+ Optional root suffix for the embedded LDAP server. Default is "dc=springframework,dc=org"
+
+
+
+
+
+ Explicitly specifies which embedded ldap server should use. Values are 'apacheds' and
+ 'unboundid'. By default, it will depends if the library is available in the classpath.
+
+
+
+
+
+
+
+
+
+
+
+
+
+ The optional server to use. If omitted, and a default LDAP server is registered (using
+ <ldap-server> with no Id), that server will be used.
+
+
+
+
+
+
+
+ Group search filter. Defaults to (uniqueMember={0}). The substituted parameter is the DN
+ of the user.
+
+
+
+
+
+
+
+ Search base for group membership searches. Defaults to "" (searching from the root).
+
+
+
+
+
+
+
+ The LDAP filter used to search for users (optional). For example "(uid={0})". The
+ substituted parameter is the user's login name.
+
+
+
+
+
+
+
+ Search base for user searches. Defaults to "". Only used with a 'user-search-filter'.
+
+
+
+
+
+
+
+ The LDAP attribute name which contains the role name which will be used within Spring
+ Security. Defaults to "cn".
+
+
+
+
+
+
+
+ Allows the objectClass of the user entry to be specified. If set, the framework will
+ attempt to load standard attributes for the defined class into the returned UserDetails
+ object
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Allows explicit customization of the loaded user object by specifying a
+ UserDetailsContextMapper bean which will be called with the context information from the
+ user's directory entry
+
+
+
+
+
+
+ This element configures a LdapUserDetailsService which is a combination of a
+ FilterBasedLdapUserSearch and a DefaultLdapAuthoritiesPopulator.
+
+
+
+
+
+
+
+
+
+ A bean identifier, used for referring to the bean elsewhere in the context.
+
+
+
+
+
+ The optional server to use. If omitted, and a default LDAP server is registered (using
+ <ldap-server> with no Id), that server will be used.
+
+
+
+
+
+ The LDAP filter used to search for users (optional). For example "(uid={0})". The
+ substituted parameter is the user's login name.
+
+
+
+
+
+ Search base for user searches. Defaults to "". Only used with a 'user-search-filter'.
+
+
+
+
+
+ Group search filter. Defaults to (uniqueMember={0}). The substituted parameter is the DN
+ of the user.
+
+
+
+
+
+ Search base for group membership searches. Defaults to "" (searching from the root).
+
+
+
+
+
+ The LDAP attribute name which contains the role name which will be used within Spring
+ Security. Defaults to "cn".
+
+
+
+
+
+ Defines a reference to a cache for use with a UserDetailsService.
+
+
+
+
+
+ A non-empty string prefix that will be added to role strings loaded from persistent
+ storage (e.g. "ROLE_"). Use the value "none" for no prefix in cases where the default is
+ non-empty.
+
+
+
+
+
+ Allows the objectClass of the user entry to be specified. If set, the framework will
+ attempt to load standard attributes for the defined class into the returned UserDetails
+ object
+
+
+
+
+
+
+
+
+
+
+
+ Allows explicit customization of the loaded user object by specifying a
+ UserDetailsContextMapper bean which will be called with the context information from the
+ user's directory entry
+
+
+
+
+
+
+
+
+ The optional server to use. If omitted, and a default LDAP server is registered (using
+ <ldap-server> with no Id), that server will be used.
+
+
+
+
+
+ Search base for user searches. Defaults to "". Only used with a 'user-search-filter'.
+
+
+
+
+
+ The LDAP filter used to search for users (optional). For example "(uid={0})". The
+ substituted parameter is the user's login name.
+
+
+
+
+
+ Search base for group membership searches. Defaults to "" (searching from the root).
+
+
+
+
+
+ Group search filter. Defaults to (uniqueMember={0}). The substituted parameter is the DN
+ of the user.
+
+
+
+
+
+ The LDAP attribute name which contains the role name which will be used within Spring
+ Security. Defaults to "cn".
+
+
+
+
+
+ A specific pattern used to build the user's DN, for example "uid={0},ou=people". The key
+ "{0}" must be present and will be substituted with the username.
+
+
+
+
+
+ A non-empty string prefix that will be added to role strings loaded from persistent
+ storage (e.g. "ROLE_"). Use the value "none" for no prefix in cases where the default is
+ non-empty.
+
+
+
+
+
+ Allows the objectClass of the user entry to be specified. If set, the framework will
+ attempt to load standard attributes for the defined class into the returned UserDetails
+ object
+
+
+
+
+
+
+
+
+
+
+
+ Allows explicit customization of the loaded user object by specifying a
+ UserDetailsContextMapper bean which will be called with the context information from the
+ user's directory entry
+
+
+
+
+
+
+
+
+ The attribute in the directory which contains the user password. Defaults to
+ "userPassword".
+
+
+
+
+
+ Defines the hashing algorithm used on user passwords. Bcrypt is recommended.
+
+
+
+
+
+
+
+
+
+
+
+ Can be used inside a bean definition to add a security interceptor to the bean and set up
+ access configuration attributes for the bean's methods
+
+
+
+
+
+
+ Defines a protected method and the access control configuration attributes that apply to
+ it. We strongly advise you NOT to mix "protect" declarations with any services provided
+ "global-method-security".
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Optional AccessDecisionManager bean ID to be used by the created method security
+ interceptor.
+
+
+
+
+
+
+
+
+ A method name
+
+
+
+
+
+ Access configuration attributes list that applies to the method, e.g. "ROLE_A,ROLE_B".
+
+
+
+
+
+
+ Creates a MethodSecurityMetadataSource instance
+
+
+
+
+
+
+ Defines a protected method and the access control configuration attributes that apply to
+ it. We strongly advise you NOT to mix "protect" declarations with any services provided
+ "global-method-security".
+
+
+
+
+
+
+
+
+
+
+
+
+
+ A bean identifier, used for referring to the bean elsewhere in the context.
+
+
+
+
+
+ Enables the use of expressions in the 'access' attributes in <intercept-url> elements
+ rather than the traditional list of configuration attributes. Defaults to 'true'. If
+ enabled, each attribute should contain a single boolean expression. If the expression
+ evaluates to 'true', access will be granted.
+
+
+
+
+
+
+ Provides method security for all beans registered in the Spring application context.
+ Specifically, beans will be scanned for matches with the ordered list of
+ "protect-pointcut" sub-elements, Spring Security annotations and/or. Where there is a
+ match, the beans will automatically be proxied and security authorization applied to the
+ methods accordingly. If you use and enable all four sources of method security metadata
+ (ie "protect-pointcut" declarations, expression annotations, @Secured and also JSR250
+ security annotations), the metadata sources will be queried in that order. In practical
+ terms, this enables you to use XML to override method security metadata expressed in
+ annotations. If using annotations, the order of precedence is EL-based (@PreAuthorize
+ etc.), @Secured and finally JSR-250.
+
+
+
+
+
+
+
+ Allows the default expression-based mechanism for handling Spring Security's pre and post
+ invocation annotations (@PreFilter, @PreAuthorize, @PostFilter, @PostAuthorize) to be
+ replace entirely. Only applies if these annotations are enabled.
+
+
+
+
+
+
+ Defines the PrePostInvocationAttributeFactory instance which is used to generate pre and
+ post invocation metadata from the annotated methods.
+
+
+
+
+
+
+
+
+ Customizes the PreInvocationAuthorizationAdviceVoter with the ref as the
+ PreInvocationAuthorizationAdviceVoter for the <pre-post-annotation-handling> element.
+
+
+
+
+
+
+
+
+ Customizes the PostInvocationAdviceProvider with the ref as the
+ PostInvocationAuthorizationAdvice for the <pre-post-annotation-handling> element.
+
+
+
+
+
+
+
+
+
+
+
+ Defines the SecurityExpressionHandler instance which will be used if expression-based
+ access-control is enabled. A default implementation (with no ACL support) will be used if
+ not supplied.
+
+
+
+
+
+
+
+
+
+ Defines a protected pointcut and the access control configuration attributes that apply to
+ it. Every bean registered in the Spring application context that provides a method that
+ matches the pointcut will receive security authorization.
+
+
+
+
+
+
+
+
+ Allows addition of extra AfterInvocationProvider beans which should be called by the
+ MethodSecurityInterceptor created by global-method-security.
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Specifies whether the use of Spring Security's pre and post invocation annotations
+ (@PreFilter, @PreAuthorize, @PostFilter, @PostAuthorize) should be enabled for this
+ application context. Defaults to "disabled".
+
+
+
+
+
+
+
+
+
+
+
+ Specifies whether the use of Spring Security's @Secured annotations should be enabled for
+ this application context. Defaults to "disabled".
+
+
+
+
+
+
+
+
+
+
+
+ Specifies whether JSR-250 style attributes are to be used (for example "RolesAllowed").
+ This will require the javax.annotation.security classes on the classpath. Defaults to
+ "disabled".
+
+
+
+
+
+
+
+
+
+
+
+ Optional AccessDecisionManager bean ID to override the default used for method security.
+
+
+
+
+
+ Optional RunAsmanager implementation which will be used by the configured
+ MethodSecurityInterceptor
+
+
+
+
+
+ Allows the advice "order" to be set for the method security interceptor.
+
+
+
+
+
+ If true, class based proxying will be used instead of interface based proxying.
+
+
+
+
+
+ Can be used to specify that AspectJ should be used instead of the default Spring AOP. If
+ set, secured classes must be woven with the AnnotationSecurityAspect from the
+ spring-security-aspects module.
+
+
+
+
+
+
+
+
+
+
+ An external MethodSecurityMetadataSource instance can be supplied which will take priority
+ over other sources (such as the default annotations).
+
+
+
+
+
+ A reference to an AuthenticationManager bean
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ An AspectJ expression, including the 'execution' keyword. For example, 'execution(int
+ com.foo.TargetObject.countLength(String))' (without the quotes).
+
+
+
+
+
+ Access configuration attributes list that applies to all methods matching the pointcut,
+ e.g. "ROLE_A,ROLE_B"
+
+
+
+
+
+
+ Allows securing a Message Broker. There are two modes. If no id is specified: ensures that
+ any SimpAnnotationMethodMessageHandler has the AuthenticationPrincipalArgumentResolver
+ registered as a custom argument resolver; ensures that the
+ SecurityContextChannelInterceptor is automatically registered for the
+ clientInboundChannel; and that a ChannelSecurityInterceptor is registered with the
+ clientInboundChannel. If the id is specified, creates a ChannelSecurityInterceptor that
+ can be manually registered with the clientInboundChannel.
+
+
+
+
+
+
+
+ Defines the SecurityExpressionHandler instance which will be used if expression-based
+ access-control is enabled. A default implementation (with no ACL support) will be used if
+ not supplied.
+
+
+
+
+
+
+
+
+
+
+
+
+
+ A bean identifier, used for referring to the bean elsewhere in the context. If specified,
+ explicit configuration within clientInboundChannel is required. If not specified, ensures
+ that any SimpAnnotationMethodMessageHandler has the
+ AuthenticationPrincipalArgumentResolver registered as a custom argument resolver; ensures
+ that the SecurityContextChannelInterceptor is automatically registered for the
+ clientInboundChannel; and that a ChannelSecurityInterceptor is registered with the
+ clientInboundChannel.
+
+
+
+
+
+ Disables the requirement for CSRF token to be present in the Stomp headers (default
+ false). Changing the default is useful if it is necessary to allow other origins to make
+ SockJS connections.
+
+
+
+
+
+
+ Creates an authorization rule for a websocket message.
+
+
+
+
+
+
+
+
+
+ The destination ant pattern which will be mapped to the access attribute. For example, /**
+ matches any message with a destination, /admin/** matches any message that has a
+ destination that starts with admin.
+
+
+
+
+
+ The access configuration attributes that apply for the configured message. For example,
+ permitAll grants access to anyone, hasRole('ROLE_ADMIN') requires the user have the role
+ 'ROLE_ADMIN'.
+
+
+
+
+
+ The type of message to match on. Valid values are defined in SimpMessageType (i.e.
+ CONNECT, CONNECT_ACK, HEARTBEAT, MESSAGE, SUBSCRIBE, UNSUBSCRIBE, DISCONNECT,
+ DISCONNECT_ACK, OTHER).
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Allows a custom instance of HttpFirewall to be injected into the FilterChainProxy created
+ by the namespace.
+
+
+
+
+
+
+
+
+ Container element for HTTP security configuration. Multiple elements can now be defined,
+ each with a specific pattern to which the enclosed security configuration applies. A
+ pattern can also be configured to bypass Spring Security's filters completely by setting
+ the "security" attribute to "none".
+
+
+
+
+
+
+ Specifies the access attributes and/or filter list for a particular set of URLs.
+
+
+
+
+
+
+
+
+ Defines the access-denied strategy that should be used. An access denied page can be
+ defined or a reference to an AccessDeniedHandler instance.
+
+
+
+
+
+
+
+
+ Sets up a form login configuration for authentication with a username and password
+
+
+
+
+
+
+
+
+
+
+
+ Sets up form login for authentication with an Open ID identity. NOTE: The OpenID 1.0 and
+ 2.0 protocols have been deprecated and users are <a
+ href="https://openid.net/specs/openid-connect-migration-1_0.html">encouraged to
+ migrate</a> to <a href="https://openid.net/connect/">OpenID Connect</a>, which is
+ supported by <code>spring-security-oauth2</code>.
+
+
+
+
+
+
+
+
+
+ A reference to a user-service (or UserDetailsService bean) Id
+
+
+
+
+
+
+
+ Adds support for X.509 client authentication.
+
+
+
+
+
+
+
+
+
+ Adds support for basic authentication
+
+
+
+
+
+
+
+
+ Incorporates a logout processing filter. Most web applications require a logout filter,
+ although you may not require one if you write a controller to provider similar logic.
+
+
+
+
+
+
+
+
+ Session-management related functionality is implemented by the addition of a
+ SessionManagementFilter to the filter stack.
+
+
+
+
+
+
+ Enables concurrent session control, limiting the number of authenticated sessions a user
+ may have at the same time.
+
+
+
+
+
+
+
+
+
+
+
+
+ Sets up remember-me authentication. If used with the "key" attribute (or no attributes)
+ the cookie-only implementation will be used. Specifying "token-repository-ref" or
+ "remember-me-data-source-ref" will use the more secure, persisten token approach.
+
+
+
+
+
+
+
+
+ Adds support for automatically granting all anonymous web requests a particular principal
+ identity and a corresponding granted authority.
+
+
+
+
+
+
+
+
+ Defines the list of mappings between http and https ports for use in redirects
+
+
+
+
+
+
+ Provides a method to map http ports to https ports when forcing a redirect.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Defines the SecurityExpressionHandler instance which will be used if expression-based
+ access-control is enabled. A default implementation (with no ACL support) will be used if
+ not supplied.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ The request URL pattern which will be mapped to the filter chain created by this <http>
+ element. If omitted, the filter chain will match all requests.
+
+
+
+
+
+ When set to 'none', requests matching the pattern attribute will be ignored by Spring
+ Security. No security filters will be applied and no SecurityContext will be available. If
+ set, the <http> element must be empty, with no children.
+
+
+
+
+
+
+
+
+
+
+ Allows a RequestMatcher instance to be used, as an alternative to pattern-matching.
+
+
+
+
+
+ A legacy attribute which automatically registers a login form, BASIC authentication and a
+ logout URL and logout services. If unspecified, defaults to "false". We'd recommend you
+ avoid using this and instead explicitly configure the services you require.
+
+
+
+
+
+ Enables the use of expressions in the 'access' attributes in <intercept-url> elements
+ rather than the traditional list of configuration attributes. Defaults to 'true'. If
+ enabled, each attribute should contain a single boolean expression. If the expression
+ evaluates to 'true', access will be granted.
+
+
+
+
+
+ Controls the eagerness with which an HTTP session is created by Spring Security classes.
+ If not set, defaults to "ifRequired". If "stateless" is used, this implies that the
+ application guarantees that it will not create a session. This differs from the use of
+ "never" which means that Spring Security will not create a session, but will make use of
+ one if the application does.
+
+
+
+
+
+
+
+
+
+
+
+
+
+ A reference to a SecurityContextRepository bean. This can be used to customize how the
+ SecurityContext is stored between requests.
+
+
+
+
+
+ Defines the strategy use for matching incoming requests. Currently the options are 'mvc'
+ (for Spring MVC matcher), 'ant' (for ant path patterns), 'regex' for regular expressions
+ and 'ciRegex' for case-insensitive regular expressions.
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Provides versions of HttpServletRequest security methods such as isUserInRole() and
+ getPrincipal() which are implemented by accessing the Spring SecurityContext. Defaults to
+ "true".
+
+
+
+
+
+ If available, runs the request as the Subject acquired from the JaasAuthenticationToken.
+ Defaults to "false".
+
+
+
+
+
+ Optional attribute specifying the ID of the AccessDecisionManager implementation which
+ should be used for authorizing HTTP requests.
+
+
+
+
+
+ Optional attribute specifying the realm name that will be used for all authentication
+ features that require a realm name (eg BASIC and Digest authentication). If unspecified,
+ defaults to "Spring Security Application".
+
+
+
+
+
+ Allows a customized AuthenticationEntryPoint to be set on the ExceptionTranslationFilter.
+
+
+
+
+
+ Corresponds to the observeOncePerRequest property of FilterSecurityInterceptor. Defaults
+ to "true"
+
+
+
+
+
+ Prevents the jsessionid parameter from being added to rendered URLs. Defaults to "true"
+ (rewriting is disabled).
+
+
+
+
+
+ A bean identifier, used for referring to the bean elsewhere in the context.
+
+
+
+
+
+ A reference to an AuthenticationManager bean
+
+
+
+
+
+
+
+
+ Defines a reference to a Spring bean Id.
+
+
+
+
+
+ The access denied page that an authenticated user will be redirected to if they request a
+ page which they don't have the authority to access.
+
+
+
+
+
+
+
+ The access denied page that an authenticated user will be redirected to if they request a
+ page which they don't have the authority to access.
+
+
+
+
+
+
+
+
+ The request URL pattern which will be mapped to the FilterChain.
+
+
+
+
+
+ Allows a RequestMatcher instance to be used, as an alternative to pattern-matching.
+
+
+
+
+
+ The access configuration attributes that apply for the configured path.
+
+
+
+
+
+ The HTTP Method for which the access configuration attributes should apply. If not
+ specified, the attributes will apply to any method.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Used to specify that a URL must be accessed over http or https, or that there is no
+ preference. The value should be "http", "https" or "any", respectively.
+
+
+
+
+
+ The path to the servlet. This attribute is only applicable when 'request-matcher' is
+ 'mvc'. In addition, the value is only required in the following 2 use cases: 1) There are
+ 2 or more HttpServlet's registered in the ServletContext that have mappings starting with
+ '/' and are different; 2) The pattern starts with the same value of a registered
+ HttpServlet path, excluding the default (root) HttpServlet '/'.
+
+
+
+
+
+
+
+
+ Specifies the URL that will cause a logout. Spring Security will initialize a filter that
+ responds to this particular URL. Defaults to /logout if unspecified.
+
+
+
+
+
+ Specifies the URL to display once the user has logged out. If not specified, defaults to
+ <form-login-login-page>/?logout (i.e. /login?logout).
+
+
+
+
+
+ Specifies whether a logout also causes HttpSession invalidation, which is generally
+ desirable. If unspecified, defaults to true.
+
+
+
+
+
+ A reference to a LogoutSuccessHandler implementation which will be used to determine the
+ destination to which the user is taken after logging out.
+
+
+
+
+
+ A comma-separated list of the names of cookies which should be deleted when the user logs
+ out
+
+
+
+
+
+
+ Allow the RequestCache used for saving requests during the login process to be set
+
+
+
+
+
+
+
+
+
+
+ The URL that the login form is posted to. If unspecified, it defaults to /login.
+
+
+
+
+
+ The name of the request parameter which contains the username. Defaults to 'username'.
+
+
+
+
+
+ The name of the request parameter which contains the password. Defaults to 'password'.
+
+
+
+
+
+ The URL that will be redirected to after successful authentication, if the user's previous
+ action could not be resumed. This generally happens if the user visits a login page
+ without having first requested a secured operation that triggers authentication. If
+ unspecified, defaults to the root of the application.
+
+
+
+
+
+ Whether the user should always be redirected to the default-target-url after login.
+
+
+
+
+
+ The URL for the login page. If no login URL is specified, Spring Security will
+ automatically create a login URL at GET /login and a corresponding filter to render that
+ login URL when requested.
+
+
+
+
+
+ The URL for the login failure page. If no login failure URL is specified, Spring Security
+ will automatically create a failure login URL at /login?error and a corresponding filter
+ to render that login failure URL when requested.
+
+
+
+
+
+ Reference to an AuthenticationSuccessHandler bean which should be used to handle a
+ successful authentication request. Should not be used in combination with
+ default-target-url (or always-use-default-target-url) as the implementation should always
+ deal with navigation to the subsequent destination
+
+
+
+
+
+ Reference to an AuthenticationFailureHandler bean which should be used to handle a failed
+ authentication request. Should not be used in combination with authentication-failure-url
+ as the implementation should always deal with navigation to the subsequent destination
+
+
+
+
+
+ Reference to an AuthenticationDetailsSource which will be used by the authentication
+ filter
+
+
+
+
+
+ The URL for the ForwardAuthenticationFailureHandler
+
+
+
+
+
+ The URL for the ForwardAuthenticationSuccessHandler
+
+
+
+
+
+
+ Configures authentication support using an OAuth 2.0 and/or OpenID Connect 1.0 Provider.
+
+
+
+
+
+
+
+
+
+ Reference to the ClientRegistrationRepository
+
+
+
+
+
+ Reference to the OAuth2AuthorizedClientRepository
+
+
+
+
+
+ Reference to the OAuth2AuthorizedClientService
+
+
+
+
+
+ Reference to the AuthorizationRequestRepository
+
+
+
+
+
+ Reference to the OAuth2AuthorizationRequestResolver
+
+
+
+
+
+ Reference to the OAuth2AccessTokenResponseClient
+
+
+
+
+
+ Reference to the GrantedAuthoritiesMapper
+
+
+
+
+
+ Reference to the OAuth2UserService
+
+
+
+
+
+ Reference to the OpenID Connect OAuth2UserService
+
+
+
+
+
+ The URI where the filter processes authentication requests
+
+
+
+
+
+ The URI to send users to login
+
+
+
+
+
+ Reference to the AuthenticationSuccessHandler
+
+
+
+
+
+ Reference to the AuthenticationFailureHandler
+
+
+
+
+
+ Reference to the JwtDecoderFactory used by OidcAuthorizationCodeAuthenticationProvider
+
+
+
+
+
+
+ Configures OAuth 2.0 Client support.
+
+
+
+
+
+
+
+
+
+
+
+
+ Reference to the ClientRegistrationRepository
+
+
+
+
+
+ Reference to the OAuth2AuthorizedClientRepository
+
+
+
+
+
+ Reference to the OAuth2AuthorizedClientService
+
+
+
+
+
+
+ Configures OAuth 2.0 Authorization Code Grant.
+
+
+
+
+
+
+
+
+
+ Reference to the AuthorizationRequestRepository
+
+
+
+
+
+ Reference to the OAuth2AuthorizationRequestResolver
+
+
+
+
+
+ Reference to the OAuth2AccessTokenResponseClient
+
+
+
+
+
+
+ Container element for client(s) registered with an OAuth 2.0 or OpenID Connect 1.0
+ Provider.
+
+
+
+
+
+
+
+
+
+
+
+ Represents a client registered with an OAuth 2.0 or OpenID Connect 1.0 Provider.
+
+
+
+
+
+
+
+
+
+ The ID that uniquely identifies the client registration.
+
+
+
+
+
+ The client identifier.
+
+
+
+
+
+ The client secret.
+
+
+
+
+
+ The method used to authenticate the client with the provider. The supported values are
+ basic, post and none (public clients).
+
+
+
+
+
+
+
+
+
+
+
+
+ The OAuth 2.0 Authorization Framework defines four Authorization Grant types. The
+ supported values are authorization_code, client_credentials, password and implicit.
+
+
+
+
+
+
+
+
+
+
+
+
+
+ The client’s registered redirect URI that the Authorization Server redirects the
+ end-user’s user-agent to after the end-user has authenticated and authorized access to the
+ client.
+
+
+
+
+
+ A comma-separated list of scope(s) requested by the client during the Authorization
+ Request flow, such as openid, email, or profile.
+
+
+
+
+
+ A descriptive name used for the client. The name may be used in certain scenarios, such as
+ when displaying the name of the client in the auto-generated login page.
+
+
+
+
+
+ A reference to the associated provider. May reference a 'provider' element or use one of
+ the common providers (google, github, facebook, okta).
+
+
+
+
+
+
+ The configuration information for an OAuth 2.0 or OpenID Connect 1.0 Provider.
+
+
+
+
+
+
+
+
+
+ The ID that uniquely identifies the provider.
+
+
+
+
+
+ The Authorization Endpoint URI for the Authorization Server.
+
+
+
+
+
+ The Token Endpoint URI for the Authorization Server.
+
+
+
+
+
+ The UserInfo Endpoint URI used to access the claims/attributes of the authenticated
+ end-user.
+
+
+
+
+
+ The authentication method used when sending the access token to the UserInfo Endpoint. The
+ supported values are header, form and query.
+
+
+
+
+
+
+
+
+
+
+
+
+ The name of the attribute returned in the UserInfo Response that references the Name or
+ Identifier of the end-user.
+
+
+
+
+
+ The URI used to retrieve the JSON Web Key (JWK) Set from the Authorization Server, which
+ contains the cryptographic key(s) used to verify the JSON Web Signature (JWS) of the ID
+ Token and optionally the UserInfo Response.
+
+
+
+
+
+ The URI used to discover the configuration information for an OAuth 2.0 or OpenID Connect
+ 1.0 Provider.
+
+
+
+
+
+
+ Configures authentication support as an OAuth 2.0 Resource Server.
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Reference to an AuthenticationManagerResolver
+
+
+
+
+
+ Reference to a BearerTokenResolver
+
+
+
+
+
+ Reference to a AuthenticationEntryPoint
+
+
+
+
+
+
+ Configures JWT authentication
+
+
+
+
+
+
+
+
+
+ The URI to use to collect the JWK Set for verifying JWTs
+
+
+
+
+
+ Reference to a JwtDecoder
+
+
+
+
+
+ Reference to a Converter<Jwt, AbstractAuthenticationToken>
+
+
+
+
+
+
+ Configuration Opaque Token authentication
+
+
+
+
+
+
+
+
+
+ The URI to use to introspect opaque token attributes
+
+
+
+
+
+ The Client ID to use to authenticate the introspection request
+
+
+
+
+
+ The Client secret to use to authenticate the introspection request
+
+
+
+
+
+ Reference to an OpaqueTokenIntrospector
+
+
+
+
+
+
+
+ Sets up an attribute exchange configuration to request specified attributes from the
+ OpenID identity provider. When multiple elements are used, each must have an
+ identifier-attribute attribute. Each configuration will be matched in turn against the
+ supplied login identifier until a match is found.
+
+
+
+
+
+
+
+
+
+
+
+
+ A regular expression which will be compared against the claimed identity, when deciding
+ which attribute-exchange configuration to use during authentication.
+
+
+
+
+
+
+ Attributes used when making an OpenID AX Fetch Request. NOTE: The OpenID 1.0 and 2.0
+ protocols have been deprecated and users are <a
+ href="https://openid.net/specs/openid-connect-migration-1_0.html">encouraged to
+ migrate</a> to <a href="https://openid.net/connect/">OpenID Connect</a>, which is
+ supported by <code>spring-security-oauth2</code>.
+
+
+
+
+
+
+
+
+
+ Specifies the name of the attribute that you wish to get back. For example, email.
+
+
+
+
+
+ Specifies the attribute type. For example, https://axschema.org/contact/email. See your
+ OP's documentation for valid attribute types.
+
+
+
+
+
+ Specifies if this attribute is required to the OP, but does not error out if the OP does
+ not return the attribute. Default is false.
+
+
+
+
+
+ Specifies the number of attributes that you wish to get back. For example, return 3
+ emails. The default value is 1.
+
+
+
+
+
+
+ Used to explicitly configure a FilterChainProxy instance with a FilterChainMap
+
+
+
+
+
+
+
+
+
+
+
+
+ Defines the strategy use for matching incoming requests. Currently the options are 'mvc'
+ (for Spring MVC matcher), 'ant' (for ant path patterns), 'regex' for regular expressions
+ and 'ciRegex' for case-insensitive regular expressions.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Used within to define a specific URL pattern and the list of filters which apply to the
+ URLs matching that pattern. When multiple filter-chain elements are assembled in a list in
+ order to configure a FilterChainProxy, the most specific patterns must be placed at the
+ top of the list, with most general ones at the bottom.
+
+
+
+
+
+
+
+
+
+ The request URL pattern which will be mapped to the FilterChain.
+
+
+
+
+
+ Allows a RequestMatcher instance to be used, as an alternative to pattern-matching.
+
+
+
+
+
+ A comma separated list of bean names that implement Filter that should be processed for
+ this FilterChain. If the value is none, then no Filters will be used for this FilterChain.
+
+
+
+
+
+
+
+ The request URL pattern which will be mapped to the FilterChain.
+
+
+
+
+
+
+
+ Allows a RequestMatcher instance to be used, as an alternative to pattern-matching.
+
+
+
+
+
+
+ Used to explicitly configure a FilterSecurityMetadataSource bean for use with a
+ FilterSecurityInterceptor. Usually only needed if you are configuring a FilterChainProxy
+ explicitly, rather than using the <http> element. The intercept-url elements used should
+ only contain pattern, method and access attributes. Any others will result in a
+ configuration error.
+
+
+
+
+
+
+ Specifies the access attributes and/or filter list for a particular set of URLs.
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Enables the use of expressions in the 'access' attributes in <intercept-url> elements
+ rather than the traditional list of configuration attributes. Defaults to 'true'. If
+ enabled, each attribute should contain a single boolean expression. If the expression
+ evaluates to 'true', access will be granted.
+
+
+
+
+
+ A bean identifier, used for referring to the bean elsewhere in the context.
+
+
+
+
+
+ Defines the strategy use for matching incoming requests. Currently the options are 'mvc'
+ (for Spring MVC matcher), 'ant' (for ant path patterns), 'regex' for regular expressions
+ and 'ciRegex' for case-insensitive regular expressions.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Sets the AuthenticationEntryPoint which is used by the BasicAuthenticationFilter.
+
+
+
+
+
+ Reference to an AuthenticationDetailsSource which will be used by the authentication
+ filter
+
+
+
+
+
+
+
+
+ Indicates how session fixation protection will be applied when a user authenticates. If
+ set to "none", no protection will be applied. "newSession" will create a new empty
+ session, with only Spring Security-related attributes migrated. "migrateSession" will
+ create a new session and copy all session attributes to the new session. In Servlet 3.1
+ (Java EE 7) and newer containers, specifying "changeSessionId" will keep the existing
+ session and use the container-supplied session fixation protection
+ (HttpServletRequest#changeSessionId()). Defaults to "changeSessionId" in Servlet 3.1 and
+ newer containers, "migrateSession" in older containers. Throws an exception if
+ "changeSessionId" is used in older containers.
+
+
+
+
+
+
+
+
+
+
+
+
+
+ The URL to which a user will be redirected if they submit an invalid session indentifier.
+ Typically used to detect session timeouts.
+
+
+
+
+
+ Allows injection of the InvalidSessionStrategy instance used by the
+ SessionManagementFilter
+
+
+
+
+
+ Allows injection of the SessionAuthenticationStrategy instance used by the
+ SessionManagementFilter
+
+
+
+
+
+ Defines the URL of the error page which should be shown when the
+ SessionAuthenticationStrategy raises an exception. If not set, an unauthorized (401) error
+ code will be returned to the client. Note that this attribute doesn't apply if the error
+ occurs during a form-based login, where the URL for authentication failure will take
+ precedence.
+
+
+
+
+
+
+
+
+ The maximum number of sessions a single authenticated user can have open at the same time.
+ Defaults to "1". A negative value denotes unlimited sessions.
+
+
+
+
+
+ The URL a user will be redirected to if they attempt to use a session which has been
+ "expired" because they have logged in again.
+
+
+
+
+
+ Allows injection of the SessionInformationExpiredStrategy instance used by the
+ ConcurrentSessionFilter
+
+
+
+
+
+ Specifies that an unauthorized error should be reported when a user attempts to login when
+ they already have the maximum configured sessions open. The default behaviour is to expire
+ the original session. If the session-authentication-error-url attribute is set on the
+ session-management URL, the user will be redirected to this URL.
+
+
+
+
+
+ Allows you to define an alias for the SessionRegistry bean in order to access it in your
+ own configuration.
+
+
+
+
+
+ Allows you to define an external SessionRegistry bean to be used by the concurrency
+ control setup.
+
+
+
+
+
+
+
+
+ The "key" used to identify cookies from a specific token-based remember-me application.
+ You should set this to a unique value for your application. If unset, it will default to a
+ random value generated by SecureRandom.
+
+
+
+
+
+ Reference to a PersistentTokenRepository bean for use with the persistent token
+ remember-me implementation.
+
+
+
+
+
+ A reference to a DataSource bean
+
+
+
+
+
+
+ A reference to a user-service (or UserDetailsService bean) Id
+
+
+
+
+
+ Exports the internally defined RememberMeServices as a bean alias, allowing it to be used
+ by other beans in the application context.
+
+
+
+
+
+ Determines whether the "secure" flag will be set on the remember-me cookie. If set to
+ true, the cookie will only be submitted over HTTPS (recommended). By default, secure
+ cookies will be used if the request is made on a secure connection.
+
+
+
+
+
+ The period (in seconds) for which the remember-me cookie should be valid.
+
+
+
+
+
+ Reference to an AuthenticationSuccessHandler bean which should be used to handle a
+ successful remember-me authentication.
+
+
+
+
+
+ The name of the request parameter which toggles remember-me authentication. Defaults to
+ 'remember-me'.
+
+
+
+
+
+ The name of cookie which store the token for remember-me authentication. Defaults to
+ 'remember-me'.
+
+
+
+
+
+
+
+ Reference to a PersistentTokenRepository bean for use with the persistent token
+ remember-me implementation.
+
+
+
+
+
+
+
+ Allows a custom implementation of RememberMeServices to be used. Note that this
+ implementation should return RememberMeAuthenticationToken instances with the same "key"
+ value as specified in the remember-me element. Alternatively it should register its own
+ AuthenticationProvider. It should also implement the LogoutHandler interface, which will
+ be invoked when a user logs out. Typically the remember-me cookie would be removed on
+ logout.
+
+
+
+
+
+
+
+
+
+
+
+ The key shared between the provider and filter. This generally does not need to be set. If
+ unset, it will default to a random value generated by SecureRandom.
+
+
+
+
+
+ The username that should be assigned to the anonymous request. This allows the principal
+ to be identified, which may be important for logging and auditing. if unset, defaults to
+ "anonymousUser".
+
+
+
+
+
+ The granted authority that should be assigned to the anonymous request. Commonly this is
+ used to assign the anonymous request particular roles, which can subsequently be used in
+ authorization decisions. If unset, defaults to "ROLE_ANONYMOUS".
+
+
+
+
+
+ With the default namespace setup, the anonymous "authentication" facility is automatically
+ enabled. You can disable it using this property.
+
+
+
+
+
+
+
+
+
+ The http port to use.
+
+
+
+
+
+
+
+ The https port to use.
+
+
+
+
+
+
+
+
+ The regular expression used to obtain the username from the certificate's subject.
+ Defaults to matching on the common name using the pattern "CN=(.*?),".
+
+
+
+
+
+ A reference to a user-service (or UserDetailsService bean) Id
+
+
+
+
+
+ Reference to an AuthenticationDetailsSource which will be used by the authentication
+ filter
+
+
+
+
+
+
+ Adds a J2eePreAuthenticatedProcessingFilter to the filter chain to provide integration
+ with container authentication.
+
+
+
+
+
+
+
+
+
+ A comma-separate list of roles to look for in the incoming HttpServletRequest.
+
+
+
+
+
+ A reference to a user-service (or UserDetailsService bean) Id
+
+
+
+
+
+
+ Registers the AuthenticationManager instance and allows its list of
+ AuthenticationProviders to be defined. Also allows you to define an alias to allow you to
+ reference the AuthenticationManager in your own beans.
+
+
+
+
+
+
+ Indicates that the contained user-service should be used as an authentication source.
+
+
+
+
+
+
+
+ element which defines a password encoding strategy. Used by an authentication provider to
+ convert submitted passwords to hashed versions, for example.
+
+
+
+
+
+
+
+
+
+
+
+
+ Sets up an ldap authentication provider
+
+
+
+
+
+
+ Specifies that an LDAP provider should use an LDAP compare operation of the user's
+ password to authenticate the user
+
+
+
+
+
+
+ element which defines a password encoding strategy. Used by an authentication provider to
+ convert submitted passwords to hashed versions, for example.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ A bean identifier, used for referring to the bean elsewhere in the context.
+
+
+
+
+
+ An alias you wish to use for the AuthenticationManager bean (not required it you are using
+ a specific id)
+
+
+
+
+
+ If set to true, the AuthenticationManger will attempt to clear any credentials data in the
+ returned Authentication object, once the user has been authenticated.
+
+
+
+
+
+
+
+
+ Defines a reference to a Spring bean Id.
+
+
+
+
+
+ A reference to a user-service (or UserDetailsService bean) Id
+
+
+
+
+
+
+ Creates an in-memory UserDetailsService from a properties file or a list of "user" child
+ elements. Usernames are converted to lower-case internally to allow for case-insensitive
+ lookups, so this should not be used if case-sensitivity is required.
+
+
+
+
+
+
+ Represents a user in the application.
+
+
+
+
+
+
+
+
+
+ A bean identifier, used for referring to the bean elsewhere in the context.
+
+
+
+
+
+
+
+
+
+ The location of a Properties file where each line is in the format of
+ username=password,grantedAuthority[,grantedAuthority][,enabled|disabled]
+
+
+
+
+
+
+
+
+ The username assigned to the user.
+
+
+
+
+
+ The password assigned to the user. This may be hashed if the corresponding authentication
+ provider supports hashing (remember to set the "hash" attribute of the "user-service"
+ element). This attribute be omitted in the case where the data will not be used for
+ authentication, but only for accessing authorities. If omitted, the namespace will
+ generate a random value, preventing its accidental use for authentication. Cannot be
+ empty.
+
+
+
+
+
+ One of more authorities granted to the user. Separate authorities with a comma (but no
+ space). For example, "ROLE_USER,ROLE_ADMINISTRATOR"
+
+
+
+
+
+ Can be set to "true" to mark an account as locked and unusable.
+
+
+
+
+
+ Can be set to "true" to mark an account as disabled and unusable.
+
+
+
+
+
+
+ Causes creation of a JDBC-based UserDetailsService.
+
+
+
+
+
+ A bean identifier, used for referring to the bean elsewhere in the context.
+
+
+
+
+
+
+
+
+
+ The bean ID of the DataSource which provides the required tables.
+
+
+
+
+
+ Defines a reference to a cache for use with a UserDetailsService.
+
+
+
+
+
+ An SQL statement to query a username, password, and enabled status given a username.
+ Default is "select username,password,enabled from users where username = ?"
+
+
+
+
+
+ An SQL statement to query for a user's granted authorities given a username. The default
+ is "select username, authority from authorities where username = ?"
+
+
+
+
+
+ An SQL statement to query user's group authorities given a username. The default is
+ "select g.id, g.group_name, ga.authority from groups g, group_members gm,
+ group_authorities ga where gm.username = ? and g.id = ga.group_id and g.id = gm.group_id"
+
+
+
+
+
+ A non-empty string prefix that will be added to role strings loaded from persistent
+ storage (e.g. "ROLE_"). Use the value "none" for no prefix in cases where the default is
+ non-empty.
+
+
+
+
+
+
+ Element for configuration of the CsrfFilter for protection against CSRF. It also updates
+ the default RequestCache to only replay "GET" requests.
+
+
+
+
+
+
+
+
+
+ Specifies if csrf protection should be disabled. Default false (i.e. CSRF protection is
+ enabled).
+
+
+
+
+
+ The RequestMatcher instance to be used to determine if CSRF should be applied. Default is
+ any HTTP method except "GET", "TRACE", "HEAD", "OPTIONS"
+
+
+
+
+
+ The CsrfTokenRepository to use. The default is HttpSessionCsrfTokenRepository wrapped by
+ LazyCsrfTokenRepository.
+
+
+
+
+
+
+ Element for configuration of the HeaderWritersFilter. Enables easy setting for the
+ X-Frame-Options, X-XSS-Protection and X-Content-Type-Options headers.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Specifies if the default headers should be disabled. Default false.
+
+
+
+
+
+ Specifies if headers should be disabled. Default false.
+
+
+
+
+
+
+ Adds support for HTTP Strict Transport Security (HSTS)
+
+
+
+
+
+
+
+
+
+ Specifies if HTTP Strict Transport Security (HSTS) should be disabled. Default false.
+
+
+
+
+
+ Specifies if subdomains should be included. Default true.
+
+
+
+
+
+ Specifies the maximum amount of time the host should be considered a Known HSTS Host.
+ Default one year.
+
+
+
+
+
+ The RequestMatcher instance to be used to determine if the header should be set. Default
+ is if HttpServletRequest.isSecure() is true.
+
+
+
+
+
+ Specifies if preload should be included. Default false.
+
+
+
+
+
+
+ Element for configuration of CorsFilter. If no CorsFilter or CorsConfigurationSource is
+ specified a HandlerMappingIntrospector is used as the CorsConfigurationSource
+
+
+
+
+
+
+
+
+
+ Defines a reference to a Spring bean Id.
+
+
+
+
+
+ Specifies a bean id that is a CorsConfigurationSource used to construct the CorsFilter to
+ use
+
+
+
+
+
+
+ Adds support for HTTP Public Key Pinning (HPKP).
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ The list with pins
+
+
+
+
+
+
+
+
+
+
+ A pin is specified using the base64-encoded SPKI fingerprint as value and the
+ cryptographic hash algorithm as attribute
+
+
+
+
+
+ The cryptographic hash algorithm
+
+
+
+
+
+
+
+
+ Specifies if HTTP Public Key Pinning (HPKP) should be disabled. Default false.
+
+
+
+
+
+ Specifies if subdomains should be included. Default false.
+
+
+
+
+
+ Sets the value for the max-age directive of the Public-Key-Pins header. Default 60 days.
+
+
+
+
+
+ Specifies if the browser should only report pin validation failures. Default true.
+
+
+
+
+
+ Specifies the URI to which the browser should report pin validation failures.
+
+
+
+
+
+
+ Adds support for Content Security Policy (CSP)
+
+
+
+
+
+
+
+
+
+ The security policy directive(s) for the Content-Security-Policy header or if report-only
+ is set to true, then the Content-Security-Policy-Report-Only header is used.
+
+
+
+
+
+ Set to true, to enable the Content-Security-Policy-Report-Only header for reporting policy
+ violations only. Defaults to false.
+
+
+
+
+
+
+ Adds support for Referrer Policy
+
+
+
+
+
+
+
+
+
+ The policies for the Referrer-Policy header.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Adds support for Feature Policy
+
+
+
+
+
+
+
+
+
+ The security policy directive(s) for the Feature-Policy header.
+
+
+
+
+
+
+ Adds Cache-Control no-cache, no-store, must-revalidate, Pragma no-cache, and Expires 0 for
+ every request
+
+
+
+
+
+
+
+
+
+ Specifies if Cache Control should be disabled. Default false.
+
+
+
+
+
+
+ Enable basic clickjacking support for newer browsers (IE8+), will set the X-Frame-Options
+ header.
+
+
+
+
+
+
+
+
+
+ If disabled, the X-Frame-Options header will not be included. Default false.
+
+
+
+
+
+ Specify the policy to use for the X-Frame-Options-Header.
+
+
+
+
+
+
+
+
+
+
+
+
+ Specify the strategy to use when ALLOW-FROM is chosen.
+
+
+
+
+
+
+
+
+
+
+
+
+ Defines a reference to a Spring bean Id.
+
+
+
+
+
+ Specify a value to use for the chosen strategy.
+
+
+
+
+
+ Specify the request parameter to use for the origin when using a 'whitelist' or 'regexp'
+ based strategy. Default is 'from'. Deprecated ALLOW-FROM is an obsolete directive that no
+ longer works in modern browsers. Instead use Content-Security-Policy with the <a
+ href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy/frame-ancestors">frame-ancestors</a>
+ directive.
+
+
+
+
+
+
+ Enable basic XSS browser protection, supported by newer browsers (IE8+), will set the
+ X-XSS-Protection header.
+
+
+
+
+
+
+
+
+
+ disable the X-XSS-Protection header. Default is 'false' meaning it is enabled.
+
+
+
+
+
+ specify that XSS Protection should be explicitly enabled or disabled. Default is 'true'
+ meaning it is enabled.
+
+
+
+
+
+ Add mode=block to the header or not, default is on.
+
+
+
+
+
+
+ Add a X-Content-Type-Options header to the resopnse. Value is always 'nosniff'.
+
+
+
+
+
+
+
+
+
+ If disabled, the X-Content-Type-Options header will not be included. Default false.
+
+
+
+
+
+
+ Add additional headers to the response.
+
+
+
+
+
+
+
+
+
+ The name of the header to add.
+
+
+
+
+
+ The value for the header.
+
+
+
+
+
+ Defines a reference to a Spring bean Id.
+
+
+
+
+
+
+
+ Used to indicate that a filter bean declaration should be incorporated into the security
+ filter chain.
+
+
+
+
+
+
+
+
+
+
+ The filter immediately after which the custom-filter should be placed in the chain. This
+ feature will only be needed by advanced users who wish to mix their own filters into the
+ security filter chain and have some knowledge of the standard Spring Security filters. The
+ filter names map to specific Spring Security implementation filters.
+
+
+
+
+
+ The filter immediately before which the custom-filter should be placed in the chain
+
+
+
+
+
+ The explicit position at which the custom-filter should be placed in the chain. Use if you
+ are replacing a standard filter.
+
+
+
+
+
+
+
+ The filter immediately after which the custom-filter should be placed in the chain. This
+ feature will only be needed by advanced users who wish to mix their own filters into the
+ security filter chain and have some knowledge of the standard Spring Security filters. The
+ filter names map to specific Spring Security implementation filters.
+
+
+
+
+
+
+
+ The filter immediately before which the custom-filter should be placed in the chain
+
+
+
+
+
+
+
+ The explicit position at which the custom-filter should be placed in the chain. Use if you
+ are replacing a standard filter.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/config/src/test/java/org/springframework/security/config/annotation/rsocket/RSocketMessageHandlerITests.java b/config/src/test/java/org/springframework/security/config/annotation/rsocket/RSocketMessageHandlerITests.java
index 6af06326bfd..19f5e1010ce 100644
--- a/config/src/test/java/org/springframework/security/config/annotation/rsocket/RSocketMessageHandlerITests.java
+++ b/config/src/test/java/org/springframework/security/config/annotation/rsocket/RSocketMessageHandlerITests.java
@@ -103,7 +103,9 @@ public void retrieveMonoWhenSecureThenDenied() throws Exception {
.data(data)
.retrieveMono(String.class)
.block()
- ).isInstanceOf(ApplicationErrorException.class);
+ ).isInstanceOf(ApplicationErrorException.class)
+ .hasMessageContaining("Access Denied");
+
assertThat(this.controller.payloads).isEmpty();
}
@@ -116,7 +118,9 @@ public void retrieveMonoWhenAuthenticationFailedThenException() throws Exception
.data(data)
.retrieveMono(String.class)
.block()
- ).isInstanceOf(ApplicationErrorException.class);
+ ).isInstanceOf(ApplicationErrorException.class)
+ .hasMessageContaining("Invalid Credentials");
+
assertThat(this.controller.payloads).isEmpty();
}
@@ -149,12 +153,13 @@ public void retrieveMonoWhenPublicThenGranted() throws Exception {
@Test
public void retrieveFluxWhenDataFluxAndSecureThenDenied() throws Exception {
Flux data = Flux.just("a", "b", "c");
- assertThatCode(() -> this.requester.route("secure.secure.retrieve-flux")
+ assertThatCode(() -> this.requester.route("secure.retrieve-flux")
.data(data, String.class)
.retrieveFlux(String.class)
.collectList()
- .block()).isInstanceOf(
- ApplicationErrorException.class);
+ .block()
+ ).isInstanceOf(ApplicationErrorException.class)
+ .hasMessageContaining("Access Denied");
assertThat(this.controller.payloads).isEmpty();
}
@@ -179,8 +184,9 @@ public void retrieveFluxWhenDataStringAndSecureThenDenied() throws Exception {
.data(data)
.retrieveFlux(String.class)
.collectList()
- .block()).isInstanceOf(
- ApplicationErrorException.class);
+ .block()
+ ).isInstanceOf(ApplicationErrorException.class)
+ .hasMessageContaining("Access Denied");
assertThat(this.controller.payloads).isEmpty();
}
diff --git a/config/src/test/java/org/springframework/security/config/annotation/web/builders/WebSecurityTests.java b/config/src/test/java/org/springframework/security/config/annotation/web/builders/WebSecurityTests.java
index 60552a0e451..a096e39ec6a 100644
--- a/config/src/test/java/org/springframework/security/config/annotation/web/builders/WebSecurityTests.java
+++ b/config/src/test/java/org/springframework/security/config/annotation/web/builders/WebSecurityTests.java
@@ -36,6 +36,8 @@
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.context.support.AnnotationConfigWebApplicationContext;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
+import org.springframework.web.servlet.config.annotation.PathMatchConfigurer;
+import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import static org.assertj.core.api.Assertions.assertThat;
@@ -69,7 +71,7 @@ public void cleanup() {
@Test
public void ignoringMvcMatcher() throws Exception {
- loadConfig(MvcMatcherConfig.class);
+ loadConfig(MvcMatcherConfig.class, LegacyMvcMatchingConfig.class);
this.request.setRequestURI("/path");
this.springSecurityFilterChain.doFilter(this.request, this.response, this.chain);
@@ -141,7 +143,7 @@ public String path() {
@Test
public void ignoringMvcMatcherServletPath() throws Exception {
- loadConfig(MvcMatcherServletPathConfig.class);
+ loadConfig(MvcMatcherServletPathConfig.class, LegacyMvcMatchingConfig.class);
this.request.setServletPath("/spring");
this.request.setRequestURI("/spring/path");
@@ -216,6 +218,14 @@ public String path() {
}
}
+ @Configuration
+ static class LegacyMvcMatchingConfig implements WebMvcConfigurer {
+ @Override
+ public void configurePathMatch(PathMatchConfigurer configurer) {
+ configurer.setUseSuffixPatternMatch(true);
+ }
+ }
+
public void loadConfig(Class>... configs) {
this.context = new AnnotationConfigWebApplicationContext();
this.context.register(configs);
diff --git a/config/src/test/java/org/springframework/security/config/annotation/web/configuration/OAuth2ClientConfigurationTests.java b/config/src/test/java/org/springframework/security/config/annotation/web/configuration/OAuth2ClientConfigurationTests.java
index 8c42b037d7f..48eb3fd45d7 100644
--- a/config/src/test/java/org/springframework/security/config/annotation/web/configuration/OAuth2ClientConfigurationTests.java
+++ b/config/src/test/java/org/springframework/security/config/annotation/web/configuration/OAuth2ClientConfigurationTests.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2019 the original author or authors.
+ * Copyright 2002-2020 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.
@@ -25,6 +25,7 @@
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.test.SpringTestRule;
import org.springframework.security.oauth2.client.OAuth2AuthorizedClient;
+import org.springframework.security.oauth2.client.OAuth2AuthorizedClientManager;
import org.springframework.security.oauth2.client.annotation.RegisteredOAuth2AuthorizedClient;
import org.springframework.security.oauth2.client.endpoint.OAuth2AccessTokenResponseClient;
import org.springframework.security.oauth2.client.endpoint.OAuth2ClientCredentialsGrantRequest;
@@ -32,6 +33,7 @@
import org.springframework.security.oauth2.client.registration.ClientRegistrationRepository;
import org.springframework.security.oauth2.client.web.OAuth2AuthorizedClientRepository;
import org.springframework.security.oauth2.core.OAuth2AccessToken;
+import org.springframework.security.oauth2.core.TestOAuth2AccessTokens;
import org.springframework.security.oauth2.core.endpoint.OAuth2AccessTokenResponse;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.web.bind.annotation.GetMapping;
@@ -41,7 +43,14 @@
import javax.servlet.http.HttpServletRequest;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
-import static org.mockito.Mockito.*;
+import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.eq;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoInteractions;
+import static org.mockito.Mockito.verifyZeroInteractions;
+import static org.mockito.Mockito.when;
import static org.springframework.security.oauth2.client.registration.TestClientRegistrations.clientCredentials;
import static org.springframework.security.oauth2.client.registration.TestClientRegistrations.clientRegistration;
import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.authentication;
@@ -314,4 +323,71 @@ public OAuth2AccessTokenResponseClient acce
return mock(OAuth2AccessTokenResponseClient.class);
}
}
+
+ // gh-8700
+ @Test
+ public void requestWhenAuthorizedClientManagerConfiguredThenUsed() throws Exception {
+ String clientRegistrationId = "client1";
+ String principalName = "user1";
+ TestingAuthenticationToken authentication = new TestingAuthenticationToken(principalName, "password");
+
+ ClientRegistrationRepository clientRegistrationRepository = mock(ClientRegistrationRepository.class);
+ OAuth2AuthorizedClientRepository authorizedClientRepository = mock(OAuth2AuthorizedClientRepository.class);
+ OAuth2AuthorizedClientManager authorizedClientManager = mock(OAuth2AuthorizedClientManager.class);
+
+ ClientRegistration clientRegistration = clientRegistration().registrationId(clientRegistrationId).build();
+ OAuth2AuthorizedClient authorizedClient = new OAuth2AuthorizedClient(
+ clientRegistration, principalName, TestOAuth2AccessTokens.noScopes());
+
+ when(authorizedClientManager.authorize(any())).thenReturn(authorizedClient);
+
+ OAuth2AuthorizedClientManagerRegisteredConfig.CLIENT_REGISTRATION_REPOSITORY = clientRegistrationRepository;
+ OAuth2AuthorizedClientManagerRegisteredConfig.AUTHORIZED_CLIENT_REPOSITORY = authorizedClientRepository;
+ OAuth2AuthorizedClientManagerRegisteredConfig.AUTHORIZED_CLIENT_MANAGER = authorizedClientManager;
+ this.spring.register(OAuth2AuthorizedClientManagerRegisteredConfig.class).autowire();
+
+ this.mockMvc.perform(get("/authorized-client").with(authentication(authentication)))
+ .andExpect(status().isOk())
+ .andExpect(content().string("resolved"));
+
+ verify(authorizedClientManager).authorize(any());
+ verifyNoInteractions(clientRegistrationRepository);
+ verifyNoInteractions(authorizedClientRepository);
+ }
+
+ @EnableWebMvc
+ @EnableWebSecurity
+ static class OAuth2AuthorizedClientManagerRegisteredConfig extends WebSecurityConfigurerAdapter {
+ static ClientRegistrationRepository CLIENT_REGISTRATION_REPOSITORY;
+ static OAuth2AuthorizedClientRepository AUTHORIZED_CLIENT_REPOSITORY;
+ static OAuth2AuthorizedClientManager AUTHORIZED_CLIENT_MANAGER;
+
+ @Override
+ protected void configure(HttpSecurity http) {
+ }
+
+ @RestController
+ public class Controller {
+
+ @GetMapping("/authorized-client")
+ public String authorizedClient(@RegisteredOAuth2AuthorizedClient("client1") OAuth2AuthorizedClient authorizedClient) {
+ return authorizedClient != null ? "resolved" : "not-resolved";
+ }
+ }
+
+ @Bean
+ public ClientRegistrationRepository clientRegistrationRepository() {
+ return CLIENT_REGISTRATION_REPOSITORY;
+ }
+
+ @Bean
+ public OAuth2AuthorizedClientRepository authorizedClientRepository() {
+ return AUTHORIZED_CLIENT_REPOSITORY;
+ }
+
+ @Bean
+ public OAuth2AuthorizedClientManager authorizedClientManager() {
+ return AUTHORIZED_CLIENT_MANAGER;
+ }
+ }
}
diff --git a/config/src/test/java/org/springframework/security/config/annotation/web/configuration/WebSecurityConfigurationTests.java b/config/src/test/java/org/springframework/security/config/annotation/web/configuration/WebSecurityConfigurationTests.java
index c27fca6201a..c1d7d47b9fd 100644
--- a/config/src/test/java/org/springframework/security/config/annotation/web/configuration/WebSecurityConfigurationTests.java
+++ b/config/src/test/java/org/springframework/security/config/annotation/web/configuration/WebSecurityConfigurationTests.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2019 the original author or authors.
+ * Copyright 2002-2020 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.
@@ -32,6 +32,8 @@
import org.springframework.security.access.PermissionEvaluator;
import org.springframework.security.access.expression.AbstractSecurityExpressionHandler;
import org.springframework.security.access.expression.SecurityExpressionHandler;
+import org.springframework.security.access.hierarchicalroles.RoleHierarchy;
+import org.springframework.security.access.hierarchicalroles.RoleHierarchyImpl;
import org.springframework.security.authentication.TestingAuthenticationToken;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.authentication.configuration.EnableGlobalAuthentication;
@@ -69,6 +71,7 @@
*
* @author Rob Winch
* @author Joe Grandja
+ * @author Evgeniy Cheban
*/
public class WebSecurityConfigurationTests {
@Rule
@@ -290,6 +293,31 @@ protected void configure(HttpSecurity http) throws Exception {
}
}
+ @Test
+ public void securityExpressionHandlerWhenRoleHierarchyBeanThenRoleHierarchyUsed() {
+ this.spring.register(WebSecurityExpressionHandlerRoleHierarchyBeanConfig.class).autowire();
+ TestingAuthenticationToken authentication = new TestingAuthenticationToken("user", "notused", "ROLE_ADMIN");
+ FilterInvocation invocation = new FilterInvocation(new MockHttpServletRequest("GET", ""),
+ new MockHttpServletResponse(), new MockFilterChain());
+
+ AbstractSecurityExpressionHandler handler = this.spring.getContext().getBean(AbstractSecurityExpressionHandler.class);
+ EvaluationContext evaluationContext = handler.createEvaluationContext(authentication, invocation);
+ Expression expression = handler.getExpressionParser()
+ .parseExpression("hasRole('ROLE_USER')");
+ boolean granted = expression.getValue(evaluationContext, Boolean.class);
+ assertThat(granted).isTrue();
+ }
+
+ @EnableWebSecurity
+ static class WebSecurityExpressionHandlerRoleHierarchyBeanConfig extends WebSecurityConfigurerAdapter {
+ @Bean
+ RoleHierarchy roleHierarchy() {
+ RoleHierarchyImpl roleHierarchy = new RoleHierarchyImpl();
+ roleHierarchy.setHierarchy("ROLE_ADMIN > ROLE_USER");
+ return roleHierarchy;
+ }
+ }
+
@Test
public void securityExpressionHandlerWhenPermissionEvaluatorBeanThenPermissionEvaluatorUsed() {
this.spring.register(WebSecurityExpressionHandlerPermissionEvaluatorBeanConfig.class).autowire();
diff --git a/config/src/test/java/org/springframework/security/config/annotation/web/configurers/AuthorizeRequestsTests.java b/config/src/test/java/org/springframework/security/config/annotation/web/configurers/AuthorizeRequestsTests.java
index b9fb86db21b..d10ea89dce3 100644
--- a/config/src/test/java/org/springframework/security/config/annotation/web/configurers/AuthorizeRequestsTests.java
+++ b/config/src/test/java/org/springframework/security/config/annotation/web/configurers/AuthorizeRequestsTests.java
@@ -46,6 +46,8 @@
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.context.support.AnnotationConfigWebApplicationContext;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
+import org.springframework.web.servlet.config.annotation.PathMatchConfigurer;
+import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.spy;
@@ -292,7 +294,7 @@ public RoleHierarchy roleHiearchy() {
@Test
public void mvcMatcher() throws Exception {
- loadConfig(MvcMatcherConfig.class);
+ loadConfig(MvcMatcherConfig.class, LegacyMvcMatchingConfig.class);
this.request.setRequestURI("/path");
this.springSecurityFilterChain.doFilter(this.request, this.response, this.chain);
@@ -350,7 +352,7 @@ public String path() {
@Test
public void requestWhenMvcMatcherDenyAllThenRespondsWithUnauthorized() throws Exception {
- loadConfig(MvcMatcherInLambdaConfig.class);
+ loadConfig(MvcMatcherInLambdaConfig.class, LegacyMvcMatchingConfig.class);
this.request.setRequestURI("/path");
this.springSecurityFilterChain.doFilter(this.request, this.response, this.chain);
@@ -410,7 +412,7 @@ public String path() {
@Test
public void mvcMatcherServletPath() throws Exception {
- loadConfig(MvcMatcherServletPathConfig.class);
+ loadConfig(MvcMatcherServletPathConfig.class, LegacyMvcMatchingConfig.class);
this.request.setServletPath("/spring");
this.request.setRequestURI("/spring/path");
@@ -487,7 +489,7 @@ public String path() {
@Test
public void requestWhenMvcMatcherServletPathDenyAllThenMatchesOnServletPath() throws Exception {
- loadConfig(MvcMatcherServletPathInLambdaConfig.class);
+ loadConfig(MvcMatcherServletPathInLambdaConfig.class, LegacyMvcMatchingConfig.class);
this.request.setServletPath("/spring");
this.request.setRequestURI("/spring/path");
@@ -697,6 +699,14 @@ public String path() {
}
}
+ @Configuration
+ static class LegacyMvcMatchingConfig implements WebMvcConfigurer {
+ @Override
+ public void configurePathMatch(PathMatchConfigurer configurer) {
+ configurer.setUseSuffixPatternMatch(true);
+ }
+ }
+
public void loadConfig(Class>... configs) {
this.context = new AnnotationConfigWebApplicationContext();
this.context.register(configs);
diff --git a/config/src/test/java/org/springframework/security/config/annotation/web/configurers/HttpSecurityRequestMatchersTests.java b/config/src/test/java/org/springframework/security/config/annotation/web/configurers/HttpSecurityRequestMatchersTests.java
index ef4d4a2e425..fec73febccb 100644
--- a/config/src/test/java/org/springframework/security/config/annotation/web/configurers/HttpSecurityRequestMatchersTests.java
+++ b/config/src/test/java/org/springframework/security/config/annotation/web/configurers/HttpSecurityRequestMatchersTests.java
@@ -36,6 +36,8 @@
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.context.support.AnnotationConfigWebApplicationContext;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
+import org.springframework.web.servlet.config.annotation.PathMatchConfigurer;
+import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import static org.assertj.core.api.Assertions.assertThat;
import static org.springframework.security.config.Customizer.withDefaults;
@@ -71,7 +73,7 @@ public void cleanup() {
@Test
public void mvcMatcher() throws Exception {
- loadConfig(MvcMatcherConfig.class);
+ loadConfig(MvcMatcherConfig.class, LegacyMvcMatchingConfig.class);
this.request.setServletPath("/path");
this.springSecurityFilterChain.doFilter(this.request, this.response, this.chain);
@@ -137,7 +139,7 @@ public String path() {
@Test
public void requestMatchersMvcMatcher() throws Exception {
- loadConfig(RequestMatchersMvcMatcherConfig.class);
+ loadConfig(RequestMatchersMvcMatcherConfig.class, LegacyMvcMatchingConfig.class);
this.request.setServletPath("/path");
this.springSecurityFilterChain.doFilter(this.request, this.response, this.chain);
@@ -198,7 +200,7 @@ public String path() {
@Test
public void requestMatchersWhenMvcMatcherInLambdaThenPathIsSecured() throws Exception {
- loadConfig(RequestMatchersMvcMatcherInLambdaConfig.class);
+ loadConfig(RequestMatchersMvcMatcherInLambdaConfig.class, LegacyMvcMatchingConfig.class);
this.request.setServletPath("/path");
this.springSecurityFilterChain.doFilter(this.request, this.response, this.chain);
@@ -377,6 +379,14 @@ public String path() {
}
}
+ @Configuration
+ static class LegacyMvcMatchingConfig implements WebMvcConfigurer {
+ @Override
+ public void configurePathMatch(PathMatchConfigurer configurer) {
+ configurer.setUseSuffixPatternMatch(true);
+ }
+ }
+
public void loadConfig(Class>... configs) {
this.context = new AnnotationConfigWebApplicationContext();
this.context.register(configs);
diff --git a/config/src/test/java/org/springframework/security/config/annotation/web/configurers/ServletApiConfigurerTests.java b/config/src/test/java/org/springframework/security/config/annotation/web/configurers/ServletApiConfigurerTests.java
index dd3561d9fb8..7a9e5b1f943 100644
--- a/config/src/test/java/org/springframework/security/config/annotation/web/configurers/ServletApiConfigurerTests.java
+++ b/config/src/test/java/org/springframework/security/config/annotation/web/configurers/ServletApiConfigurerTests.java
@@ -22,6 +22,7 @@
import org.junit.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.AuthenticationTrustResolver;
@@ -44,10 +45,14 @@
import org.springframework.security.web.authentication.logout.LogoutSuccessEventPublishingLogoutHandler;
import org.springframework.security.web.servletapi.SecurityContextHolderAwareRequestFilter;
import org.springframework.test.web.servlet.MockMvc;
+import org.springframework.test.web.servlet.MvcResult;
+import org.springframework.test.web.servlet.setup.MockMvcBuilders;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
+import org.springframework.web.context.ConfigurableWebApplicationContext;
import javax.servlet.Filter;
+import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@@ -60,6 +65,7 @@
import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestBuilders.formLogin;
import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.authentication;
import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.user;
+import static org.springframework.security.test.web.servlet.setup.SecurityMockMvcConfigurers.springSecurity;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
@@ -329,6 +335,39 @@ protected void configure(HttpSecurity http) throws Exception {
}
}
+ @Test
+ public void logoutServletApiWhenCsrfDisabled() throws Exception {
+ ConfigurableWebApplicationContext context = this.spring.register(CsrfDisabledConfig.class).getContext();
+ MockMvc mockMvc = MockMvcBuilders.webAppContextSetup(context)
+ .apply(springSecurity())
+ .build();
+ MvcResult mvcResult = mockMvc.perform(get("/"))
+ .andReturn();
+ assertThat(mvcResult.getRequest().getSession(false)).isNull();
+ }
+
+ @Configuration
+ @EnableWebSecurity
+ static class CsrfDisabledConfig extends WebSecurityConfigurerAdapter {
+ @Override
+ protected void configure(HttpSecurity http) throws Exception {
+ // @formatter:off
+ http
+ .csrf().disable();
+ // @formatter:on
+ }
+
+ @RestController
+ static class LogoutController {
+ @GetMapping("/")
+ String logout(HttpServletRequest request) throws ServletException {
+ request.getSession().setAttribute("foo", "bar");
+ request.logout();
+ return "logout";
+ }
+ }
+ }
+
private T getFilter(Class filterClass) {
return (T) getFilters().stream()
.filter(filterClass::isInstance)
diff --git a/config/src/test/java/org/springframework/security/config/annotation/web/configurers/SessionManagementConfigurerTests.java b/config/src/test/java/org/springframework/security/config/annotation/web/configurers/SessionManagementConfigurerTests.java
index a5e2cdf1607..b1104f58d32 100644
--- a/config/src/test/java/org/springframework/security/config/annotation/web/configurers/SessionManagementConfigurerTests.java
+++ b/config/src/test/java/org/springframework/security/config/annotation/web/configurers/SessionManagementConfigurerTests.java
@@ -30,6 +30,7 @@
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.config.test.SpringTestRule;
import org.springframework.security.core.context.SecurityContext;
+import org.springframework.security.core.session.SessionRegistry;
import org.springframework.security.core.userdetails.PasswordEncodedUser;
import org.springframework.security.web.authentication.session.ChangeSessionIdAuthenticationStrategy;
import org.springframework.security.web.authentication.session.CompositeSessionAuthenticationStrategy;
@@ -53,6 +54,7 @@
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoInteractions;
import static org.mockito.Mockito.when;
import static org.springframework.security.config.Customizer.withDefaults;
import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.csrf;
@@ -483,4 +485,74 @@ protected void configure(HttpSecurity http) {
// @formatter:on
}
}
+
+ @Test
+ public void whenOneSessionRegistryBeanThenUseIt() throws Exception {
+ SessionRegistryOneBeanConfig.SESSION_REGISTRY = mock(SessionRegistry.class);
+ this.spring.register(SessionRegistryOneBeanConfig.class).autowire();
+
+ MockHttpSession session = new MockHttpSession(this.spring.getContext().getServletContext());
+ this.mvc.perform(get("/").session(session));
+
+ verify(SessionRegistryOneBeanConfig.SESSION_REGISTRY)
+ .getSessionInformation(session.getId());
+ }
+
+ @Test
+ public void whenTwoSessionRegistryBeansThenUseNeither() throws Exception {
+ SessionRegistryTwoBeansConfig.SESSION_REGISTRY_ONE = mock(SessionRegistry.class);
+ SessionRegistryTwoBeansConfig.SESSION_REGISTRY_TWO = mock(SessionRegistry.class);
+ this.spring.register(SessionRegistryTwoBeansConfig.class).autowire();
+
+ MockHttpSession session = new MockHttpSession(this.spring.getContext().getServletContext());
+ this.mvc.perform(get("/").session(session));
+
+ verifyNoInteractions(SessionRegistryTwoBeansConfig.SESSION_REGISTRY_ONE);
+ verifyNoInteractions(SessionRegistryTwoBeansConfig.SESSION_REGISTRY_TWO);
+ }
+
+ @EnableWebSecurity
+ static class SessionRegistryOneBeanConfig extends WebSecurityConfigurerAdapter {
+ private static SessionRegistry SESSION_REGISTRY;
+
+ @Override
+ protected void configure(HttpSecurity http) throws Exception {
+ // @formatter:off
+ http
+ .sessionManagement()
+ .maximumSessions(1);
+ // @formatter:on
+ }
+
+ @Bean
+ public SessionRegistry sessionRegistry() {
+ return SESSION_REGISTRY;
+ }
+ }
+
+ @EnableWebSecurity
+ static class SessionRegistryTwoBeansConfig extends WebSecurityConfigurerAdapter {
+ private static SessionRegistry SESSION_REGISTRY_ONE;
+
+ private static SessionRegistry SESSION_REGISTRY_TWO;
+
+ @Override
+ protected void configure(HttpSecurity http) throws Exception {
+ // @formatter:off
+ http
+ .sessionManagement()
+ .maximumSessions(1);
+ // @formatter:on
+ }
+
+ @Bean
+ public SessionRegistry sessionRegistryOne() {
+ return SESSION_REGISTRY_ONE;
+ }
+
+ @Bean
+ public SessionRegistry sessionRegistryTwo() {
+ return SESSION_REGISTRY_TWO;
+ }
+ }
}
diff --git a/config/src/test/java/org/springframework/security/config/annotation/web/configurers/UrlAuthorizationConfigurerTests.java b/config/src/test/java/org/springframework/security/config/annotation/web/configurers/UrlAuthorizationConfigurerTests.java
index c9d0fb9db87..9b86829ebc8 100644
--- a/config/src/test/java/org/springframework/security/config/annotation/web/configurers/UrlAuthorizationConfigurerTests.java
+++ b/config/src/test/java/org/springframework/security/config/annotation/web/configurers/UrlAuthorizationConfigurerTests.java
@@ -36,6 +36,8 @@
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.context.support.AnnotationConfigWebApplicationContext;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
+import org.springframework.web.servlet.config.annotation.PathMatchConfigurer;
+import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import static org.assertj.core.api.Assertions.assertThat;
@@ -71,7 +73,7 @@ public void cleanup() {
@Test
public void mvcMatcher() throws Exception {
- loadConfig(MvcMatcherConfig.class);
+ loadConfig(MvcMatcherConfig.class, LegacyMvcMatchingConfig.class);
this.request.setRequestURI("/path");
this.springSecurityFilterChain.doFilter(this.request, this.response, this.chain);
@@ -129,7 +131,7 @@ public String path() {
@Test
public void mvcMatcherServletPath() throws Exception {
- loadConfig(MvcMatcherServletPathConfig.class);
+ loadConfig(MvcMatcherServletPathConfig.class, LegacyMvcMatchingConfig.class);
this.request.setServletPath("/spring");
this.request.setRequestURI("/spring/path");
@@ -222,6 +224,14 @@ public void configure(HttpSecurity http) throws Exception {
}
}
+ @Configuration
+ static class LegacyMvcMatchingConfig implements WebMvcConfigurer {
+ @Override
+ public void configurePathMatch(PathMatchConfigurer configurer) {
+ configurer.setUseSuffixPatternMatch(true);
+ }
+ }
+
public void loadConfig(Class>... configs) {
this.context = new AnnotationConfigWebApplicationContext();
this.context.register(configs);
diff --git a/config/src/test/java/org/springframework/security/config/annotation/web/configurers/oauth2/client/OAuth2ClientConfigurerTests.java b/config/src/test/java/org/springframework/security/config/annotation/web/configurers/oauth2/client/OAuth2ClientConfigurerTests.java
index 86e4b4e3c41..ffc06ee6b02 100644
--- a/config/src/test/java/org/springframework/security/config/annotation/web/configurers/oauth2/client/OAuth2ClientConfigurerTests.java
+++ b/config/src/test/java/org/springframework/security/config/annotation/web/configurers/oauth2/client/OAuth2ClientConfigurerTests.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2019 the original author or authors.
+ * Copyright 2002-2020 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.
@@ -75,6 +75,7 @@
* Tests for {@link OAuth2ClientConfigurer}.
*
* @author Joe Grandja
+ * @author Parikshit Dutta
*/
public class OAuth2ClientConfigurerTests {
private static ClientRegistrationRepository clientRegistrationRepository;
@@ -208,6 +209,43 @@ public void configureWhenRequestCacheProvidedAndClientAuthorizationRequiredExcep
verify(requestCache).saveRequest(any(HttpServletRequest.class), any(HttpServletResponse.class));
}
+ @Test
+ public void configureWhenRequestCacheProvidedAndClientAuthorizationSucceedsThenRequestCacheUsed() throws Exception {
+ this.spring.register(OAuth2ClientConfig.class).autowire();
+
+ // Setup the Authorization Request in the session
+ Map attributes = new HashMap<>();
+ attributes.put(OAuth2ParameterNames.REGISTRATION_ID, this.registration1.getRegistrationId());
+ OAuth2AuthorizationRequest authorizationRequest = OAuth2AuthorizationRequest.authorizationCode()
+ .authorizationUri(this.registration1.getProviderDetails().getAuthorizationUri())
+ .clientId(this.registration1.getClientId())
+ .redirectUri("http://localhost/client-1")
+ .state("state")
+ .attributes(attributes)
+ .build();
+
+ AuthorizationRequestRepository authorizationRequestRepository =
+ new HttpSessionOAuth2AuthorizationRequestRepository();
+ MockHttpServletRequest request = new MockHttpServletRequest("GET", "");
+ MockHttpServletResponse response = new MockHttpServletResponse();
+ authorizationRequestRepository.saveAuthorizationRequest(authorizationRequest, request, response);
+
+ MockHttpSession session = (MockHttpSession) request.getSession();
+
+ String principalName = "user1";
+ TestingAuthenticationToken authentication = new TestingAuthenticationToken(principalName, "password");
+
+ this.mockMvc.perform(get("/client-1")
+ .param(OAuth2ParameterNames.CODE, "code")
+ .param(OAuth2ParameterNames.STATE, "state")
+ .with(authentication(authentication))
+ .session(session))
+ .andExpect(status().is3xxRedirection())
+ .andExpect(redirectedUrl("http://localhost/client-1"));
+
+ verify(requestCache).getRequest(any(HttpServletRequest.class), any(HttpServletResponse.class));
+ }
+
// gh-5521
@Test
public void configureWhenCustomAuthorizationRequestResolverSetThenAuthorizationRequestIncludesCustomParameters() throws Exception {
diff --git a/config/src/test/java/org/springframework/security/config/annotation/web/configurers/oauth2/server/resource/OAuth2ResourceServerConfigurerTests.java b/config/src/test/java/org/springframework/security/config/annotation/web/configurers/oauth2/server/resource/OAuth2ResourceServerConfigurerTests.java
index 51c64141c36..cf552a6ece9 100644
--- a/config/src/test/java/org/springframework/security/config/annotation/web/configurers/oauth2/server/resource/OAuth2ResourceServerConfigurerTests.java
+++ b/config/src/test/java/org/springframework/security/config/annotation/web/configurers/oauth2/server/resource/OAuth2ResourceServerConfigurerTests.java
@@ -160,6 +160,7 @@
* Tests for {@link OAuth2ResourceServerConfigurer}
*
* @author Josh Cummings
+ * @author Evgeniy Cheban
*/
public class OAuth2ResourceServerConfigurerTests {
private static final String JWT_TOKEN = "token";
@@ -1452,6 +1453,80 @@ public void configureWhenUsingBothAuthenticationManagerResolverAndOpaqueThenWiri
.hasMessageContaining("authenticationManagerResolver");
}
+ @Test
+ public void getJwtAuthenticationConverterWhenNoConverterSpecifiedThenTheDefaultIsUsed() {
+ ApplicationContext context =
+ this.spring.context(new GenericWebApplicationContext()).getContext();
+
+ OAuth2ResourceServerConfigurer.JwtConfigurer jwtConfigurer =
+ new OAuth2ResourceServerConfigurer(context).jwt();
+
+ assertThat(jwtConfigurer.getJwtAuthenticationConverter()).isInstanceOf(JwtAuthenticationConverter.class);
+ }
+
+ @Test
+ public void getJwtAuthenticationConverterWhenConverterBeanSpecified() {
+ JwtAuthenticationConverter converterBean = new JwtAuthenticationConverter();
+
+ GenericWebApplicationContext context = new GenericWebApplicationContext();
+ context.registerBean(JwtAuthenticationConverter.class, () -> converterBean);
+ this.spring.context(context).autowire();
+
+ OAuth2ResourceServerConfigurer.JwtConfigurer jwtConfigurer =
+ new OAuth2ResourceServerConfigurer(context).jwt();
+
+ assertThat(jwtConfigurer.getJwtAuthenticationConverter()).isEqualTo(converterBean);
+ }
+
+ @Test
+ public void getJwtAuthenticationConverterWhenConverterBeanAndAnotherOnTheDslThenTheDslOneIsUsed() {
+ JwtAuthenticationConverter converter = new JwtAuthenticationConverter();
+ JwtAuthenticationConverter converterBean = new JwtAuthenticationConverter();
+
+ GenericWebApplicationContext context = new GenericWebApplicationContext();
+ context.registerBean(JwtAuthenticationConverter.class, () -> converterBean);
+ this.spring.context(context).autowire();
+
+ OAuth2ResourceServerConfigurer.JwtConfigurer jwtConfigurer =
+ new OAuth2ResourceServerConfigurer(context).jwt();
+ jwtConfigurer.jwtAuthenticationConverter(converter);
+
+ assertThat(jwtConfigurer.getJwtAuthenticationConverter()).isEqualTo(converter);
+ }
+
+ @Test
+ public void getJwtAuthenticationConverterWhenDuplicateConverterBeansAndAnotherOnTheDslThenTheDslOneIsUsed() {
+ JwtAuthenticationConverter converter = new JwtAuthenticationConverter();
+ JwtAuthenticationConverter converterBean = new JwtAuthenticationConverter();
+
+ GenericWebApplicationContext context = new GenericWebApplicationContext();
+ context.registerBean("converterOne", JwtAuthenticationConverter.class, () -> converterBean);
+ context.registerBean("converterTwo", JwtAuthenticationConverter.class, () -> converterBean);
+ this.spring.context(context).autowire();
+
+ OAuth2ResourceServerConfigurer.JwtConfigurer jwtConfigurer =
+ new OAuth2ResourceServerConfigurer(context).jwt();
+ jwtConfigurer.jwtAuthenticationConverter(converter);
+
+ assertThat(jwtConfigurer.getJwtAuthenticationConverter()).isEqualTo(converter);
+ }
+
+ @Test
+ public void getJwtAuthenticationConverterWhenDuplicateConverterBeansThenThrowsException() {
+ JwtAuthenticationConverter converterBean = new JwtAuthenticationConverter();
+
+ GenericWebApplicationContext context = new GenericWebApplicationContext();
+ context.registerBean("converterOne", JwtAuthenticationConverter.class, () -> converterBean);
+ context.registerBean("converterTwo", JwtAuthenticationConverter.class, () -> converterBean);
+ this.spring.context(context).autowire();
+
+ OAuth2ResourceServerConfigurer.JwtConfigurer jwtConfigurer =
+ new OAuth2ResourceServerConfigurer(context).jwt();
+
+ assertThatCode(jwtConfigurer::getJwtAuthenticationConverter)
+ .isInstanceOf(NoUniqueBeanDefinitionException.class);
+ }
+
// -- support
@EnableWebSecurity
diff --git a/config/src/test/java/org/springframework/security/config/annotation/web/configurers/saml2/Saml2LoginConfigurerTests.java b/config/src/test/java/org/springframework/security/config/annotation/web/configurers/saml2/Saml2LoginConfigurerTests.java
index 586781f5d5e..de9c20407e2 100644
--- a/config/src/test/java/org/springframework/security/config/annotation/web/configurers/saml2/Saml2LoginConfigurerTests.java
+++ b/config/src/test/java/org/springframework/security/config/annotation/web/configurers/saml2/Saml2LoginConfigurerTests.java
@@ -23,6 +23,7 @@
import java.util.Collection;
import java.util.Collections;
import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
import org.junit.After;
import org.junit.Assert;
@@ -55,9 +56,13 @@
import org.springframework.security.core.authority.mapping.GrantedAuthoritiesMapper;
import org.springframework.security.saml2.provider.service.authentication.OpenSamlAuthenticationProvider;
import org.springframework.security.saml2.provider.service.authentication.Saml2Authentication;
+import org.springframework.security.saml2.provider.service.authentication.Saml2AuthenticationRequestContext;
import org.springframework.security.saml2.provider.service.authentication.Saml2AuthenticationToken;
+import org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistration;
import org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistrationRepository;
import org.springframework.security.saml2.provider.service.servlet.filter.Saml2WebSsoAuthenticationFilter;
+import org.springframework.security.saml2.provider.service.servlet.filter.Saml2WebSsoAuthenticationRequestFilter;
+import org.springframework.security.saml2.provider.service.web.Saml2AuthenticationRequestContextResolver;
import org.springframework.security.web.FilterChainProxy;
import org.springframework.security.web.context.HttpRequestResponseHolder;
import org.springframework.security.web.context.HttpSessionSecurityContextRepository;
@@ -66,10 +71,15 @@
import org.springframework.test.web.servlet.MockMvc;
import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
-import static org.springframework.security.config.annotation.web.configurers.saml2.TestRelyingPartyRegistrations.saml2AuthenticationConfiguration;
+import static org.springframework.security.saml2.provider.service.authentication.TestSaml2AuthenticationRequestContexts.authenticationRequestContext;
+import static org.springframework.security.saml2.provider.service.registration.TestRelyingPartyRegistrations.relyingPartyRegistration;
+import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
+import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
/**
* Tests for different Java configuration for {@link Saml2LoginConfigurer}
@@ -133,6 +143,20 @@ public void saml2LoginWhenConfiguringAuthenticationDefaultsUsingCustomizerThenTh
validateSaml2WebSsoAuthenticationFilterConfiguration();
}
+ @Test
+ public void saml2LoginWhenCustomAuthenticationRequestContextResolverThenUses() throws Exception {
+ this.spring.register(CustomAuthenticationRequestContextResolver.class).autowire();
+
+ Saml2AuthenticationRequestContext context = authenticationRequestContext().build();
+ Saml2AuthenticationRequestContextResolver resolver =
+ CustomAuthenticationRequestContextResolver.resolver;
+ when(resolver.resolve(any(HttpServletRequest.class), any(RelyingPartyRegistration.class)))
+ .thenReturn(context);
+ this.mvc.perform(get("/saml2/authenticate/registration-id"))
+ .andExpect(status().isFound());
+ verify(resolver).resolve(any(HttpServletRequest.class), any(RelyingPartyRegistration.class));
+ }
+
private void validateSaml2WebSsoAuthenticationFilterConfiguration() {
// get the OpenSamlAuthenticationProvider
Saml2WebSsoAuthenticationFilter filter = getSaml2SsoFilter(this.springSecurityFilterChain);
@@ -219,6 +243,38 @@ public O postProcess(O provider) {
}
}
+ @EnableWebSecurity
+ @Import(Saml2LoginConfigBeans.class)
+ static class CustomAuthenticationRequestContextResolver extends WebSecurityConfigurerAdapter {
+ private static final Saml2AuthenticationRequestContextResolver resolver =
+ mock(Saml2AuthenticationRequestContextResolver.class);
+
+ @Override
+ protected void configure(HttpSecurity http) throws Exception {
+ ObjectPostProcessor processor
+ = new ObjectPostProcessor() {
+ @Override
+ public O postProcess(O filter) {
+ filter.setAuthenticationRequestContextResolver(resolver);
+ return filter;
+ }
+ };
+
+ http
+ .authorizeRequests(authz -> authz
+ .anyRequest().authenticated()
+ )
+ .saml2Login(saml2 -> saml2
+ .addObjectPostProcessor(processor)
+ );
+ }
+
+ @Bean
+ Saml2AuthenticationRequestContextResolver resolver() {
+ return resolver;
+ }
+ }
+
private static AuthenticationManager getAuthenticationManagerMock(String role) {
return new AuthenticationManager() {
@@ -253,9 +309,8 @@ SecurityContextRepository securityContextRepository() {
@Bean
RelyingPartyRegistrationRepository relyingPartyRegistrationRepository() {
RelyingPartyRegistrationRepository repository = mock(RelyingPartyRegistrationRepository.class);
- when(repository.findByRegistrationId(anyString())).thenReturn(
- saml2AuthenticationConfiguration()
- );
+ when(repository.findByRegistrationId(anyString()))
+ .thenReturn(relyingPartyRegistration().build());
return repository;
}
}
diff --git a/config/src/test/java/org/springframework/security/config/annotation/web/configurers/saml2/TestRelyingPartyRegistrations.java b/config/src/test/java/org/springframework/security/config/annotation/web/configurers/saml2/TestRelyingPartyRegistrations.java
deleted file mode 100644
index b69456e2bcd..00000000000
--- a/config/src/test/java/org/springframework/security/config/annotation/web/configurers/saml2/TestRelyingPartyRegistrations.java
+++ /dev/null
@@ -1,56 +0,0 @@
-/*
- * Copyright 2002-2019 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.configurers.saml2;
-
-import org.springframework.security.saml2.credentials.Saml2X509Credential;
-import org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistration;
-import org.springframework.security.saml2.provider.service.servlet.filter.Saml2WebSsoAuthenticationFilter;
-
-import static org.springframework.security.config.annotation.web.configurers.saml2.TestSaml2Credentials.signingCredential;
-import static org.springframework.security.config.annotation.web.configurers.saml2.TestSaml2Credentials.verificationCertificate;
-
-/**
- * Preconfigured test data for {@link RelyingPartyRegistration} objects
- */
-public class TestRelyingPartyRegistrations {
-
- static RelyingPartyRegistration saml2AuthenticationConfiguration() {
- //remote IDP entity ID
- String idpEntityId = "https://simplesaml-for-spring-saml.cfapps.io/saml2/idp/metadata.php";
- //remote WebSSO Endpoint - Where to Send AuthNRequests to
- String webSsoEndpoint = "https://simplesaml-for-spring-saml.cfapps.io/saml2/idp/SSOService.php";
- //local registration ID
- String registrationId = "simplesamlphp";
- //local entity ID - autogenerated based on URL
- String localEntityIdTemplate = "{baseUrl}/saml2/service-provider-metadata/{registrationId}";
- //local signing (and decryption key)
- Saml2X509Credential signingCredential = signingCredential();
- //IDP certificate for verification of incoming messages
- Saml2X509Credential idpVerificationCertificate = verificationCertificate();
- String acsUrlTemplate = "{baseUrl}" + Saml2WebSsoAuthenticationFilter.DEFAULT_FILTER_PROCESSES_URI;
- return RelyingPartyRegistration.withRegistrationId(registrationId)
- .providerDetails(c -> c.entityId(idpEntityId))
- .providerDetails(c -> c.webSsoUrl(webSsoEndpoint))
- .credentials(c -> c.add(signingCredential))
- .credentials(c -> c.add(idpVerificationCertificate))
- .localEntityIdTemplate(localEntityIdTemplate)
- .assertionConsumerServiceUrlTemplate(acsUrlTemplate)
- .build();
- }
-
-
-}
diff --git a/config/src/test/java/org/springframework/security/config/annotation/web/reactive/EnableWebFluxSecurityTests.java b/config/src/test/java/org/springframework/security/config/annotation/web/reactive/EnableWebFluxSecurityTests.java
index e317d8f6250..4be1c2b325b 100644
--- a/config/src/test/java/org/springframework/security/config/annotation/web/reactive/EnableWebFluxSecurityTests.java
+++ b/config/src/test/java/org/springframework/security/config/annotation/web/reactive/EnableWebFluxSecurityTests.java
@@ -47,6 +47,7 @@
import org.springframework.security.test.context.annotation.SecurityTestExecutionListeners;
import org.springframework.security.test.context.support.WithMockUser;
import org.springframework.security.test.web.reactive.server.WebTestClientBuilder;
+import org.springframework.security.web.reactive.result.method.annotation.AuthenticationPrincipalArgumentResolver;
import org.springframework.security.web.reactive.result.view.CsrfRequestDataValueProcessor;
import org.springframework.security.web.server.SecurityWebFilterChain;
import org.springframework.security.web.server.WebFilterChainProxy;
@@ -59,6 +60,7 @@
import org.springframework.util.MultiValueMap;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
+import org.springframework.web.reactive.config.DelegatingWebFluxConfiguration;
import org.springframework.web.reactive.config.EnableWebFlux;
import org.springframework.web.reactive.function.BodyInserters;
import org.springframework.web.reactive.result.view.AbstractView;
@@ -434,4 +436,23 @@ static class Child {
Child() {
}
}
+
+ @Test
+ // gh-8596
+ public void resolveAuthenticationPrincipalArgumentResolverFirstDoesNotCauseBeanCurrentlyInCreationException() {
+ this.spring.register(EnableWebFluxSecurityConfiguration.class,
+ ReactiveAuthenticationTestConfiguration.class,
+ DelegatingWebFluxConfiguration.class).autowire();
+ }
+
+ @EnableWebFluxSecurity
+ @Configuration(proxyBeanMethods = false)
+ static class EnableWebFluxSecurityConfiguration {
+ /**
+ * It is necessary to Autowire AuthenticationPrincipalArgumentResolver because it triggers eager loading of
+ * AuthenticationPrincipalArgumentResolver bean which causes BeanCurrentlyInCreationException
+ */
+ @Autowired
+ AuthenticationPrincipalArgumentResolver resolver;
+ }
}
diff --git a/config/src/test/java/org/springframework/security/config/doc/XsdDocumentedTests.java b/config/src/test/java/org/springframework/security/config/doc/XsdDocumentedTests.java
index 05bb9177332..cd00c06896e 100644
--- a/config/src/test/java/org/springframework/security/config/doc/XsdDocumentedTests.java
+++ b/config/src/test/java/org/springframework/security/config/doc/XsdDocumentedTests.java
@@ -15,19 +15,26 @@
*/
package org.springframework.security.config.doc;
-import org.apache.commons.lang.StringUtils;
-import org.junit.After;
-import org.junit.Test;
-import org.springframework.core.io.ClassPathResource;
-import org.springframework.security.config.http.SecurityFiltersAssertions;
-
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Paths;
-import java.util.*;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
import java.util.stream.Collectors;
import java.util.stream.Stream;
+import org.apache.commons.lang.StringUtils;
+import org.junit.After;
+import org.junit.Test;
+
+import org.springframework.core.io.ClassPathResource;
+import org.springframework.security.config.http.SecurityFiltersAssertions;
+
import static org.assertj.core.api.Assertions.assertThat;
/**
@@ -45,12 +52,17 @@ public class XsdDocumentedTests {
"nsa-websocket-security",
"nsa-ldap",
"nsa-method-security",
- "nsa-web");
+ "nsa-web",
+ // deprecated and for removal
+ "nsa-frame-options-strategy",
+ "nsa-frame-options-ref",
+ "nsa-frame-options-value",
+ "nsa-frame-options-from-parameter");
String referenceLocation = "../docs/manual/src/docs/asciidoc/_includes/servlet/appendix/namespace.adoc";
String schema31xDocumentLocation = "org/springframework/security/config/spring-security-3.1.xsd";
- String schemaDocumentLocation = "org/springframework/security/config/spring-security-5.3.xsd";
+ String schemaDocumentLocation = "org/springframework/security/config/spring-security-5.4.xsd";
XmlSupport xml = new XmlSupport();
@@ -142,8 +154,8 @@ public void sizeWhenReadingFilesystemThenIsCorrectNumberOfSchemaFiles()
String[] schemas = resource.getFile().getParentFile().list((dir, name) -> name.endsWith(".xsd"));
- assertThat(schemas.length).isEqualTo(15)
- .withFailMessage("the count is equal to 15, if not then schemaDocument needs updating");
+ assertThat(schemas.length).isEqualTo(16)
+ .withFailMessage("the count is equal to 16, if not then schemaDocument needs updating");
}
/**
diff --git a/config/src/test/java/org/springframework/security/config/http/MiscHttpConfigTests.java b/config/src/test/java/org/springframework/security/config/http/MiscHttpConfigTests.java
index c4903195ab0..4049067b0fe 100644
--- a/config/src/test/java/org/springframework/security/config/http/MiscHttpConfigTests.java
+++ b/config/src/test/java/org/springframework/security/config/http/MiscHttpConfigTests.java
@@ -94,6 +94,8 @@
import org.springframework.security.web.csrf.CsrfFilter;
import org.springframework.security.web.firewall.FirewalledRequest;
import org.springframework.security.web.firewall.HttpFirewall;
+import org.springframework.security.web.firewall.RequestRejectedException;
+import org.springframework.security.web.firewall.RequestRejectedHandler;
import org.springframework.security.web.header.HeaderWriterFilter;
import org.springframework.security.web.savedrequest.RequestCache;
import org.springframework.security.web.savedrequest.RequestCacheAwareFilter;
@@ -754,6 +756,21 @@ public void reset() { }
verify(firewall).getFirewalledResponse(any(HttpServletResponse.class));
}
+ @Test
+ public void getWhenUsingCustomRequestRejectedHandlerThenRequestRejectedHandlerIsInvoked() throws Exception {
+ this.spring.configLocations(xml("RequestRejectedHandler")).autowire();
+
+ HttpServletResponse response = new MockHttpServletResponse();
+
+ RequestRejectedException rejected = new RequestRejectedException("failed");
+ HttpFirewall firewall = this.spring.getContext().getBean(HttpFirewall.class);
+ RequestRejectedHandler requestRejectedHandler = this.spring.getContext().getBean(RequestRejectedHandler.class);
+ when(firewall.getFirewalledRequest(any(HttpServletRequest.class))).thenThrow(rejected);
+ this.mvc.perform(get("/unprotected"));
+
+ verify(requestRejectedHandler).handle(any(), any(), any());
+ }
+
@Test
public void getWhenUsingCustomAccessDecisionManagerThenAuthorizesAccordingly() throws Exception {
this.spring.configLocations(xml("CustomAccessDecisionManager")).autowire();
diff --git a/config/src/test/java/org/springframework/security/config/http/OAuth2ClientBeanDefinitionParserTests.java b/config/src/test/java/org/springframework/security/config/http/OAuth2ClientBeanDefinitionParserTests.java
index 6774cfcdf2b..a7778d25d32 100644
--- a/config/src/test/java/org/springframework/security/config/http/OAuth2ClientBeanDefinitionParserTests.java
+++ b/config/src/test/java/org/springframework/security/config/http/OAuth2ClientBeanDefinitionParserTests.java
@@ -24,6 +24,7 @@
import org.springframework.security.config.test.SpringTestRule;
import org.springframework.security.oauth2.client.OAuth2AuthorizedClient;
import org.springframework.security.oauth2.client.OAuth2AuthorizedClientService;
+import org.springframework.security.oauth2.client.annotation.RegisteredOAuth2AuthorizedClient;
import org.springframework.security.oauth2.client.endpoint.OAuth2AccessTokenResponseClient;
import org.springframework.security.oauth2.client.endpoint.OAuth2AuthorizationCodeGrantRequest;
import org.springframework.security.oauth2.client.registration.ClientRegistration;
@@ -31,6 +32,7 @@
import org.springframework.security.oauth2.client.web.AuthorizationRequestRepository;
import org.springframework.security.oauth2.client.web.OAuth2AuthorizationRequestResolver;
import org.springframework.security.oauth2.client.web.OAuth2AuthorizedClientRepository;
+import org.springframework.security.oauth2.core.TestOAuth2AccessTokens;
import org.springframework.security.oauth2.core.endpoint.OAuth2AccessTokenResponse;
import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationRequest;
import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames;
@@ -39,8 +41,11 @@
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.MvcResult;
+import org.springframework.ui.Model;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.RestController;
import java.util.HashMap;
import java.util.Map;
@@ -51,6 +56,7 @@
import static org.mockito.Mockito.when;
import static org.springframework.security.oauth2.core.endpoint.TestOAuth2AccessTokenResponses.accessTokenResponse;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
+import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.redirectedUrl;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
@@ -200,6 +206,32 @@ public void requestWhenCustomAuthorizedClientServiceThenCalled() throws Exceptio
verify(this.authorizedClientService).saveAuthorizedClient(any(), any());
}
+ @WithMockUser
+ @Test
+ public void requestWhenAuthorizedClientFoundThenMethodArgumentResolved() throws Exception {
+ this.spring.configLocations(xml("AuthorizedClientArgumentResolver")).autowire();
+
+ ClientRegistration clientRegistration = this.clientRegistrationRepository.findByRegistrationId("google");
+
+ OAuth2AuthorizedClient authorizedClient = new OAuth2AuthorizedClient(
+ clientRegistration, "user", TestOAuth2AccessTokens.noScopes());
+ when(this.authorizedClientRepository.loadAuthorizedClient(any(), any(), any()))
+ .thenReturn(authorizedClient);
+
+ this.mvc.perform(get("/authorized-client"))
+ .andExpect(status().isOk())
+ .andExpect(content().string("resolved"));
+ }
+
+ @RestController
+ static class AuthorizedClientController {
+
+ @GetMapping("/authorized-client")
+ String authorizedClient(Model model, @RegisteredOAuth2AuthorizedClient("google") OAuth2AuthorizedClient authorizedClient) {
+ return authorizedClient != null ? "resolved" : "not-resolved";
+ }
+ }
+
private static OAuth2AuthorizationRequest createAuthorizationRequest(ClientRegistration clientRegistration) {
Map attributes = new HashMap<>();
attributes.put(OAuth2ParameterNames.REGISTRATION_ID, clientRegistration.getRegistrationId());
diff --git a/config/src/test/java/org/springframework/security/config/http/OAuth2LoginBeanDefinitionParserTests.java b/config/src/test/java/org/springframework/security/config/http/OAuth2LoginBeanDefinitionParserTests.java
index 9470cd03b46..3c0c793b250 100644
--- a/config/src/test/java/org/springframework/security/config/http/OAuth2LoginBeanDefinitionParserTests.java
+++ b/config/src/test/java/org/springframework/security/config/http/OAuth2LoginBeanDefinitionParserTests.java
@@ -17,6 +17,7 @@
import org.junit.Rule;
import org.junit.Test;
+import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationListener;
@@ -28,7 +29,9 @@
import org.springframework.security.core.authority.AuthorityUtils;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.authority.mapping.GrantedAuthoritiesMapper;
+import org.springframework.security.oauth2.client.OAuth2AuthorizedClient;
import org.springframework.security.oauth2.client.OAuth2AuthorizedClientService;
+import org.springframework.security.oauth2.client.annotation.RegisteredOAuth2AuthorizedClient;
import org.springframework.security.oauth2.client.endpoint.OAuth2AccessTokenResponseClient;
import org.springframework.security.oauth2.client.endpoint.OAuth2AuthorizationCodeGrantRequest;
import org.springframework.security.oauth2.client.registration.ClientRegistration;
@@ -40,6 +43,7 @@
import org.springframework.security.oauth2.client.web.OAuth2AuthorizationRequestResolver;
import org.springframework.security.oauth2.client.web.OAuth2AuthorizedClientRepository;
import org.springframework.security.oauth2.core.OAuth2AuthenticationException;
+import org.springframework.security.oauth2.core.TestOAuth2AccessTokens;
import org.springframework.security.oauth2.core.endpoint.OAuth2AccessTokenResponse;
import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationRequest;
import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames;
@@ -50,13 +54,23 @@
import org.springframework.security.oauth2.jwt.Jwt;
import org.springframework.security.oauth2.jwt.JwtDecoderFactory;
import org.springframework.security.oauth2.jwt.TestJwts;
+import org.springframework.security.test.context.annotation.SecurityTestExecutionListeners;
+import org.springframework.security.test.context.support.WithMockUser;
import org.springframework.security.web.authentication.AuthenticationFailureHandler;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
import org.springframework.security.web.savedrequest.RequestCache;
+import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.MvcResult;
+import org.springframework.ui.Model;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.Map;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.ArgumentMatchers.any;
@@ -66,18 +80,17 @@
import static org.springframework.security.oauth2.core.endpoint.TestOAuth2AccessTokenResponses.accessTokenResponse;
import static org.springframework.security.oauth2.core.endpoint.TestOAuth2AccessTokenResponses.oidcAccessTokenResponse;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
+import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.redirectedUrl;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
-import java.util.Collection;
-import java.util.HashMap;
-import java.util.Map;
-
/**
* Tests for {@link OAuth2LoginBeanDefinitionParser}.
*
* @author Ruby Hartono
*/
+@RunWith(SpringJUnit4ClassRunner.class)
+@SecurityTestExecutionListeners
public class OAuth2LoginBeanDefinitionParserTests {
private static final String CONFIG_LOCATION_PREFIX = "classpath:org/springframework/security/config/http/OAuth2LoginBeanDefinitionParserTests";
@@ -489,6 +502,32 @@ public void requestWhenCustomAuthorizedClientServiceThenCalled() throws Exceptio
verify(authorizedClientService).saveAuthorizedClient(any(), any());
}
+ @WithMockUser
+ @Test
+ public void requestWhenAuthorizedClientFoundThenMethodArgumentResolved() throws Exception {
+ this.spring.configLocations(xml("AuthorizedClientArgumentResolver")).autowire();
+
+ ClientRegistration clientRegistration = this.clientRegistrationRepository.findByRegistrationId("google-login");
+
+ OAuth2AuthorizedClient authorizedClient = new OAuth2AuthorizedClient(
+ clientRegistration, "user", TestOAuth2AccessTokens.noScopes());
+ when(this.authorizedClientRepository.loadAuthorizedClient(any(), any(), any()))
+ .thenReturn(authorizedClient);
+
+ this.mvc.perform(get("/authorized-client"))
+ .andExpect(status().isOk())
+ .andExpect(content().string("resolved"));
+ }
+
+ @RestController
+ static class AuthorizedClientController {
+
+ @GetMapping("/authorized-client")
+ String authorizedClient(Model model, @RegisteredOAuth2AuthorizedClient("google") OAuth2AuthorizedClient authorizedClient) {
+ return authorizedClient != null ? "resolved" : "not-resolved";
+ }
+ }
+
private String xml(String configName) {
return CONFIG_LOCATION_PREFIX + "-" + configName + ".xml";
}
diff --git a/config/src/test/java/org/springframework/security/config/oauth2/client/ClientRegistrationsBeanDefinitionParserTests.java b/config/src/test/java/org/springframework/security/config/oauth2/client/ClientRegistrationsBeanDefinitionParserTests.java
index 606af24ae41..0a2eac541fa 100644
--- a/config/src/test/java/org/springframework/security/config/oauth2/client/ClientRegistrationsBeanDefinitionParserTests.java
+++ b/config/src/test/java/org/springframework/security/config/oauth2/client/ClientRegistrationsBeanDefinitionParserTests.java
@@ -165,6 +165,7 @@ public void parseWhenIssuerUriConfiguredThenRequestConfigFromIssuer() throws Exc
.isEqualTo(AuthenticationMethod.HEADER);
assertThat(googleProviderDetails.getUserInfoEndpoint().getUserNameAttributeName()).isEqualTo("sub");
assertThat(googleProviderDetails.getJwkSetUri()).isEqualTo("https://example.com/oauth2/v3/certs");
+ assertThat(googleProviderDetails.getIssuerUri()).isEqualTo(serverUrl);
}
@Test
@@ -195,6 +196,7 @@ public void parseWhenMultipleClientsConfiguredThenAvailableInRepository() {
.isEqualTo(AuthenticationMethod.HEADER);
assertThat(googleProviderDetails.getUserInfoEndpoint().getUserNameAttributeName()).isEqualTo("sub");
assertThat(googleProviderDetails.getJwkSetUri()).isEqualTo("https://www.googleapis.com/oauth2/v3/certs");
+ assertThat(googleProviderDetails.getIssuerUri()).isEqualTo("https://accounts.google.com");
ClientRegistration githubRegistration = clientRegistrationRepository.findByRegistrationId("github-login");
assertThat(githubRegistration).isNotNull();
diff --git a/config/src/test/java/org/springframework/security/config/oauth2/client/CommonOAuth2ProviderTests.java b/config/src/test/java/org/springframework/security/config/oauth2/client/CommonOAuth2ProviderTests.java
index 89111a9f544..8b8ec4f3abb 100644
--- a/config/src/test/java/org/springframework/security/config/oauth2/client/CommonOAuth2ProviderTests.java
+++ b/config/src/test/java/org/springframework/security/config/oauth2/client/CommonOAuth2ProviderTests.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2018 the original author or authors.
+ * Copyright 2002-2020 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.
@@ -47,6 +47,8 @@ public void getBuilderWhenGoogleShouldHaveGoogleSettings() {
.isEqualTo(IdTokenClaimNames.SUB);
assertThat(providerDetails.getJwkSetUri())
.isEqualTo("https://www.googleapis.com/oauth2/v3/certs");
+ assertThat(providerDetails.getIssuerUri())
+ .isEqualTo("https://accounts.google.com");
assertThat(registration.getClientAuthenticationMethod())
.isEqualTo(ClientAuthenticationMethod.BASIC);
assertThat(registration.getAuthorizationGrantType())
diff --git a/config/src/test/java/org/springframework/security/config/test/SpringTestContext.java b/config/src/test/java/org/springframework/security/config/test/SpringTestContext.java
index 4909d66107a..09d580acc7c 100644
--- a/config/src/test/java/org/springframework/security/config/test/SpringTestContext.java
+++ b/config/src/test/java/org/springframework/security/config/test/SpringTestContext.java
@@ -17,7 +17,6 @@
package org.springframework.security.config.test;
import org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor;
-import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.mock.web.MockServletConfig;
import org.springframework.mock.web.MockServletContext;
import org.springframework.security.config.util.InMemoryXmlWebApplicationContext;
@@ -113,8 +112,10 @@ private SpringTestContext addFilter(Filter filter) {
return this;
}
- public ConfigurableApplicationContext getContext() {
+ public ConfigurableWebApplicationContext getContext() {
if (!this.context.isRunning()) {
+ this.context.setServletContext(new MockServletContext());
+ this.context.setServletConfig(new MockServletConfig());
this.context.refresh();
}
return this.context;
diff --git a/config/src/test/java/org/springframework/security/config/util/InMemoryXmlApplicationContext.java b/config/src/test/java/org/springframework/security/config/util/InMemoryXmlApplicationContext.java
index 78422a06fc7..ac7b485f756 100644
--- a/config/src/test/java/org/springframework/security/config/util/InMemoryXmlApplicationContext.java
+++ b/config/src/test/java/org/springframework/security/config/util/InMemoryXmlApplicationContext.java
@@ -41,7 +41,7 @@ public class InMemoryXmlApplicationContext extends AbstractXmlApplicationContext
+ "http://www.springframework.org/schema/security https://www.springframework.org/schema/security/spring-security-";
static final String BEANS_CLOSE = "\n";
- static final String SPRING_SECURITY_VERSION = "5.3";
+ static final String SPRING_SECURITY_VERSION = "5.4";
Resource inMemoryXml;
diff --git a/config/src/test/java/org/springframework/security/config/web/server/LogoutSpecTests.java b/config/src/test/java/org/springframework/security/config/web/server/LogoutSpecTests.java
index e417a3cda56..723251e4cf2 100644
--- a/config/src/test/java/org/springframework/security/config/web/server/LogoutSpecTests.java
+++ b/config/src/test/java/org/springframework/security/config/web/server/LogoutSpecTests.java
@@ -21,6 +21,7 @@
import org.springframework.security.config.annotation.web.reactive.ServerHttpSecurityConfigurationBuilder;
import org.springframework.security.htmlunit.server.WebTestClientHtmlUnitDriverBuilder;
import org.springframework.security.web.server.SecurityWebFilterChain;
+import org.springframework.security.web.server.context.WebSessionServerSecurityContextRepository;
import org.springframework.security.web.server.util.matcher.ServerWebExchangeMatchers;
import org.springframework.test.web.reactive.server.WebTestClient;
import org.springframework.security.test.web.reactive.server.WebTestClientBuilder;
@@ -200,4 +201,46 @@ public void logoutWhenDisabledThenPostToLogoutDoesNothing() {
homePage
.assertAt();
}
+
+
+ @Test
+ public void logoutWhenCustomSecurityContextRepositoryThenLogsOut() {
+ WebSessionServerSecurityContextRepository repository = new WebSessionServerSecurityContextRepository();
+ repository.setSpringSecurityContextAttrName("CUSTOM_CONTEXT_ATTR");
+ SecurityWebFilterChain securityWebFilter = this.http
+ .securityContextRepository(repository)
+ .authorizeExchange()
+ .anyExchange().authenticated()
+ .and()
+ .formLogin()
+ .and()
+ .logout()
+ .and()
+ .build();
+
+ WebTestClient webTestClient = WebTestClientBuilder
+ .bindToWebFilters(securityWebFilter)
+ .build();
+
+ WebDriver driver = WebTestClientHtmlUnitDriverBuilder
+ .webTestClientSetup(webTestClient)
+ .build();
+
+ FormLoginTests.DefaultLoginPage loginPage = FormLoginTests.HomePage.to(driver, FormLoginTests.DefaultLoginPage.class)
+ .assertAt();
+
+ FormLoginTests.HomePage homePage = loginPage.loginForm()
+ .username("user")
+ .password("password")
+ .submit(FormLoginTests.HomePage.class);
+
+ homePage.assertAt();
+
+ FormLoginTests.DefaultLogoutPage.to(driver)
+ .assertAt()
+ .logout();
+
+ FormLoginTests.HomePage.to(driver, FormLoginTests.DefaultLoginPage.class)
+ .assertAt();
+ }
}
diff --git a/config/src/test/java/org/springframework/security/config/web/server/OAuth2ClientSpecTests.java b/config/src/test/java/org/springframework/security/config/web/server/OAuth2ClientSpecTests.java
index 4e07a65d785..0ea9c446da6 100644
--- a/config/src/test/java/org/springframework/security/config/web/server/OAuth2ClientSpecTests.java
+++ b/config/src/test/java/org/springframework/security/config/web/server/OAuth2ClientSpecTests.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2019 the original author or authors.
+ * Copyright 2002-2020 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.
@@ -16,6 +16,8 @@
package org.springframework.security.config.web.server;
+import java.net.URI;
+
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -48,6 +50,7 @@
import org.springframework.security.test.context.support.WithMockUser;
import org.springframework.security.web.server.SecurityWebFilterChain;
import org.springframework.security.web.server.authentication.ServerAuthenticationConverter;
+import org.springframework.security.web.server.savedrequest.ServerRequestCache;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.test.web.reactive.server.WebTestClient;
import org.springframework.web.bind.annotation.GetMapping;
@@ -62,6 +65,7 @@
/**
* @author Rob Winch
+ * @author Parikshit Dutta
* @since 5.1
*/
@RunWith(SpringRunner.class)
@@ -146,6 +150,7 @@ public void oauth2ClientWhenCustomObjectsThenUsed() {
ServerAuthenticationConverter converter = config.authenticationConverter;
ReactiveAuthenticationManager manager = config.manager;
ServerAuthorizationRequestRepository authorizationRequestRepository = config.authorizationRequestRepository;
+ ServerRequestCache requestCache = config.requestCache;
OAuth2AuthorizationRequest authorizationRequest = TestOAuth2AuthorizationRequests.request()
.redirectUri("/authorize/oauth2/code/registration-id")
@@ -163,6 +168,7 @@ public void oauth2ClientWhenCustomObjectsThenUsed() {
when(authorizationRequestRepository.loadAuthorizationRequest(any())).thenReturn(Mono.just(authorizationRequest));
when(converter.convert(any())).thenReturn(Mono.just(new TestingAuthenticationToken("a", "b", "c")));
when(manager.authenticate(any())).thenReturn(Mono.just(result));
+ when(requestCache.getRedirectUri(any())).thenReturn(Mono.just(URI.create("/saved-request")));
this.client.get()
.uri(uriBuilder ->
@@ -175,6 +181,7 @@ public void oauth2ClientWhenCustomObjectsThenUsed() {
verify(converter).convert(any());
verify(manager).authenticate(any());
+ verify(requestCache).getRedirectUri(any());
}
@EnableWebFlux
@@ -197,13 +204,17 @@ static class OAuth2ClientCustomConfig {
ServerAuthorizationRequestRepository authorizationRequestRepository = mock(ServerAuthorizationRequestRepository.class);
+ ServerRequestCache requestCache = mock(ServerRequestCache.class);
+
@Bean
public SecurityWebFilterChain springSecurityFilter(ServerHttpSecurity http) {
http
.oauth2Client()
.authenticationConverter(this.authenticationConverter)
.authenticationManager(this.manager)
- .authorizationRequestRepository(this.authorizationRequestRepository);
+ .authorizationRequestRepository(this.authorizationRequestRepository)
+ .and()
+ .requestCache(c -> c.requestCache(this.requestCache));
return http.build();
}
}
@@ -217,6 +228,7 @@ public void oauth2ClientWhenCustomObjectsInLambdaThenUsed() {
ServerAuthenticationConverter converter = config.authenticationConverter;
ReactiveAuthenticationManager manager = config.manager;
ServerAuthorizationRequestRepository authorizationRequestRepository = config.authorizationRequestRepository;
+ ServerRequestCache requestCache = config.requestCache;
OAuth2AuthorizationRequest authorizationRequest = TestOAuth2AuthorizationRequests.request()
.redirectUri("/authorize/oauth2/code/registration-id")
@@ -234,6 +246,7 @@ public void oauth2ClientWhenCustomObjectsInLambdaThenUsed() {
when(authorizationRequestRepository.loadAuthorizationRequest(any())).thenReturn(Mono.just(authorizationRequest));
when(converter.convert(any())).thenReturn(Mono.just(new TestingAuthenticationToken("a", "b", "c")));
when(manager.authenticate(any())).thenReturn(Mono.just(result));
+ when(requestCache.getRedirectUri(any())).thenReturn(Mono.just(URI.create("/saved-request")));
this.client.get()
.uri(uriBuilder ->
@@ -246,6 +259,7 @@ public void oauth2ClientWhenCustomObjectsInLambdaThenUsed() {
verify(converter).convert(any());
verify(manager).authenticate(any());
+ verify(requestCache).getRedirectUri(any());
}
@Configuration
@@ -256,6 +270,8 @@ static class OAuth2ClientInLambdaCustomConfig {
ServerAuthorizationRequestRepository authorizationRequestRepository = mock(ServerAuthorizationRequestRepository.class);
+ ServerRequestCache requestCache = mock(ServerRequestCache.class);
+
@Bean
public SecurityWebFilterChain springSecurityFilter(ServerHttpSecurity http) {
http
@@ -263,8 +279,8 @@ public SecurityWebFilterChain springSecurityFilter(ServerHttpSecurity http) {
oauth2Client
.authenticationConverter(this.authenticationConverter)
.authenticationManager(this.manager)
- .authorizationRequestRepository(this.authorizationRequestRepository)
- );
+ .authorizationRequestRepository(this.authorizationRequestRepository))
+ .requestCache(c -> c.requestCache(this.requestCache));
return http.build();
}
}
diff --git a/config/src/test/java/org/springframework/security/config/web/server/OAuth2LoginTests.java b/config/src/test/java/org/springframework/security/config/web/server/OAuth2LoginTests.java
index a783a8212af..b1e5662a3ef 100644
--- a/config/src/test/java/org/springframework/security/config/web/server/OAuth2LoginTests.java
+++ b/config/src/test/java/org/springframework/security/config/web/server/OAuth2LoginTests.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2019 the original author or authors.
+ * Copyright 2002-2020 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.
@@ -24,6 +24,7 @@
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
+import org.springframework.http.HttpHeaders;
import org.springframework.security.authentication.ReactiveAuthenticationManager;
import org.springframework.security.authentication.TestingAuthenticationToken;
import org.springframework.security.config.annotation.web.reactive.EnableWebFluxSecurity;
@@ -82,6 +83,7 @@
import org.springframework.security.web.server.authentication.ServerAuthenticationConverter;
import org.springframework.security.web.server.authentication.ServerAuthenticationFailureHandler;
import org.springframework.security.web.server.authentication.ServerAuthenticationSuccessHandler;
+import org.springframework.security.web.server.authentication.logout.SecurityContextServerLogoutHandler;
import org.springframework.security.web.server.context.ServerSecurityContextRepository;
import org.springframework.security.web.server.savedrequest.ServerRequestCache;
import org.springframework.security.web.server.util.matcher.ServerWebExchangeMatcher;
@@ -101,7 +103,10 @@
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.Mockito.*;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
import static org.springframework.security.oauth2.jwt.TestJwts.jwt;
/**
@@ -185,6 +190,22 @@ public void defaultLoginPageWithSingleClientRegistrationThenRedirect() {
assertThat(driver.getCurrentUrl()).startsWith("https://github.com/login/oauth/authorize");
}
+ // gh-8118
+ @Test
+ public void defaultLoginPageWithSingleClientRegistrationAndXhrRequestThenDoesNotRedirectForAuthorization() {
+ this.spring.register(OAuth2LoginWithSingleClientRegistrations.class, WebFluxConfig.class).autowire();
+
+ this.client.get()
+ .uri("/")
+ .header("X-Requested-With", "XMLHttpRequest")
+ .exchange()
+ .expectStatus().is3xxRedirection()
+ .expectHeader().valueEquals(HttpHeaders.LOCATION, "/login");
+ }
+
+ @EnableWebFlux
+ static class WebFluxConfig { }
+
@EnableWebFluxSecurity
static class OAuth2LoginWithSingleClientRegistrations {
@Bean
@@ -665,7 +686,6 @@ private ReactiveJwtDecoder getJwtDecoder() {
}
}
-
@Test
public void logoutWhenUsingOidcLogoutHandlerThenRedirects() {
this.spring.register(OAuth2LoginConfigWithOidcLogoutSuccessHandler.class).autowire();
@@ -699,6 +719,8 @@ public SecurityWebFilterChain springSecurity(ServerHttpSecurity http) {
http
.csrf().disable()
.logout()
+ // avoid using mock ServerSecurityContextRepository for logout
+ .logoutHandler(new SecurityContextServerLogoutHandler())
.logoutSuccessHandler(
new OidcClientInitiatedServerLogoutSuccessHandler(
new InMemoryReactiveClientRegistrationRepository(this.withLogout)))
@@ -719,6 +741,24 @@ ClientRegistration clientRegistration() {
}
}
+ // gh-8609
+ @Test
+ public void oauth2LoginWhenAuthenticationConverterFailsThenDefaultRedirectToLogin() {
+ this.spring.register(OAuth2LoginWithMultipleClientRegistrations.class).autowire();
+
+ WebTestClient webTestClient = WebTestClientBuilder
+ .bindToWebFilters(this.springSecurity)
+ .build();
+
+ webTestClient.get()
+ .uri("/login/oauth2/code/google")
+ .exchange()
+ .expectStatus()
+ .is3xxRedirection()
+ .expectHeader()
+ .valueEquals("Location", "/login?error");
+ }
+
static class GitHubWebFilter implements WebFilter {
@Override
diff --git a/config/src/test/java/org/springframework/security/config/web/server/OAuth2ResourceServerSpecTests.java b/config/src/test/java/org/springframework/security/config/web/server/OAuth2ResourceServerSpecTests.java
index b059c008802..943fe62d712 100644
--- a/config/src/test/java/org/springframework/security/config/web/server/OAuth2ResourceServerSpecTests.java
+++ b/config/src/test/java/org/springframework/security/config/web/server/OAuth2ResourceServerSpecTests.java
@@ -698,7 +698,7 @@ SecurityWebFilterChain springSecurity(ServerHttpSecurity http) {
// @formatter:off
http
.authorizeExchange()
- .pathMatchers("/**/message/**").hasAnyAuthority("SCOPE_message:read")
+ .pathMatchers("/*/message/**").hasAnyAuthority("SCOPE_message:read")
.and()
.oauth2ResourceServer()
.authenticationManagerResolver(authenticationManagerResolver());
diff --git a/config/src/test/kotlin/org/springframework/security/config/web/server/AuthorizeExchangeDslTests.kt b/config/src/test/kotlin/org/springframework/security/config/web/server/AuthorizeExchangeDslTests.kt
new file mode 100644
index 00000000000..b39a0564e53
--- /dev/null
+++ b/config/src/test/kotlin/org/springframework/security/config/web/server/AuthorizeExchangeDslTests.kt
@@ -0,0 +1,183 @@
+/*
+ * Copyright 2002-2020 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.server
+
+import org.junit.Rule
+import org.junit.Test
+import org.springframework.beans.factory.annotation.Autowired
+import org.springframework.context.ApplicationContext
+import org.springframework.context.annotation.Bean
+import org.springframework.security.config.annotation.web.reactive.EnableWebFluxSecurity
+import org.springframework.security.core.userdetails.MapReactiveUserDetailsService
+import org.springframework.security.core.userdetails.User
+import org.springframework.security.config.test.SpringTestRule
+import org.springframework.security.web.server.SecurityWebFilterChain
+import org.springframework.test.web.reactive.server.WebTestClient
+import org.springframework.web.bind.annotation.RequestMapping
+import org.springframework.web.bind.annotation.RestController
+import org.springframework.web.reactive.config.EnableWebFlux
+import java.util.*
+
+/**
+ * Tests for [AuthorizeExchangeDsl]
+ *
+ * @author Eleftheria Stein
+ */
+class AuthorizeExchangeDslTests {
+ @Rule
+ @JvmField
+ val spring = SpringTestRule()
+
+ private lateinit var client: WebTestClient
+
+ @Autowired
+ fun setup(context: ApplicationContext) {
+ this.client = WebTestClient
+ .bindToApplicationContext(context)
+ .configureClient()
+ .build()
+ }
+
+ @Test
+ fun `request when secured by matcher then responds with unauthorized`() {
+ this.spring.register(MatcherAuthenticatedConfig::class.java).autowire()
+
+ this.client.get()
+ .uri("/")
+ .exchange()
+ .expectStatus().isUnauthorized
+ }
+
+ @EnableWebFluxSecurity
+ @EnableWebFlux
+ open class MatcherAuthenticatedConfig {
+ @Bean
+ open fun springWebFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain {
+ return http {
+ authorizeExchange {
+ authorize(anyExchange, authenticated)
+ }
+ }
+ }
+ }
+
+ @Test
+ fun `request when allowed by matcher then responds with ok`() {
+ this.spring.register(MatcherPermitAllConfig::class.java).autowire()
+
+ this.client.get()
+ .uri("/")
+ .exchange()
+ .expectStatus().isOk
+ }
+
+ @EnableWebFluxSecurity
+ @EnableWebFlux
+ open class MatcherPermitAllConfig {
+ @Bean
+ open fun springWebFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain {
+ return http {
+ authorizeExchange {
+ authorize(anyExchange, permitAll)
+ }
+ }
+ }
+
+ @RestController
+ internal class PathController {
+ @RequestMapping("/")
+ fun path() {
+ }
+ }
+ }
+
+ @Test
+ fun `request when secured by pattern then responds with unauthorized`() {
+ this.spring.register(PatternAuthenticatedConfig::class.java).autowire()
+
+ this.client.get()
+ .uri("/")
+ .exchange()
+ .expectStatus().isUnauthorized
+ }
+
+ @Test
+ fun `request when allowed by pattern then responds with ok`() {
+ this.spring.register(PatternAuthenticatedConfig::class.java).autowire()
+
+ this.client.get()
+ .uri("/public")
+ .exchange()
+ .expectStatus().isOk
+ }
+
+ @EnableWebFluxSecurity
+ @EnableWebFlux
+ open class PatternAuthenticatedConfig {
+ @Bean
+ open fun springWebFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain {
+ return http {
+ authorizeExchange {
+ authorize("/public", permitAll)
+ authorize("/**", authenticated)
+ }
+ }
+ }
+
+ @RestController
+ internal class PathController {
+ @RequestMapping("/public")
+ fun public() {
+ }
+ }
+ }
+
+ @Test
+ fun `request when missing required role then responds with forbidden`() {
+ this.spring.register(HasRoleConfig::class.java).autowire()
+ this.client
+ .get()
+ .uri("/")
+ .header("Authorization", "Basic " + Base64.getEncoder().encodeToString("user:password".toByteArray()))
+ .exchange()
+ .expectStatus().isForbidden
+ }
+
+ @EnableWebFluxSecurity
+ @EnableWebFlux
+ open class HasRoleConfig {
+ @Bean
+ open fun springWebFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain {
+ return http {
+ authorizeExchange {
+ authorize(anyExchange, hasRole("ADMIN"))
+ }
+ httpBasic { }
+ }
+ }
+
+ @Bean
+ open fun userDetailsService(): MapReactiveUserDetailsService {
+ val user = User.withDefaultPasswordEncoder()
+ .username("user")
+ .password("password")
+ .roles("USER")
+ .build()
+ return MapReactiveUserDetailsService(user)
+ }
+ }
+}
diff --git a/config/src/test/kotlin/org/springframework/security/config/web/server/ServerAnonymousDslTests.kt b/config/src/test/kotlin/org/springframework/security/config/web/server/ServerAnonymousDslTests.kt
new file mode 100644
index 00000000000..24d13b1c869
--- /dev/null
+++ b/config/src/test/kotlin/org/springframework/security/config/web/server/ServerAnonymousDslTests.kt
@@ -0,0 +1,192 @@
+/*
+ * Copyright 2002-2020 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.server
+
+import org.assertj.core.api.Assertions.assertThat
+import org.junit.Rule
+import org.junit.Test
+import org.springframework.beans.factory.annotation.Autowired
+import org.springframework.context.ApplicationContext
+import org.springframework.context.annotation.Bean
+import org.springframework.security.authentication.AnonymousAuthenticationToken
+import org.springframework.security.config.annotation.web.reactive.EnableWebFluxSecurity
+import org.springframework.security.core.annotation.AuthenticationPrincipal
+import org.springframework.security.core.authority.SimpleGrantedAuthority
+import org.springframework.security.config.test.SpringTestRule
+import org.springframework.security.web.server.SecurityWebFilterChain
+import org.springframework.test.web.reactive.server.WebTestClient
+import org.springframework.test.web.reactive.server.expectBody
+import org.springframework.web.bind.annotation.GetMapping
+import org.springframework.web.bind.annotation.RestController
+import org.springframework.web.reactive.config.EnableWebFlux
+import reactor.core.publisher.Mono
+
+/**
+ * Tests for [ServerAnonymousDsl]
+ *
+ * @author Eleftheria Stein
+ */
+class ServerAnonymousDslTests {
+ @Rule
+ @JvmField
+ val spring = SpringTestRule()
+
+ private lateinit var client: WebTestClient
+
+ @Autowired
+ fun setup(context: ApplicationContext) {
+ this.client = WebTestClient
+ .bindToApplicationContext(context)
+ .configureClient()
+ .build()
+ }
+
+ @Test
+ fun `authentication when anonymous enabled then is of type anonymous authentication`() {
+ this.spring.register(AnonymousConfig::class.java, HttpMeController::class.java).autowire()
+
+ this.client.get()
+ .uri("/principal")
+ .exchange()
+ .expectStatus().isOk
+ .expectBody().isEqualTo("anonymousUser")
+ }
+
+ @EnableWebFluxSecurity
+ @EnableWebFlux
+ open class AnonymousConfig {
+ @Bean
+ open fun springWebFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain {
+ return http {
+ anonymous { }
+ }
+ }
+ }
+
+ @Test
+ fun `anonymous when custom principal specified then custom principal is used`() {
+ this.spring.register(CustomPrincipalConfig::class.java, HttpMeController::class.java).autowire()
+
+ this.client.get()
+ .uri("/principal")
+ .exchange()
+ .expectStatus().isOk
+ .expectBody().isEqualTo("anon")
+ }
+
+ @EnableWebFluxSecurity
+ @EnableWebFlux
+ open class CustomPrincipalConfig {
+ @Bean
+ open fun springWebFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain {
+ return http {
+ anonymous {
+ principal = "anon"
+ }
+ }
+ }
+ }
+
+ @Test
+ fun `anonymous when disabled then principal is null`() {
+ this.spring.register(AnonymousDisabledConfig::class.java, HttpMeController::class.java).autowire()
+
+ this.client.get()
+ .uri("/principal")
+ .exchange()
+ .expectStatus().isOk
+ .expectBody().consumeWith { body -> assertThat(body.responseBody).isNull() }
+ }
+
+ @EnableWebFluxSecurity
+ @EnableWebFlux
+ open class AnonymousDisabledConfig {
+ @Bean
+ open fun springWebFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain {
+ return http {
+ anonymous {
+ disable()
+ }
+ }
+ }
+ }
+
+ @Test
+ fun `anonymous when custom key specified then custom key used`() {
+ this.spring.register(CustomKeyConfig::class.java, HttpMeController::class.java).autowire()
+
+ this.client.get()
+ .uri("/key")
+ .exchange()
+ .expectStatus().isOk
+ .expectBody().isEqualTo("key".hashCode().toString())
+ }
+
+ @EnableWebFluxSecurity
+ @EnableWebFlux
+ open class CustomKeyConfig {
+ @Bean
+ open fun springWebFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain {
+ return http {
+ anonymous {
+ key = "key"
+ }
+ }
+ }
+ }
+
+ @Test
+ fun `anonymous when custom authorities specified then custom authorities used`() {
+ this.spring.register(CustomAuthoritiesConfig::class.java, HttpMeController::class.java).autowire()
+
+ this.client.get()
+ .uri("/principal")
+ .exchange()
+ .expectStatus().isOk
+ }
+
+ @EnableWebFluxSecurity
+ @EnableWebFlux
+ open class CustomAuthoritiesConfig {
+ @Bean
+ open fun springWebFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain {
+ return http {
+ anonymous {
+ authorities = listOf(SimpleGrantedAuthority("TEST"))
+ }
+ authorizeExchange {
+ authorize(anyExchange, hasAuthority("TEST"))
+ }
+ }
+ }
+ }
+
+ @RestController
+ class HttpMeController {
+ @GetMapping("/principal")
+ fun principal(@AuthenticationPrincipal principal: String?): String? {
+ return principal
+ }
+
+ @GetMapping("/key")
+ fun key(@AuthenticationPrincipal principal: Mono): Mono {
+ return principal
+ .map { it.keyHash }
+ .map { it.toString() }
+ }
+ }
+}
diff --git a/config/src/test/kotlin/org/springframework/security/config/web/server/ServerCacheControlDslTests.kt b/config/src/test/kotlin/org/springframework/security/config/web/server/ServerCacheControlDslTests.kt
new file mode 100644
index 00000000000..b1c5d06d463
--- /dev/null
+++ b/config/src/test/kotlin/org/springframework/security/config/web/server/ServerCacheControlDslTests.kt
@@ -0,0 +1,102 @@
+/*
+ * Copyright 2002-2020 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.server
+
+import org.junit.Rule
+import org.junit.Test
+import org.springframework.beans.factory.annotation.Autowired
+import org.springframework.context.ApplicationContext
+import org.springframework.context.annotation.Bean
+import org.springframework.http.HttpHeaders
+import org.springframework.security.config.annotation.web.reactive.EnableWebFluxSecurity
+import org.springframework.security.config.test.SpringTestRule
+import org.springframework.security.web.server.SecurityWebFilterChain
+import org.springframework.test.web.reactive.server.WebTestClient
+import org.springframework.web.reactive.config.EnableWebFlux
+
+/**
+ * Tests for [ServerCacheControlDsl]
+ *
+ * @author Eleftheria Stein
+ */
+class ServerCacheControlDslTests {
+ @Rule
+ @JvmField
+ val spring = SpringTestRule()
+
+ private lateinit var client: WebTestClient
+
+ @Autowired
+ fun setup(context: ApplicationContext) {
+ this.client = WebTestClient
+ .bindToApplicationContext(context)
+ .configureClient()
+ .build()
+ }
+
+ @Test
+ fun `request when cache control configured then cache headers in response`() {
+ this.spring.register(CacheControlConfig::class.java).autowire()
+
+ this.client.get()
+ .uri("/")
+ .exchange()
+ .expectHeader().valueEquals(HttpHeaders.CACHE_CONTROL, "no-cache, no-store, max-age=0, must-revalidate")
+ .expectHeader().valueEquals(HttpHeaders.EXPIRES, "0")
+ .expectHeader().valueEquals(HttpHeaders.PRAGMA, "no-cache")
+ }
+
+ @EnableWebFluxSecurity
+ @EnableWebFlux
+ open class CacheControlConfig {
+ @Bean
+ open fun springWebFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain {
+ return http {
+ headers {
+ cache { }
+ }
+ }
+ }
+ }
+
+ @Test
+ fun `request when cache control disabled then no cache headers in response`() {
+ this.spring.register(CacheControlDisabledConfig::class.java).autowire()
+
+ this.client.get()
+ .uri("/")
+ .exchange()
+ .expectHeader().doesNotExist(HttpHeaders.CACHE_CONTROL)
+ .expectHeader().doesNotExist(HttpHeaders.EXPIRES)
+ .expectHeader().doesNotExist(HttpHeaders.PRAGMA)
+ }
+
+ @EnableWebFluxSecurity
+ @EnableWebFlux
+ open class CacheControlDisabledConfig {
+ @Bean
+ open fun springWebFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain {
+ return http {
+ headers {
+ cache {
+ disable()
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/config/src/test/kotlin/org/springframework/security/config/web/server/ServerContentSecurityPolicyDslTests.kt b/config/src/test/kotlin/org/springframework/security/config/web/server/ServerContentSecurityPolicyDslTests.kt
new file mode 100644
index 00000000000..69699d95632
--- /dev/null
+++ b/config/src/test/kotlin/org/springframework/security/config/web/server/ServerContentSecurityPolicyDslTests.kt
@@ -0,0 +1,123 @@
+/*
+ * Copyright 2002-2020 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.server
+
+import org.junit.Rule
+import org.junit.Test
+import org.springframework.beans.factory.annotation.Autowired
+import org.springframework.context.ApplicationContext
+import org.springframework.context.annotation.Bean
+import org.springframework.security.config.annotation.web.reactive.EnableWebFluxSecurity
+import org.springframework.security.config.test.SpringTestRule
+import org.springframework.security.web.server.SecurityWebFilterChain
+import org.springframework.security.web.server.header.ContentSecurityPolicyServerHttpHeadersWriter
+import org.springframework.test.web.reactive.server.WebTestClient
+import org.springframework.web.reactive.config.EnableWebFlux
+
+/**
+ * Tests for [ServerContentSecurityPolicyDsl]
+ *
+ * @author Eleftheria Stein
+ */
+class ServerContentSecurityPolicyDslTests {
+ @Rule
+ @JvmField
+ val spring = SpringTestRule()
+
+ private lateinit var client: WebTestClient
+
+ @Autowired
+ fun setup(context: ApplicationContext) {
+ this.client = WebTestClient
+ .bindToApplicationContext(context)
+ .configureClient()
+ .build()
+ }
+
+ @Test
+ fun `request when content security policy configured then content security policy header in response`() {
+ this.spring.register(ContentSecurityPolicyConfig::class.java).autowire()
+
+ this.client.get()
+ .uri("https://example.com")
+ .exchange()
+ .expectHeader().valueEquals(ContentSecurityPolicyServerHttpHeadersWriter.CONTENT_SECURITY_POLICY, "default-src 'self'")
+ }
+
+ @EnableWebFluxSecurity
+ @EnableWebFlux
+ open class ContentSecurityPolicyConfig {
+ @Bean
+ open fun springWebFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain {
+ return http {
+ headers {
+ contentSecurityPolicy { }
+ }
+ }
+ }
+ }
+
+ @Test
+ fun `request when custom policy directives then custom policy directive in response header`() {
+ this.spring.register(CustomPolicyDirectivesConfig::class.java).autowire()
+
+ this.client.get()
+ .uri("https://example.com")
+ .exchange()
+ .expectHeader().valueEquals(ContentSecurityPolicyServerHttpHeadersWriter.CONTENT_SECURITY_POLICY, "default-src 'self'; script-src trustedscripts.example.com")
+ }
+
+ @EnableWebFluxSecurity
+ @EnableWebFlux
+ open class CustomPolicyDirectivesConfig {
+ @Bean
+ open fun springWebFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain {
+ return http {
+ headers {
+ contentSecurityPolicy {
+ policyDirectives = "default-src 'self'; script-src trustedscripts.example.com"
+ }
+ }
+ }
+ }
+ }
+
+ @Test
+ fun `request when report only configured then content security policy report only header in response`() {
+ this.spring.register(ReportOnlyConfig::class.java).autowire()
+
+ this.client.get()
+ .uri("https://example.com")
+ .exchange()
+ .expectHeader().valueEquals(ContentSecurityPolicyServerHttpHeadersWriter.CONTENT_SECURITY_POLICY_REPORT_ONLY, "default-src 'self'")
+ }
+
+ @EnableWebFluxSecurity
+ @EnableWebFlux
+ open class ReportOnlyConfig {
+ @Bean
+ open fun springWebFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain {
+ return http {
+ headers {
+ contentSecurityPolicy {
+ reportOnly = true
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/config/src/test/kotlin/org/springframework/security/config/web/server/ServerContentTypeOptionsDslTests.kt b/config/src/test/kotlin/org/springframework/security/config/web/server/ServerContentTypeOptionsDslTests.kt
new file mode 100644
index 00000000000..a27f4e65cc7
--- /dev/null
+++ b/config/src/test/kotlin/org/springframework/security/config/web/server/ServerContentTypeOptionsDslTests.kt
@@ -0,0 +1,98 @@
+/*
+ * Copyright 2002-2020 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.server
+
+import org.junit.Rule
+import org.junit.Test
+import org.springframework.beans.factory.annotation.Autowired
+import org.springframework.context.ApplicationContext
+import org.springframework.context.annotation.Bean
+import org.springframework.security.config.annotation.web.reactive.EnableWebFluxSecurity
+import org.springframework.security.config.test.SpringTestRule
+import org.springframework.security.web.server.SecurityWebFilterChain
+import org.springframework.security.web.server.header.ContentTypeOptionsServerHttpHeadersWriter
+import org.springframework.test.web.reactive.server.WebTestClient
+import org.springframework.web.reactive.config.EnableWebFlux
+
+/**
+ * Tests for [ServerContentTypeOptionsDsl]
+ *
+ * @author Eleftheria Stein
+ */
+class ServerContentTypeOptionsDslTests {
+ @Rule
+ @JvmField
+ val spring = SpringTestRule()
+
+ private lateinit var client: WebTestClient
+
+ @Autowired
+ fun setup(context: ApplicationContext) {
+ this.client = WebTestClient
+ .bindToApplicationContext(context)
+ .configureClient()
+ .build()
+ }
+
+ @Test
+ fun `request when content type options configured then header in response`() {
+ this.spring.register(ContentTypeOptionsConfig::class.java).autowire()
+
+ this.client.get()
+ .uri("/")
+ .exchange()
+ .expectHeader().valueEquals(ContentTypeOptionsServerHttpHeadersWriter.X_CONTENT_OPTIONS, "nosniff")
+ }
+
+ @EnableWebFluxSecurity
+ @EnableWebFlux
+ open class ContentTypeOptionsConfig {
+ @Bean
+ open fun springWebFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain {
+ return http {
+ headers {
+ contentTypeOptions { }
+ }
+ }
+ }
+ }
+
+ @Test
+ fun `request when content type options disabled then no content type options header in response`() {
+ this.spring.register(ContentTypeOptionsDisabledConfig::class.java).autowire()
+
+ this.client.get()
+ .uri("/")
+ .exchange()
+ .expectHeader().doesNotExist(ContentTypeOptionsServerHttpHeadersWriter.X_CONTENT_OPTIONS)
+ }
+
+ @EnableWebFluxSecurity
+ @EnableWebFlux
+ open class ContentTypeOptionsDisabledConfig {
+ @Bean
+ open fun springWebFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain {
+ return http {
+ headers {
+ contentTypeOptions {
+ disable()
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/config/src/test/kotlin/org/springframework/security/config/web/server/ServerCorsDslTests.kt b/config/src/test/kotlin/org/springframework/security/config/web/server/ServerCorsDslTests.kt
new file mode 100644
index 00000000000..90256ccb546
--- /dev/null
+++ b/config/src/test/kotlin/org/springframework/security/config/web/server/ServerCorsDslTests.kt
@@ -0,0 +1,136 @@
+/*
+ * Copyright 2002-2020 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.server
+
+import org.junit.Rule
+import org.junit.Test
+import org.springframework.beans.factory.annotation.Autowired
+import org.springframework.context.ApplicationContext
+import org.springframework.context.annotation.Bean
+import org.springframework.http.HttpHeaders
+import org.springframework.security.config.annotation.web.reactive.EnableWebFluxSecurity
+import org.springframework.security.config.test.SpringTestRule
+import org.springframework.security.web.server.SecurityWebFilterChain
+import org.springframework.test.web.reactive.server.WebTestClient
+import org.springframework.web.cors.CorsConfiguration
+import org.springframework.web.cors.reactive.CorsConfigurationSource
+import org.springframework.web.cors.reactive.UrlBasedCorsConfigurationSource
+import org.springframework.web.reactive.config.EnableWebFlux
+
+/**
+ * Tests for [ServerCorsDsl]
+ *
+ * @author Eleftheria Stein
+ */
+class ServerCorsDslTests {
+ @Rule
+ @JvmField
+ val spring = SpringTestRule()
+
+ private lateinit var client: WebTestClient
+
+ @Autowired
+ fun setup(context: ApplicationContext) {
+ this.client = WebTestClient
+ .bindToApplicationContext(context)
+ .configureClient()
+ .build()
+ }
+
+ @Test
+ fun `request when CORS configured using bean then Access-Control-Allow-Origin header in response`() {
+ this.spring.register(CorsBeanConfig::class.java).autowire()
+
+ this.client.get()
+ .uri("https://example.com")
+ .header(HttpHeaders.ORIGIN, "https://origin.example.com")
+ .exchange()
+ .expectHeader().valueEquals("Access-Control-Allow-Origin", "*")
+ }
+
+ @EnableWebFluxSecurity
+ @EnableWebFlux
+ open class CorsBeanConfig {
+ @Bean
+ open fun springWebFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain {
+ return http {
+ cors { }
+ }
+ }
+
+ @Bean
+ open fun corsConfigurationSource(): CorsConfigurationSource {
+ val source = UrlBasedCorsConfigurationSource()
+ val corsConfiguration = CorsConfiguration()
+ corsConfiguration.allowedOrigins = listOf("*")
+ source.registerCorsConfiguration("/**", corsConfiguration)
+ return source
+ }
+ }
+
+ @Test
+ fun `request when CORS configured using source then Access-Control-Allow-Origin header in response`() {
+ this.spring.register(CorsSourceConfig::class.java).autowire()
+
+ this.client.get()
+ .uri("https://example.com")
+ .header(HttpHeaders.ORIGIN, "https://origin.example.com")
+ .exchange()
+ .expectHeader().valueEquals("Access-Control-Allow-Origin", "*")
+ }
+
+ @EnableWebFluxSecurity
+ @EnableWebFlux
+ open class CorsSourceConfig {
+ @Bean
+ open fun springWebFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain {
+ val source = UrlBasedCorsConfigurationSource()
+ val corsConfiguration = CorsConfiguration()
+ corsConfiguration.allowedOrigins = listOf("*")
+ source.registerCorsConfiguration("/**", corsConfiguration)
+ return http {
+ cors {
+ configurationSource = source
+ }
+ }
+ }
+ }
+
+ @Test
+ fun `request when CORS disabled then no Access-Control-Allow-Origin header in response`() {
+ this.spring.register(CorsDisabledConfig::class.java).autowire()
+
+ this.client.get()
+ .uri("https://example.com")
+ .header(HttpHeaders.ORIGIN, "https://origin.example.com")
+ .exchange()
+ .expectHeader().doesNotExist("Access-Control-Allow-Origin")
+ }
+
+ @EnableWebFluxSecurity
+ @EnableWebFlux
+ open class CorsDisabledConfig {
+ @Bean
+ open fun springWebFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain {
+ return http {
+ cors {
+ disable()
+ }
+ }
+ }
+ }
+}
diff --git a/config/src/test/kotlin/org/springframework/security/config/web/server/ServerCsrfDslTests.kt b/config/src/test/kotlin/org/springframework/security/config/web/server/ServerCsrfDslTests.kt
new file mode 100644
index 00000000000..d63d9e27f9a
--- /dev/null
+++ b/config/src/test/kotlin/org/springframework/security/config/web/server/ServerCsrfDslTests.kt
@@ -0,0 +1,294 @@
+/*
+ * Copyright 2002-2020 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.server
+
+import org.junit.Rule
+import org.junit.Test
+import org.mockito.ArgumentMatchers.any
+import org.mockito.Mockito
+import org.mockito.Mockito.`when`
+import org.mockito.Mockito.mock
+import org.springframework.beans.factory.annotation.Autowired
+import org.springframework.context.ApplicationContext
+import org.springframework.context.annotation.Bean
+import org.springframework.http.MediaType
+import org.springframework.security.config.annotation.web.reactive.EnableWebFluxSecurity
+import org.springframework.security.config.test.SpringTestRule
+import org.springframework.security.web.server.SecurityWebFilterChain
+import org.springframework.security.web.server.authorization.ServerAccessDeniedHandler
+import org.springframework.security.web.server.csrf.CsrfToken
+import org.springframework.security.web.server.csrf.DefaultCsrfToken
+import org.springframework.security.web.server.csrf.ServerCsrfTokenRepository
+import org.springframework.security.web.server.util.matcher.PathPatternParserServerWebExchangeMatcher
+import org.springframework.test.web.reactive.server.WebTestClient
+import org.springframework.web.bind.annotation.PostMapping
+import org.springframework.web.bind.annotation.RestController
+import org.springframework.web.reactive.config.EnableWebFlux
+import org.springframework.web.reactive.function.BodyInserters.fromMultipartData
+import reactor.core.publisher.Mono
+
+/**
+ * Tests for [ServerCsrfDsl]
+ *
+ * @author Eleftheria Stein
+ */
+class ServerCsrfDslTests {
+ @Rule
+ @JvmField
+ val spring = SpringTestRule()
+
+ private val token: CsrfToken = DefaultCsrfToken("csrf", "CSRF", "a")
+
+ private lateinit var client: WebTestClient
+
+ @Autowired
+ fun setup(context: ApplicationContext) {
+ this.client = WebTestClient
+ .bindToApplicationContext(context)
+ .configureClient()
+ .build()
+ }
+
+ @Test
+ fun `post when CSRF protection enabled then requires CSRF token`() {
+ this.spring.register(CsrfConfig::class.java).autowire()
+
+ this.client.post()
+ .uri("/")
+ .exchange()
+ .expectStatus().isForbidden
+ }
+
+ @EnableWebFluxSecurity
+ @EnableWebFlux
+ open class CsrfConfig {
+ @Bean
+ open fun springWebFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain {
+ return http {
+ csrf { }
+ }
+ }
+ }
+
+ @Test
+ fun `post when CSRF protection disabled then CSRF token is not required`() {
+ this.spring.register(CsrfDisabledConfig::class.java).autowire()
+
+ this.client.post()
+ .uri("/")
+ .exchange()
+ .expectStatus().isOk
+ }
+
+ @EnableWebFluxSecurity
+ @EnableWebFlux
+ open class CsrfDisabledConfig {
+ @Bean
+ open fun springWebFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain {
+ return http {
+ csrf {
+ disable()
+ }
+ }
+ }
+
+ @RestController
+ internal class TestController {
+ @PostMapping("/")
+ fun home() {
+ }
+ }
+ }
+
+ @Test
+ fun `post when request matches CSRF matcher then CSRF token required`() {
+ this.spring.register(CsrfMatcherConfig::class.java).autowire()
+
+ this.client.post()
+ .uri("/csrf")
+ .exchange()
+ .expectStatus().isForbidden
+ }
+
+ @Test
+ fun `post when request does not match CSRF matcher then CSRF token is not required`() {
+ this.spring.register(CsrfMatcherConfig::class.java).autowire()
+
+ this.client.post()
+ .uri("/")
+ .exchange()
+ .expectStatus().isOk
+ }
+
+ @EnableWebFluxSecurity
+ @EnableWebFlux
+ open class CsrfMatcherConfig {
+ @Bean
+ open fun springWebFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain {
+ return http {
+ csrf {
+ requireCsrfProtectionMatcher = PathPatternParserServerWebExchangeMatcher("/csrf")
+ }
+ }
+ }
+
+ @RestController
+ internal class TestController {
+ @PostMapping("/")
+ fun home() {
+ }
+
+ @PostMapping("/csrf")
+ fun csrf() {
+ }
+ }
+ }
+
+ @Test
+ fun `csrf when custom access denied handler then handler used`() {
+ this.spring.register(CustomAccessDeniedHandlerConfig::class.java).autowire()
+
+ this.client.post()
+ .uri("/")
+ .exchange()
+
+ Mockito.verify(CustomAccessDeniedHandlerConfig.ACCESS_DENIED_HANDLER)
+ .handle(any(), any())
+ }
+
+ @EnableWebFluxSecurity
+ @EnableWebFlux
+ open class CustomAccessDeniedHandlerConfig {
+ companion object {
+ var ACCESS_DENIED_HANDLER: ServerAccessDeniedHandler = mock(ServerAccessDeniedHandler::class.java)
+ }
+
+ @Bean
+ open fun springWebFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain {
+ return http {
+ csrf {
+ accessDeniedHandler = ACCESS_DENIED_HANDLER
+ }
+ }
+ }
+ }
+
+ @Test
+ fun `csrf when custom token repository then repository used`() {
+ `when`(CustomCsrfTokenRepositoryConfig.TOKEN_REPOSITORY.loadToken(any()))
+ .thenReturn(Mono.just(this.token))
+ this.spring.register(CustomCsrfTokenRepositoryConfig::class.java).autowire()
+
+ this.client.post()
+ .uri("/")
+ .exchange()
+
+ Mockito.verify(CustomCsrfTokenRepositoryConfig.TOKEN_REPOSITORY)
+ .loadToken(any())
+ }
+
+ @EnableWebFluxSecurity
+ @EnableWebFlux
+ open class CustomCsrfTokenRepositoryConfig {
+ companion object {
+ var TOKEN_REPOSITORY: ServerCsrfTokenRepository = mock(ServerCsrfTokenRepository::class.java)
+ }
+
+ @Bean
+ open fun springWebFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain {
+ return http {
+ csrf {
+ csrfTokenRepository = TOKEN_REPOSITORY
+ }
+ }
+ }
+ }
+
+ @Test
+ fun `csrf when multipart form data and not enabled then denied`() {
+ `when`(MultipartFormDataNotEnabledConfig.TOKEN_REPOSITORY.loadToken(any()))
+ .thenReturn(Mono.just(this.token))
+ `when`(MultipartFormDataNotEnabledConfig.TOKEN_REPOSITORY.generateToken(any()))
+ .thenReturn(Mono.just(this.token))
+ this.spring.register(MultipartFormDataNotEnabledConfig::class.java).autowire()
+
+ this.client.post()
+ .uri("/")
+ .contentType(MediaType.MULTIPART_FORM_DATA)
+ .body(fromMultipartData(this.token.parameterName, this.token.token))
+ .exchange()
+ .expectStatus().isForbidden
+ }
+
+ @EnableWebFluxSecurity
+ @EnableWebFlux
+ open class MultipartFormDataNotEnabledConfig {
+ companion object {
+ var TOKEN_REPOSITORY: ServerCsrfTokenRepository = mock(ServerCsrfTokenRepository::class.java)
+ }
+
+ @Bean
+ open fun springWebFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain {
+ return http {
+ csrf {
+ csrfTokenRepository = TOKEN_REPOSITORY
+ }
+ }
+ }
+ }
+
+ @Test
+ fun `csrf when multipart form data and enabled then granted`() {
+ `when`(MultipartFormDataEnabledConfig.TOKEN_REPOSITORY.loadToken(any()))
+ .thenReturn(Mono.just(this.token))
+ `when`(MultipartFormDataEnabledConfig.TOKEN_REPOSITORY.generateToken(any()))
+ .thenReturn(Mono.just(this.token))
+ this.spring.register(MultipartFormDataEnabledConfig::class.java).autowire()
+
+ this.client.post()
+ .uri("/")
+ .contentType(MediaType.MULTIPART_FORM_DATA)
+ .body(fromMultipartData(this.token.parameterName, this.token.token))
+ .exchange()
+ .expectStatus().isOk
+ }
+
+ @EnableWebFluxSecurity
+ @EnableWebFlux
+ open class MultipartFormDataEnabledConfig {
+ companion object {
+ var TOKEN_REPOSITORY: ServerCsrfTokenRepository = mock(ServerCsrfTokenRepository::class.java)
+ }
+
+ @Bean
+ open fun springWebFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain {
+ return http {
+ csrf {
+ csrfTokenRepository = TOKEN_REPOSITORY
+ tokenFromMultipartDataEnabled = true
+ }
+ }
+ }
+
+ @RestController
+ internal class TestController {
+ @PostMapping("/")
+ fun home() {
+ }
+ }
+ }
+}
diff --git a/config/src/test/kotlin/org/springframework/security/config/web/server/ServerExceptionHandlingDslTests.kt b/config/src/test/kotlin/org/springframework/security/config/web/server/ServerExceptionHandlingDslTests.kt
new file mode 100644
index 00000000000..22b5de4b44c
--- /dev/null
+++ b/config/src/test/kotlin/org/springframework/security/config/web/server/ServerExceptionHandlingDslTests.kt
@@ -0,0 +1,126 @@
+/*
+ * Copyright 2002-2020 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.server
+
+import org.assertj.core.api.Assertions
+import org.junit.Rule
+import org.junit.Test
+import org.springframework.beans.factory.annotation.Autowired
+import org.springframework.context.ApplicationContext
+import org.springframework.context.annotation.Bean
+import org.springframework.http.HttpStatus
+import org.springframework.security.config.annotation.web.reactive.EnableWebFluxSecurity
+import org.springframework.security.core.userdetails.MapReactiveUserDetailsService
+import org.springframework.security.core.userdetails.User
+import org.springframework.security.config.test.SpringTestRule
+import org.springframework.security.web.server.SecurityWebFilterChain
+import org.springframework.security.web.server.authentication.RedirectServerAuthenticationEntryPoint
+import org.springframework.security.web.server.authorization.HttpStatusServerAccessDeniedHandler
+import org.springframework.test.web.reactive.server.WebTestClient
+import org.springframework.web.reactive.config.EnableWebFlux
+import java.util.*
+
+/**
+ * Tests for [ServerExceptionHandlingDsl]
+ *
+ * @author Eleftheria Stein
+ */
+class ServerExceptionHandlingDslTests {
+ @Rule
+ @JvmField
+ val spring = SpringTestRule()
+
+ private lateinit var client: WebTestClient
+
+ @Autowired
+ fun setup(context: ApplicationContext) {
+ this.client = WebTestClient
+ .bindToApplicationContext(context)
+ .configureClient()
+ .build()
+ }
+
+ @Test
+ fun `unauthenticated request when custom entry point then directed to custom entry point`() {
+ this.spring.register(EntryPointConfig::class.java).autowire()
+
+ val result = this.client.get()
+ .uri("/")
+ .exchange()
+ .expectStatus().is3xxRedirection
+ .returnResult(String::class.java)
+
+ result.assertWithDiagnostics {
+ Assertions.assertThat(result.responseHeaders.location).hasPath("/auth")
+ }
+ }
+
+ @EnableWebFluxSecurity
+ @EnableWebFlux
+ open class EntryPointConfig {
+ @Bean
+ open fun springWebFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain {
+ return http {
+ authorizeExchange {
+ authorize(anyExchange, authenticated)
+ }
+ exceptionHandling {
+ authenticationEntryPoint = RedirectServerAuthenticationEntryPoint("/auth")
+ }
+ }
+ }
+ }
+
+ @Test
+ fun `unauthorized request when custom access denied handler then directed to custom access denied handler`() {
+ this.spring.register(AccessDeniedHandlerConfig::class.java).autowire()
+
+ this.client
+ .get()
+ .uri("/")
+ .header("Authorization", "Basic " + Base64.getEncoder().encodeToString("user:password".toByteArray()))
+ .exchange()
+ .expectStatus().isSeeOther
+ }
+
+ @EnableWebFluxSecurity
+ @EnableWebFlux
+ open class AccessDeniedHandlerConfig {
+ @Bean
+ open fun springWebFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain {
+ return http {
+ authorizeExchange {
+ authorize(anyExchange, hasRole("ADMIN"))
+ }
+ httpBasic { }
+ exceptionHandling {
+ accessDeniedHandler = HttpStatusServerAccessDeniedHandler(HttpStatus.SEE_OTHER)
+ }
+ }
+ }
+
+ @Bean
+ open fun userDetailsService(): MapReactiveUserDetailsService {
+ val user = User.withDefaultPasswordEncoder()
+ .username("user")
+ .password("password")
+ .roles("USER")
+ .build()
+ return MapReactiveUserDetailsService(user)
+ }
+ }
+}
diff --git a/config/src/test/kotlin/org/springframework/security/config/web/server/ServerFormLoginDslTests.kt b/config/src/test/kotlin/org/springframework/security/config/web/server/ServerFormLoginDslTests.kt
new file mode 100644
index 00000000000..23afe0b8653
--- /dev/null
+++ b/config/src/test/kotlin/org/springframework/security/config/web/server/ServerFormLoginDslTests.kt
@@ -0,0 +1,325 @@
+/*
+ * Copyright 2002-2020 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.server
+
+import org.assertj.core.api.Assertions.assertThat
+import org.junit.Rule
+import org.junit.Test
+import org.mockito.ArgumentMatchers.any
+import org.mockito.Mockito
+import org.mockito.Mockito.verify
+import org.springframework.beans.factory.annotation.Autowired
+import org.springframework.context.ApplicationContext
+import org.springframework.context.annotation.Bean
+import org.springframework.context.annotation.Configuration
+import org.springframework.http.HttpMethod
+import org.springframework.security.authentication.ReactiveAuthenticationManager
+import org.springframework.security.config.annotation.web.reactive.EnableWebFluxSecurity
+import org.springframework.security.core.userdetails.MapReactiveUserDetailsService
+import org.springframework.security.core.userdetails.User
+import org.springframework.security.config.test.SpringTestRule
+import org.springframework.security.test.web.reactive.server.SecurityMockServerConfigurers.csrf
+import org.springframework.security.web.server.SecurityWebFilterChain
+import org.springframework.security.web.server.authentication.RedirectServerAuthenticationEntryPoint
+import org.springframework.security.web.server.authentication.RedirectServerAuthenticationFailureHandler
+import org.springframework.security.web.server.authentication.RedirectServerAuthenticationSuccessHandler
+import org.springframework.security.web.server.context.ServerSecurityContextRepository
+import org.springframework.security.web.server.util.matcher.ServerWebExchangeMatchers
+import org.springframework.test.web.reactive.server.FluxExchangeResult
+import org.springframework.test.web.reactive.server.WebTestClient
+import org.springframework.util.LinkedMultiValueMap
+import org.springframework.util.MultiValueMap
+import org.springframework.web.reactive.config.EnableWebFlux
+import org.springframework.web.reactive.function.BodyInserters
+
+/**
+ * Tests for [ServerFormLoginDsl]
+ *
+ * @author Eleftheria Stein
+ */
+class ServerFormLoginDslTests {
+ @Rule
+ @JvmField
+ val spring = SpringTestRule()
+
+ private lateinit var client: WebTestClient
+
+ @Autowired
+ fun setup(context: ApplicationContext) {
+ this.client = WebTestClient
+ .bindToApplicationContext(context)
+ .configureClient()
+ .build()
+ }
+
+ @Test
+ fun `request when form login enabled then redirects to default login page`() {
+ this.spring.register(DefaultFormLoginConfig::class.java, UserDetailsConfig::class.java).autowire()
+
+ val result: FluxExchangeResult = this.client.get()
+ .uri("/")
+ .exchange()
+ .expectStatus().is3xxRedirection
+ .returnResult(String::class.java)
+
+ result.assertWithDiagnostics {
+ assertThat(result.responseHeaders.location).hasPath("/login")
+ }
+ }
+
+ @EnableWebFluxSecurity
+ @EnableWebFlux
+ open class DefaultFormLoginConfig {
+ @Bean
+ open fun springWebFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain {
+ return http {
+ authorizeExchange {
+ authorize(anyExchange, authenticated)
+ }
+ formLogin { }
+ }
+ }
+ }
+
+ @Test
+ fun `request when custom login page then redirects to custom login page`() {
+ this.spring.register(CustomLoginPageConfig::class.java, UserDetailsConfig::class.java).autowire()
+
+ val result: FluxExchangeResult = this.client.get()
+ .uri("/")
+ .exchange()
+ .expectStatus().is3xxRedirection
+ .returnResult(String::class.java)
+
+ result.assertWithDiagnostics {
+ assertThat(result.responseHeaders.location).hasPath("/log-in")
+ }
+ }
+
+ @EnableWebFluxSecurity
+ @EnableWebFlux
+ open class CustomLoginPageConfig {
+ @Bean
+ open fun springWebFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain {
+ return http {
+ authorizeExchange {
+ authorize(anyExchange, authenticated)
+ }
+ formLogin {
+ loginPage = "/log-in"
+ }
+ }
+ }
+ }
+
+ @Test
+ fun `form login when custom authentication manager then manager used`() {
+ this.spring.register(CustomAuthenticationManagerConfig::class.java).autowire()
+ val data: MultiValueMap = LinkedMultiValueMap()
+ data.add("username", "user")
+ data.add("password", "password")
+
+ this.client
+ .mutateWith(csrf())
+ .post()
+ .uri("/login")
+ .body(BodyInserters.fromFormData(data))
+ .exchange()
+
+ verify(CustomAuthenticationManagerConfig.AUTHENTICATION_MANAGER)
+ .authenticate(any())
+ }
+
+ @EnableWebFluxSecurity
+ @EnableWebFlux
+ open class CustomAuthenticationManagerConfig {
+ companion object {
+ var AUTHENTICATION_MANAGER: ReactiveAuthenticationManager = Mockito.mock(ReactiveAuthenticationManager::class.java)
+ }
+
+ @Bean
+ open fun springWebFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain {
+ return http {
+ authorizeExchange {
+ authorize(anyExchange, authenticated)
+ }
+ formLogin {
+ authenticationManager = AUTHENTICATION_MANAGER
+ }
+ }
+ }
+ }
+
+ @Test
+ fun `form login when custom authentication entry point then entry point used`() {
+ this.spring.register(CustomConfig::class.java, UserDetailsConfig::class.java).autowire()
+
+ val result = this.client.get()
+ .uri("/")
+ .exchange()
+ .expectStatus().is3xxRedirection
+ .returnResult(String::class.java)
+
+ result.assertWithDiagnostics {
+ assertThat(result.responseHeaders.location).hasPath("/entry")
+ }
+ }
+
+ @Test
+ fun `form login when custom requires authentication matcher then matching request logs in`() {
+ this.spring.register(CustomConfig::class.java, UserDetailsConfig::class.java).autowire()
+ val data: MultiValueMap = LinkedMultiValueMap()
+ data.add("username", "user")
+ data.add("password", "password")
+
+ val result = this.client
+ .mutateWith(csrf())
+ .post()
+ .uri("/log-in")
+ .body(BodyInserters.fromFormData(data))
+ .exchange()
+ .expectStatus().is3xxRedirection
+ .returnResult(String::class.java)
+
+ result.assertWithDiagnostics {
+ assertThat(result.responseHeaders.location).hasPath("/")
+ }
+ }
+
+ @Test
+ fun `invalid login when custom failure handler then failure handler used`() {
+ this.spring.register(CustomConfig::class.java, UserDetailsConfig::class.java).autowire()
+
+ val result = this.client
+ .mutateWith(csrf())
+ .post()
+ .uri("/log-in")
+ .exchange()
+ .expectStatus().is3xxRedirection
+ .returnResult(String::class.java)
+
+ result.assertWithDiagnostics {
+ assertThat(result.responseHeaders.location).hasPath("/log-in-error")
+ }
+ }
+
+ @EnableWebFluxSecurity
+ @EnableWebFlux
+ open class CustomConfig {
+ @Bean
+ open fun springWebFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain {
+ return http {
+ authorizeExchange {
+ authorize(anyExchange, authenticated)
+ }
+ formLogin {
+ authenticationEntryPoint = RedirectServerAuthenticationEntryPoint("/entry")
+ requiresAuthenticationMatcher = ServerWebExchangeMatchers.pathMatchers(HttpMethod.POST, "/log-in")
+ authenticationFailureHandler = RedirectServerAuthenticationFailureHandler("/log-in-error")
+ }
+ }
+ }
+ }
+
+ @Test
+ fun `login when custom success handler then success handler used`() {
+ this.spring.register(CustomSuccessHandlerConfig::class.java, UserDetailsConfig::class.java).autowire()
+ val data: MultiValueMap = LinkedMultiValueMap()
+ data.add("username", "user")
+ data.add("password", "password")
+
+ val result = this.client
+ .mutateWith(csrf())
+ .post()
+ .uri("/login")
+ .body(BodyInserters.fromFormData(data))
+ .exchange()
+ .expectStatus().is3xxRedirection
+ .returnResult(String::class.java)
+
+ result.assertWithDiagnostics {
+ assertThat(result.responseHeaders.location).hasPath("/success")
+ }
+ }
+
+ @EnableWebFluxSecurity
+ @EnableWebFlux
+ open class CustomSuccessHandlerConfig {
+ @Bean
+ open fun springWebFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain {
+ return http {
+ authorizeExchange {
+ authorize(anyExchange, authenticated)
+ }
+ formLogin {
+ authenticationSuccessHandler = RedirectServerAuthenticationSuccessHandler("/success")
+ }
+ }
+ }
+ }
+
+ @Test
+ fun `form login when custom security context repository then repository used`() {
+ this.spring.register(CustomSecurityContextRepositoryConfig::class.java, UserDetailsConfig::class.java).autowire()
+ val data: MultiValueMap = LinkedMultiValueMap()
+ data.add("username", "user")
+ data.add("password", "password")
+
+ this.client
+ .mutateWith(csrf())
+ .post()
+ .uri("/login")
+ .body(BodyInserters.fromFormData(data))
+ .exchange()
+
+ verify(CustomSecurityContextRepositoryConfig.SECURITY_CONTEXT_REPOSITORY)
+ .save(Mockito.any(), Mockito.any())
+ }
+
+ @EnableWebFluxSecurity
+ @EnableWebFlux
+ open class CustomSecurityContextRepositoryConfig {
+ companion object {
+ var SECURITY_CONTEXT_REPOSITORY: ServerSecurityContextRepository = Mockito.mock(ServerSecurityContextRepository::class.java)
+ }
+
+ @Bean
+ open fun springWebFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain {
+ return http {
+ authorizeExchange {
+ authorize(anyExchange, authenticated)
+ }
+ formLogin {
+ securityContextRepository = SECURITY_CONTEXT_REPOSITORY
+ }
+ }
+ }
+ }
+
+ @Configuration
+ open class UserDetailsConfig {
+ @Bean
+ open fun userDetailsService(): MapReactiveUserDetailsService {
+ val user = User.withDefaultPasswordEncoder()
+ .username("user")
+ .password("password")
+ .roles("USER")
+ .build()
+ return MapReactiveUserDetailsService(user)
+ }
+ }
+}
diff --git a/config/src/test/kotlin/org/springframework/security/config/web/server/ServerFrameOptionsDslTests.kt b/config/src/test/kotlin/org/springframework/security/config/web/server/ServerFrameOptionsDslTests.kt
new file mode 100644
index 00000000000..2a95dea8d8c
--- /dev/null
+++ b/config/src/test/kotlin/org/springframework/security/config/web/server/ServerFrameOptionsDslTests.kt
@@ -0,0 +1,124 @@
+/*
+ * Copyright 2002-2020 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.server
+
+import org.junit.Rule
+import org.junit.Test
+import org.springframework.beans.factory.annotation.Autowired
+import org.springframework.context.ApplicationContext
+import org.springframework.context.annotation.Bean
+import org.springframework.security.config.annotation.web.reactive.EnableWebFluxSecurity
+import org.springframework.security.config.test.SpringTestRule
+import org.springframework.security.web.header.writers.frameoptions.XFrameOptionsHeaderWriter
+import org.springframework.security.web.server.SecurityWebFilterChain
+import org.springframework.security.web.server.header.XFrameOptionsServerHttpHeadersWriter
+import org.springframework.test.web.reactive.server.WebTestClient
+import org.springframework.web.reactive.config.EnableWebFlux
+
+/**
+ * Tests for [ServerFrameOptionsDsl]
+ *
+ * @author Eleftheria Stein
+ */
+class ServerFrameOptionsDslTests {
+ @Rule
+ @JvmField
+ val spring = SpringTestRule()
+
+ private lateinit var client: WebTestClient
+
+ @Autowired
+ fun setup(context: ApplicationContext) {
+ this.client = WebTestClient
+ .bindToApplicationContext(context)
+ .configureClient()
+ .build()
+ }
+
+ @Test
+ fun `request when frame options configured then header in response`() {
+ this.spring.register(FrameOptionsConfig::class.java).autowire()
+
+ this.client.get()
+ .uri("/")
+ .exchange()
+ .expectHeader().valueEquals(XFrameOptionsServerHttpHeadersWriter.X_FRAME_OPTIONS, XFrameOptionsHeaderWriter.XFrameOptionsMode.DENY.name)
+ }
+
+ @EnableWebFluxSecurity
+ @EnableWebFlux
+ open class FrameOptionsConfig {
+ @Bean
+ open fun springWebFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain {
+ return http {
+ headers {
+ frameOptions { }
+ }
+ }
+ }
+ }
+
+ @Test
+ fun `request when frame options disabled then no frame options header in response`() {
+ this.spring.register(FrameOptionsDisabledConfig::class.java).autowire()
+
+ this.client.get()
+ .uri("/")
+ .exchange()
+ .expectHeader().doesNotExist(XFrameOptionsServerHttpHeadersWriter.X_FRAME_OPTIONS)
+ }
+
+ @EnableWebFluxSecurity
+ @EnableWebFlux
+ open class FrameOptionsDisabledConfig {
+ @Bean
+ open fun springWebFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain {
+ return http {
+ headers {
+ frameOptions {
+ disable()
+ }
+ }
+ }
+ }
+ }
+
+ @Test
+ fun `request when frame options mode set then frame options response header has mode value`() {
+ this.spring.register(CustomModeConfig::class.java).autowire()
+
+ this.client.get()
+ .uri("/")
+ .exchange()
+ .expectHeader().valueEquals(XFrameOptionsServerHttpHeadersWriter.X_FRAME_OPTIONS, XFrameOptionsHeaderWriter.XFrameOptionsMode.SAMEORIGIN.name)
+ }
+
+ @EnableWebFluxSecurity
+ @EnableWebFlux
+ open class CustomModeConfig {
+ @Bean
+ open fun springWebFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain {
+ return http {
+ headers {
+ frameOptions {
+ mode = XFrameOptionsServerHttpHeadersWriter.Mode.SAMEORIGIN
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/config/src/test/kotlin/org/springframework/security/config/web/server/ServerHeadersDslTests.kt b/config/src/test/kotlin/org/springframework/security/config/web/server/ServerHeadersDslTests.kt
new file mode 100644
index 00000000000..6cebc2cbc3f
--- /dev/null
+++ b/config/src/test/kotlin/org/springframework/security/config/web/server/ServerHeadersDslTests.kt
@@ -0,0 +1,134 @@
+/*
+ * Copyright 2002-2020 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.server
+
+import org.junit.Rule
+import org.junit.Test
+import org.springframework.beans.factory.annotation.Autowired
+import org.springframework.context.ApplicationContext
+import org.springframework.context.annotation.Bean
+import org.springframework.http.HttpHeaders
+import org.springframework.security.config.annotation.web.reactive.EnableWebFluxSecurity
+import org.springframework.security.config.test.SpringTestRule
+import org.springframework.security.web.header.writers.frameoptions.XFrameOptionsHeaderWriter
+import org.springframework.security.web.server.SecurityWebFilterChain
+import org.springframework.security.web.server.header.ContentTypeOptionsServerHttpHeadersWriter
+import org.springframework.security.web.server.header.StrictTransportSecurityServerHttpHeadersWriter
+import org.springframework.security.web.server.header.XFrameOptionsServerHttpHeadersWriter
+import org.springframework.security.web.server.header.XXssProtectionServerHttpHeadersWriter
+import org.springframework.test.web.reactive.server.WebTestClient
+import org.springframework.web.reactive.config.EnableWebFlux
+
+/**
+ * Tests for [ServerHeadersDsl]
+ *
+ * @author Eleftheria Stein
+ */
+class ServerHeadersDslTests {
+ @Rule
+ @JvmField
+ val spring = SpringTestRule()
+
+ private lateinit var client: WebTestClient
+
+ @Autowired
+ fun setup(context: ApplicationContext) {
+ this.client = WebTestClient
+ .bindToApplicationContext(context)
+ .configureClient()
+ .build()
+ }
+
+ @Test
+ fun `request when default headers configured then default headers are in the response`() {
+ this.spring.register(DefaultHeadersConfig::class.java).autowire()
+
+ this.client.get()
+ .uri("https://example.com")
+ .exchange()
+ .expectHeader().valueEquals(ContentTypeOptionsServerHttpHeadersWriter.X_CONTENT_OPTIONS, "nosniff")
+ .expectHeader().valueEquals(XFrameOptionsServerHttpHeadersWriter.X_FRAME_OPTIONS, XFrameOptionsHeaderWriter.XFrameOptionsMode.DENY.name)
+ .expectHeader().valueEquals(StrictTransportSecurityServerHttpHeadersWriter.STRICT_TRANSPORT_SECURITY, "max-age=31536000 ; includeSubDomains")
+ .expectHeader().valueEquals(HttpHeaders.CACHE_CONTROL, "no-cache, no-store, max-age=0, must-revalidate")
+ .expectHeader().valueEquals(HttpHeaders.EXPIRES, "0")
+ .expectHeader().valueEquals(HttpHeaders.PRAGMA, "no-cache")
+ .expectHeader().valueEquals(XXssProtectionServerHttpHeadersWriter.X_XSS_PROTECTION, "1 ; mode=block")
+ }
+
+ @EnableWebFluxSecurity
+ @EnableWebFlux
+ open class DefaultHeadersConfig {
+ @Bean
+ open fun springWebFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain {
+ return http {
+ headers { }
+ }
+ }
+ }
+
+ @Test
+ fun `request when headers disabled then no security headers are in the response`() {
+ this.spring.register(HeadersDisabledConfig::class.java).autowire()
+
+ this.client.get()
+ .uri("https://example.com")
+ .exchange()
+ .expectHeader().doesNotExist(ContentTypeOptionsServerHttpHeadersWriter.X_CONTENT_OPTIONS)
+ .expectHeader().doesNotExist(XFrameOptionsServerHttpHeadersWriter.X_FRAME_OPTIONS)
+ .expectHeader().doesNotExist(StrictTransportSecurityServerHttpHeadersWriter.STRICT_TRANSPORT_SECURITY)
+ .expectHeader().doesNotExist(HttpHeaders.CACHE_CONTROL)
+ .expectHeader().doesNotExist(HttpHeaders.EXPIRES)
+ .expectHeader().doesNotExist(HttpHeaders.PRAGMA)
+ .expectHeader().doesNotExist(XXssProtectionServerHttpHeadersWriter.X_XSS_PROTECTION)
+ }
+
+ @EnableWebFluxSecurity
+ @EnableWebFlux
+ open class HeadersDisabledConfig {
+ @Bean
+ open fun springWebFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain {
+ return http {
+ headers {
+ disable()
+ }
+ }
+ }
+ }
+
+ @Test
+ fun `request when feature policy configured then feature policy header in response`() {
+ this.spring.register(FeaturePolicyConfig::class.java).autowire()
+
+ this.client.get()
+ .uri("/")
+ .exchange()
+ .expectHeader().valueEquals("Feature-Policy", "geolocation 'self'")
+ }
+
+ @EnableWebFluxSecurity
+ @EnableWebFlux
+ open class FeaturePolicyConfig {
+ @Bean
+ open fun springWebFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain {
+ return http {
+ headers {
+ featurePolicy("geolocation 'self'")
+ }
+ }
+ }
+ }
+}
diff --git a/config/src/test/kotlin/org/springframework/security/config/web/server/ServerHttpBasicDslTests.kt b/config/src/test/kotlin/org/springframework/security/config/web/server/ServerHttpBasicDslTests.kt
new file mode 100644
index 00000000000..4d4c926d17b
--- /dev/null
+++ b/config/src/test/kotlin/org/springframework/security/config/web/server/ServerHttpBasicDslTests.kt
@@ -0,0 +1,219 @@
+/*
+ * Copyright 2002-2020 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.server
+
+import org.junit.Rule
+import org.junit.Test
+import org.mockito.BDDMockito.given
+import org.mockito.Mockito.*
+import org.springframework.beans.factory.annotation.Autowired
+import org.springframework.context.ApplicationContext
+import org.springframework.context.annotation.Bean
+import org.springframework.context.annotation.Configuration
+import org.springframework.security.authentication.ReactiveAuthenticationManager
+import org.springframework.security.authentication.TestingAuthenticationToken
+import org.springframework.security.config.annotation.web.reactive.EnableWebFluxSecurity
+import org.springframework.security.core.Authentication
+import org.springframework.security.core.userdetails.MapReactiveUserDetailsService
+import org.springframework.security.core.userdetails.User
+import org.springframework.security.config.test.SpringTestRule
+import org.springframework.security.web.server.SecurityWebFilterChain
+import org.springframework.security.web.server.ServerAuthenticationEntryPoint
+import org.springframework.security.web.server.context.ServerSecurityContextRepository
+import org.springframework.test.web.reactive.server.WebTestClient
+import org.springframework.web.bind.annotation.RequestMapping
+import org.springframework.web.bind.annotation.RestController
+import org.springframework.web.reactive.config.EnableWebFlux
+import reactor.core.publisher.Mono
+import java.util.*
+
+/**
+ * Tests for [ServerHttpBasicDsl]
+ *
+ * @author Eleftheria Stein
+ */
+class ServerHttpBasicDslTests {
+ @Rule
+ @JvmField
+ val spring = SpringTestRule()
+
+ private lateinit var client: WebTestClient
+
+ @Autowired
+ fun setup(context: ApplicationContext) {
+ this.client = WebTestClient
+ .bindToApplicationContext(context)
+ .configureClient()
+ .build()
+ }
+
+ @Test
+ fun `http basic when no authorization header then responds with unauthorized`() {
+ this.spring.register(HttpBasicConfig::class.java, UserDetailsConfig::class.java).autowire()
+
+ this.client.get()
+ .uri("/")
+ .exchange()
+ .expectStatus().isUnauthorized
+ }
+
+ @Test
+ fun `http basic when valid authorization header then responds with ok`() {
+ this.spring.register(HttpBasicConfig::class.java, UserDetailsConfig::class.java).autowire()
+
+ this.client.get()
+ .uri("/")
+ .header("Authorization", "Basic " + Base64.getEncoder().encodeToString("user:password".toByteArray()))
+ .exchange()
+ .expectStatus().isOk
+ }
+
+ @EnableWebFluxSecurity
+ @EnableWebFlux
+ open class HttpBasicConfig {
+ @Bean
+ open fun springWebFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain {
+ return http {
+ authorizeExchange {
+ authorize(anyExchange, authenticated)
+ }
+ httpBasic { }
+ }
+ }
+
+ @RestController
+ internal class PathController {
+ @RequestMapping("/")
+ fun path() {
+ }
+ }
+ }
+
+ @Test
+ fun `http basic when custom authentication manager then manager used`() {
+ given>(CustomAuthenticationManagerConfig.AUTHENTICATION_MANAGER.authenticate(any()))
+ .willReturn(Mono.just(TestingAuthenticationToken("user", "password", "ROLE_USER")))
+
+ this.spring.register(CustomAuthenticationManagerConfig::class.java).autowire()
+
+ this.client.get()
+ .uri("/")
+ .header("Authorization", "Basic " + Base64.getEncoder().encodeToString("user:password".toByteArray()))
+ .exchange()
+
+ verify(CustomAuthenticationManagerConfig.AUTHENTICATION_MANAGER)
+ .authenticate(any())
+ }
+
+ @EnableWebFluxSecurity
+ @EnableWebFlux
+ open class CustomAuthenticationManagerConfig {
+ companion object {
+ var AUTHENTICATION_MANAGER: ReactiveAuthenticationManager = mock(ReactiveAuthenticationManager::class.java)
+ }
+
+ @Bean
+ open fun springWebFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain {
+ return http {
+ authorizeExchange {
+ authorize(anyExchange, authenticated)
+ }
+ httpBasic {
+ authenticationManager = AUTHENTICATION_MANAGER
+ }
+ }
+ }
+ }
+
+ @Test
+ fun `http basic when custom security context repository then repository used`() {
+ this.spring.register(CustomSecurityContextRepositoryConfig::class.java, UserDetailsConfig::class.java).autowire()
+
+ this.client.get()
+ .uri("/")
+ .header("Authorization", "Basic " + Base64.getEncoder().encodeToString("user:password".toByteArray()))
+ .exchange()
+
+ verify(CustomSecurityContextRepositoryConfig.SECURITY_CONTEXT_REPOSITORY)
+ .save(any(), any())
+ }
+
+ @EnableWebFluxSecurity
+ @EnableWebFlux
+ open class CustomSecurityContextRepositoryConfig {
+ companion object {
+ var SECURITY_CONTEXT_REPOSITORY: ServerSecurityContextRepository = mock(ServerSecurityContextRepository::class.java)
+ }
+
+ @Bean
+ open fun springWebFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain {
+ return http {
+ authorizeExchange {
+ authorize(anyExchange, authenticated)
+ }
+ httpBasic {
+ securityContextRepository = SECURITY_CONTEXT_REPOSITORY
+ }
+ }
+ }
+ }
+
+ @Test
+ fun `http basic when custom authentication entry point then entry point used`() {
+ this.spring.register(CustomAuthenticationEntryPointConfig::class.java, UserDetailsConfig::class.java).autowire()
+
+ this.client.get()
+ .uri("/")
+ .exchange()
+
+ verify(CustomAuthenticationEntryPointConfig.ENTRY_POINT)
+ .commence(any(), any())
+ }
+
+ @EnableWebFluxSecurity
+ @EnableWebFlux
+ open class CustomAuthenticationEntryPointConfig {
+ companion object {
+ var ENTRY_POINT: ServerAuthenticationEntryPoint = mock(ServerAuthenticationEntryPoint::class.java)
+ }
+
+ @Bean
+ open fun springWebFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain {
+ return http {
+ authorizeExchange {
+ authorize(anyExchange, authenticated)
+ }
+ httpBasic {
+ authenticationEntryPoint = ENTRY_POINT
+ }
+ }
+ }
+ }
+
+ @Configuration
+ open class UserDetailsConfig {
+ @Bean
+ open fun userDetailsService(): MapReactiveUserDetailsService {
+ val user = User.withDefaultPasswordEncoder()
+ .username("user")
+ .password("password")
+ .roles("USER")
+ .build()
+ return MapReactiveUserDetailsService(user)
+ }
+ }
+}
diff --git a/config/src/test/kotlin/org/springframework/security/config/web/server/ServerHttpSecurityDslTests.kt b/config/src/test/kotlin/org/springframework/security/config/web/server/ServerHttpSecurityDslTests.kt
new file mode 100644
index 00000000000..0b6135741f0
--- /dev/null
+++ b/config/src/test/kotlin/org/springframework/security/config/web/server/ServerHttpSecurityDslTests.kt
@@ -0,0 +1,126 @@
+/*
+ * Copyright 2002-2020 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.server
+
+import org.junit.Rule
+import org.junit.Test
+import org.springframework.beans.factory.annotation.Autowired
+import org.springframework.context.ApplicationContext
+import org.springframework.context.annotation.Bean
+import org.springframework.http.HttpHeaders
+import org.springframework.security.config.annotation.web.reactive.EnableWebFluxSecurity
+import org.springframework.security.config.test.SpringTestRule
+import org.springframework.security.web.header.writers.frameoptions.XFrameOptionsHeaderWriter
+import org.springframework.security.web.server.SecurityWebFilterChain
+import org.springframework.security.web.server.header.ContentTypeOptionsServerHttpHeadersWriter
+import org.springframework.security.web.server.header.StrictTransportSecurityServerHttpHeadersWriter
+import org.springframework.security.web.server.header.XFrameOptionsServerHttpHeadersWriter
+import org.springframework.security.web.server.header.XXssProtectionServerHttpHeadersWriter
+import org.springframework.security.web.server.util.matcher.PathPatternParserServerWebExchangeMatcher
+import org.springframework.test.web.reactive.server.WebTestClient
+import org.springframework.web.reactive.config.EnableWebFlux
+
+/**
+ * Tests for [ServerHttpSecurityDsl]
+ *
+ * @author Eleftheria Stein
+ */
+class ServerHttpSecurityDslTests {
+ @Rule
+ @JvmField
+ val spring = SpringTestRule()
+
+ private lateinit var client: WebTestClient
+
+ @Autowired
+ fun setup(context: ApplicationContext) {
+ this.client = WebTestClient
+ .bindToApplicationContext(context)
+ .configureClient()
+ .build()
+ }
+
+ @Test
+ fun `request when it does not match the security matcher then the security rules do not apply`() {
+ this.spring.register(PatternMatcherConfig::class.java).autowire()
+
+ this.client.get()
+ .uri("/")
+ .exchange()
+ .expectStatus().isNotFound
+ }
+
+ @Test
+ fun `request when it matches the security matcher then the security rules apply`() {
+ this.spring.register(PatternMatcherConfig::class.java).autowire()
+
+ this.client.get()
+ .uri("/api")
+ .exchange()
+ .expectStatus().isUnauthorized
+ }
+
+ @EnableWebFluxSecurity
+ @EnableWebFlux
+ open class PatternMatcherConfig {
+ @Bean
+ open fun springWebFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain {
+ return http {
+ securityMatcher(PathPatternParserServerWebExchangeMatcher("/api/**"))
+ authorizeExchange {
+ authorize(anyExchange, authenticated)
+ }
+ }
+ }
+ }
+
+ @Test
+ fun `post when default security configured then CSRF prevents the request`() {
+ this.spring.register(DefaultSecurityConfig::class.java).autowire()
+
+ this.client.post()
+ .uri("/")
+ .exchange()
+ .expectStatus().isForbidden
+ }
+
+ @Test
+ fun `request when default security configured then default headers are in the response`() {
+ this.spring.register(DefaultSecurityConfig::class.java).autowire()
+
+ this.client.get()
+ .uri("https://example.com")
+ .exchange()
+ .expectHeader().valueEquals(ContentTypeOptionsServerHttpHeadersWriter.X_CONTENT_OPTIONS, "nosniff")
+ .expectHeader().valueEquals(XFrameOptionsServerHttpHeadersWriter.X_FRAME_OPTIONS, XFrameOptionsHeaderWriter.XFrameOptionsMode.DENY.name)
+ .expectHeader().valueEquals(StrictTransportSecurityServerHttpHeadersWriter.STRICT_TRANSPORT_SECURITY, "max-age=31536000 ; includeSubDomains")
+ .expectHeader().valueEquals(HttpHeaders.CACHE_CONTROL, "no-cache, no-store, max-age=0, must-revalidate")
+ .expectHeader().valueEquals(HttpHeaders.EXPIRES, "0")
+ .expectHeader().valueEquals(HttpHeaders.PRAGMA, "no-cache")
+ .expectHeader().valueEquals(XXssProtectionServerHttpHeadersWriter.X_XSS_PROTECTION, "1 ; mode=block")
+ }
+
+ @EnableWebFluxSecurity
+ @EnableWebFlux
+ open class DefaultSecurityConfig {
+ @Bean
+ open fun springWebFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain {
+ return http {
+ }
+ }
+ }
+}
diff --git a/config/src/test/kotlin/org/springframework/security/config/web/server/ServerHttpStrictTransportSecurityDslTests.kt b/config/src/test/kotlin/org/springframework/security/config/web/server/ServerHttpStrictTransportSecurityDslTests.kt
new file mode 100644
index 00000000000..ccc62592939
--- /dev/null
+++ b/config/src/test/kotlin/org/springframework/security/config/web/server/ServerHttpStrictTransportSecurityDslTests.kt
@@ -0,0 +1,176 @@
+/*
+ * Copyright 2002-2020 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.server.headers
+
+import org.junit.Rule
+import org.junit.Test
+import org.springframework.beans.factory.annotation.Autowired
+import org.springframework.context.ApplicationContext
+import org.springframework.context.annotation.Bean
+import org.springframework.security.config.annotation.web.reactive.EnableWebFluxSecurity
+import org.springframework.security.config.web.server.invoke
+import org.springframework.security.config.test.SpringTestRule
+import org.springframework.security.config.web.server.ServerHttpSecurity
+import org.springframework.security.web.server.SecurityWebFilterChain
+import org.springframework.security.web.server.header.StrictTransportSecurityServerHttpHeadersWriter
+import org.springframework.test.web.reactive.server.WebTestClient
+import org.springframework.web.reactive.config.EnableWebFlux
+import java.time.Duration
+
+/**
+ * Tests for [ServerReferrerPolicyDsl]
+ *
+ * @author Eleftheria Stein
+ */
+class ServerHttpStrictTransportSecurityDslTests {
+ @Rule
+ @JvmField
+ val spring = SpringTestRule()
+
+ private lateinit var client: WebTestClient
+
+ @Autowired
+ fun setup(context: ApplicationContext) {
+ this.client = WebTestClient
+ .bindToApplicationContext(context)
+ .configureClient()
+ .build()
+ }
+
+ @Test
+ fun `request when hsts configured then hsts header in response`() {
+ this.spring.register(HstsConfig::class.java).autowire()
+
+ this.client.get()
+ .uri("https://example.com")
+ .exchange()
+ .expectHeader().valueEquals(StrictTransportSecurityServerHttpHeadersWriter.STRICT_TRANSPORT_SECURITY, "max-age=31536000 ; includeSubDomains")
+ }
+
+ @EnableWebFluxSecurity
+ @EnableWebFlux
+ open class HstsConfig {
+ @Bean
+ open fun springWebFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain {
+ return http {
+ headers {
+ hsts { }
+ }
+ }
+ }
+ }
+
+ @Test
+ fun `request when hsts disabled then no hsts header in response`() {
+ this.spring.register(HstsDisabledConfig::class.java).autowire()
+
+ this.client.get()
+ .uri("https://example.com")
+ .exchange()
+ .expectHeader().doesNotExist(StrictTransportSecurityServerHttpHeadersWriter.STRICT_TRANSPORT_SECURITY)
+ }
+
+ @EnableWebFluxSecurity
+ @EnableWebFlux
+ open class HstsDisabledConfig {
+ @Bean
+ open fun springWebFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain {
+ return http {
+ headers {
+ hsts {
+ disable()
+ }
+ }
+ }
+ }
+ }
+
+ @Test
+ fun `request when max age set then max age in response header`() {
+ this.spring.register(MaxAgeConfig::class.java).autowire()
+
+ this.client.get()
+ .uri("https://example.com")
+ .exchange()
+ .expectHeader().valueEquals(StrictTransportSecurityServerHttpHeadersWriter.STRICT_TRANSPORT_SECURITY, "max-age=1 ; includeSubDomains")
+ }
+
+ @EnableWebFluxSecurity
+ @EnableWebFlux
+ open class MaxAgeConfig {
+ @Bean
+ open fun springWebFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain {
+ return http {
+ headers {
+ hsts {
+ maxAge = Duration.ofSeconds(1)
+ }
+ }
+ }
+ }
+ }
+
+ @Test
+ fun `request when includeSubdomains false then includeSubdomains not in response header`() {
+ this.spring.register(IncludeSubdomainsConfig::class.java).autowire()
+
+ this.client.get()
+ .uri("https://example.com")
+ .exchange()
+ .expectHeader().valueEquals(StrictTransportSecurityServerHttpHeadersWriter.STRICT_TRANSPORT_SECURITY, "max-age=31536000")
+ }
+
+ @EnableWebFluxSecurity
+ @EnableWebFlux
+ open class IncludeSubdomainsConfig {
+ @Bean
+ open fun springWebFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain {
+ return http {
+ headers {
+ hsts {
+ includeSubdomains = false
+ }
+ }
+ }
+ }
+ }
+
+ @Test
+ fun `request when preload true then preload included in response header`() {
+ this.spring.register(PreloadConfig::class.java).autowire()
+
+ this.client.get()
+ .uri("https://example.com")
+ .exchange()
+ .expectHeader().valueEquals(StrictTransportSecurityServerHttpHeadersWriter.STRICT_TRANSPORT_SECURITY, "max-age=31536000 ; includeSubDomains ; preload")
+ }
+
+ @EnableWebFluxSecurity
+ @EnableWebFlux
+ open class PreloadConfig {
+ @Bean
+ open fun springWebFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain {
+ return http {
+ headers {
+ hsts {
+ preload = true
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/config/src/test/kotlin/org/springframework/security/config/web/server/ServerHttpsRedirectDslTests.kt b/config/src/test/kotlin/org/springframework/security/config/web/server/ServerHttpsRedirectDslTests.kt
new file mode 100644
index 00000000000..9de8fc77d42
--- /dev/null
+++ b/config/src/test/kotlin/org/springframework/security/config/web/server/ServerHttpsRedirectDslTests.kt
@@ -0,0 +1,199 @@
+/*
+ * Copyright 2002-2020 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.server
+
+import org.assertj.core.api.Assertions.assertThat
+import org.junit.Rule
+import org.junit.Test
+import org.springframework.beans.factory.annotation.Autowired
+import org.springframework.context.ApplicationContext
+import org.springframework.context.annotation.Bean
+import org.springframework.security.config.annotation.web.reactive.EnableWebFluxSecurity
+import org.springframework.security.config.test.SpringTestRule
+import org.springframework.security.web.PortMapperImpl
+import org.springframework.security.web.server.SecurityWebFilterChain
+import org.springframework.security.web.server.util.matcher.PathPatternParserServerWebExchangeMatcher
+import org.springframework.test.web.reactive.server.WebTestClient
+import org.springframework.web.reactive.config.EnableWebFlux
+import java.util.*
+
+/**
+ * Tests for [ServerHttpsRedirectDsl]
+ *
+ * @author Eleftheria Stein
+ */
+class ServerHttpsRedirectDslTests {
+ @Rule
+ @JvmField
+ val spring = SpringTestRule()
+
+ private lateinit var client: WebTestClient
+
+ @Autowired
+ fun setup(context: ApplicationContext) {
+ this.client = WebTestClient
+ .bindToApplicationContext(context)
+ .configureClient()
+ .build()
+ }
+
+ @Test
+ fun `request when matches redirect to HTTPS matcher then redirects to HTTPS`() {
+ this.spring.register(HttpRedirectMatcherConfig::class.java).autowire()
+
+ val result = this.client.get()
+ .uri("/secure")
+ .exchange()
+ .expectStatus().is3xxRedirection
+ .returnResult(String::class.java)
+
+ result.assertWithDiagnostics {
+ assertThat(result.responseHeaders.location).hasScheme("https")
+ }
+ }
+
+ @Test
+ fun `request when does not match redirect to HTTPS matcher then does not redirect`() {
+ this.spring.register(HttpRedirectMatcherConfig::class.java).autowire()
+
+ this.client.get()
+ .uri("/")
+ .exchange()
+ .expectStatus().isNotFound
+ }
+
+ @EnableWebFluxSecurity
+ @EnableWebFlux
+ open class HttpRedirectMatcherConfig {
+ @Bean
+ open fun springWebFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain {
+ return http {
+ redirectToHttps {
+ httpsRedirectWhen(PathPatternParserServerWebExchangeMatcher("/secure"))
+ }
+ }
+ }
+ }
+
+ @Test
+ fun `request when matches redirect to HTTPS function then redirects to HTTPS`() {
+ this.spring.register(HttpRedirectFunctionConfig::class.java).autowire()
+
+ val result = this.client.get()
+ .uri("/")
+ .header("X-Requires-Https", "required")
+ .exchange()
+ .expectStatus().is3xxRedirection
+ .returnResult(String::class.java)
+
+ result.assertWithDiagnostics {
+ assertThat(result.responseHeaders.location).hasScheme("https")
+ }
+ }
+
+ @Test
+ fun `request when does not match redirect to HTTPS function then does not redirect`() {
+ this.spring.register(HttpRedirectFunctionConfig::class.java).autowire()
+
+ this.client.get()
+ .uri("/")
+ .exchange()
+ .expectStatus().isNotFound
+ }
+
+ @EnableWebFluxSecurity
+ @EnableWebFlux
+ open class HttpRedirectFunctionConfig {
+ @Bean
+ open fun springWebFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain {
+ return http {
+ redirectToHttps {
+ httpsRedirectWhen {
+ it.request.headers.containsKey("X-Requires-Https")
+ }
+ }
+ }
+ }
+ }
+
+ @Test
+ fun `request when multiple rules configured then only the last rule applies`() {
+ this.spring.register(HttpRedirectMatcherAndFunctionConfig::class.java).autowire()
+
+ this.client.get()
+ .uri("/secure")
+ .exchange()
+ .expectStatus().isNotFound
+
+ val result = this.client.get()
+ .uri("/")
+ .header("X-Requires-Https", "required")
+ .exchange()
+ .expectStatus().is3xxRedirection
+ .returnResult(String::class.java)
+
+ result.assertWithDiagnostics {
+ assertThat(result.responseHeaders.location).hasScheme("https")
+ }
+ }
+
+ @EnableWebFluxSecurity
+ @EnableWebFlux
+ open class HttpRedirectMatcherAndFunctionConfig {
+ @Bean
+ open fun springWebFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain {
+ return http {
+ redirectToHttps {
+ httpsRedirectWhen(PathPatternParserServerWebExchangeMatcher("/secure"))
+ httpsRedirectWhen {
+ it.request.headers.containsKey("X-Requires-Https")
+ }
+ }
+ }
+ }
+ }
+
+ @Test
+ fun `request when port mapper configured then redirected to HTTPS port`() {
+ this.spring.register(PortMapperConfig::class.java).autowire()
+
+ val result = this.client.get()
+ .uri("http://localhost:543")
+ .exchange()
+ .expectStatus().is3xxRedirection
+ .returnResult(String::class.java)
+
+ result.assertWithDiagnostics {
+ assertThat(result.responseHeaders.location).hasScheme("https").hasPort(123)
+ }
+ }
+
+ @EnableWebFluxSecurity
+ @EnableWebFlux
+ open class PortMapperConfig {
+ @Bean
+ open fun springWebFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain {
+ val customPortMapper = PortMapperImpl()
+ customPortMapper.setPortMappings(Collections.singletonMap("543", "123"))
+ return http {
+ redirectToHttps {
+ portMapper = customPortMapper
+ }
+ }
+ }
+ }
+}
diff --git a/config/src/test/kotlin/org/springframework/security/config/web/server/ServerJwtDslTests.kt b/config/src/test/kotlin/org/springframework/security/config/web/server/ServerJwtDslTests.kt
new file mode 100644
index 00000000000..ddb33b5323a
--- /dev/null
+++ b/config/src/test/kotlin/org/springframework/security/config/web/server/ServerJwtDslTests.kt
@@ -0,0 +1,270 @@
+/*
+ * Copyright 2002-2020 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.server
+
+import okhttp3.mockwebserver.MockResponse
+import okhttp3.mockwebserver.MockWebServer
+import org.assertj.core.api.Assertions.assertThat
+import org.junit.Rule
+import org.junit.Test
+import org.mockito.Mockito.*
+import org.springframework.beans.factory.annotation.Autowired
+import org.springframework.context.ApplicationContext
+import org.springframework.context.annotation.Bean
+import org.springframework.core.convert.converter.Converter
+import org.springframework.http.HttpHeaders
+import org.springframework.security.authentication.AbstractAuthenticationToken
+import org.springframework.security.authentication.TestingAuthenticationToken
+import org.springframework.security.config.annotation.web.reactive.EnableWebFluxSecurity
+import org.springframework.security.config.test.SpringTestRule
+import org.springframework.security.oauth2.core.oidc.IdTokenClaimNames
+import org.springframework.security.oauth2.jwt.Jwt
+import org.springframework.security.oauth2.jwt.ReactiveJwtDecoder
+import org.springframework.security.web.server.SecurityWebFilterChain
+import org.springframework.test.web.reactive.server.WebTestClient
+import org.springframework.web.bind.annotation.GetMapping
+import org.springframework.web.bind.annotation.RestController
+import org.springframework.web.reactive.config.EnableWebFlux
+import reactor.core.publisher.Mono
+import java.math.BigInteger
+import java.security.KeyFactory
+import java.security.interfaces.RSAPublicKey
+import java.security.spec.RSAPublicKeySpec
+import javax.annotation.PreDestroy
+
+/**
+ * Tests for [ServerJwtDsl]
+ *
+ * @author Eleftheria Stein
+ */
+class ServerJwtDslTests {
+
+ private val expired = "eyJhbGciOiJSUzI1NiJ9.eyJleHAiOjE1MzUwMzc4OTd9.jqZDDjfc2eysX44lHXEIr9XFd2S8vjIZHCccZU-dRWMRJNsQ1QN5VNnJGklqJBXJR4qgla6cmVqPOLkUHDb0sL0nxM5XuzQaG5ZzKP81RV88shFyAiT0fD-6nl1k-Fai-Fu-VkzSpNXgeONoTxDaYhdB-yxmgrgsApgmbOTE_9AcMk-FQDXQ-pL9kynccFGV0lZx4CA7cyknKN7KBxUilfIycvXODwgKCjj_1WddLTCNGYogJJSg__7NoxzqbyWd3udbHVjqYq7GsMMrGB4_2kBD4CkghOSNcRHbT_DIXowxfAVT7PAg7Q0E5ruZsr2zPZacEUDhJ6-wbvlA0FAOUg"
+ private val messageReadToken = "eyJhbGciOiJSUzI1NiJ9.eyJzdWIiOiJtb2NrLXN1YmplY3QiLCJzY29wZSI6Im1lc3NhZ2U6cmVhZCIsImV4cCI6NDY4ODY0MTQxM30.cRl1bv_dDYcAN5U4NlIVKj8uu4mLMwjABF93P4dShiq-GQ-owzaqTSlB4YarNFgV3PKQvT9wxN1jBpGribvISljakoC0E8wDV-saDi8WxN-qvImYsn1zLzYFiZXCfRIxCmonJpydeiAPRxMTPtwnYDS9Ib0T_iA80TBGd-INhyxUUfrwRW5sqKRbjUciRJhpp7fW2ZYXmi9iPt3HDjRQA4IloJZ7f4-spt5Q9wl5HcQTv1t4XrX4eqhVbE5cCoIkFQnKPOc-jhVM44_eazLU6Xk-CCXP8C_UT5pX0luRS2cJrVFfHp2IR_AWxC-shItg6LNEmNFD4Zc-JLZcr0Q86Q"
+ private val jwkSet = "{\n" +
+ " \"keys\":[\n" +
+ " {\n" +
+ " \"kty\":\"RSA\",\n" +
+ " \"e\":\"AQAB\",\n" +
+ " \"use\":\"sig\",\n" +
+ " \"kid\":\"one\",\n" +
+ " \"n\":\"0IUjrPZDz-3z0UE4ppcKU36v7hnh8FJjhu3lbJYj0qj9eZiwEJxi9HHUfSK1DhUQG7mJBbYTK1tPYCgre5EkfKh-64VhYUa-vz17zYCmuB8fFj4XHE3MLkWIG-AUn8hNbPzYYmiBTjfGnMKxLHjsbdTiF4mtn-85w366916R6midnAuiPD4HjZaZ1PAsuY60gr8bhMEDtJ8unz81hoQrozpBZJ6r8aR1PrsWb1OqPMloK9kAIutJNvWYKacp8WYAp2WWy72PxQ7Fb0eIA1br3A5dnp-Cln6JROJcZUIRJ-QvS6QONWeS2407uQmS-i-lybsqaH0ldYC7NBEBA5inPQ\"\n" +
+ " }\n" +
+ " ]\n" +
+ "}\n"
+
+ @Rule
+ @JvmField
+ val spring = SpringTestRule()
+
+ private lateinit var client: WebTestClient
+
+ @Autowired
+ fun setup(context: ApplicationContext) {
+ this.client = WebTestClient
+ .bindToApplicationContext(context)
+ .configureClient()
+ .build()
+ }
+
+ @Test
+ fun `request when JWT configured with public key and valid token then responds with ok`() {
+ this.spring.register(PublicKeyConfig::class.java, BaseController::class.java).autowire()
+
+ this.client.get()
+ .uri("/")
+ .headers { headers: HttpHeaders -> headers.setBearerAuth(messageReadToken) }
+ .exchange()
+ .expectStatus().isOk
+ }
+
+ @Test
+ fun `request when JWT configured with public key and expired token then responds with unauthorized`() {
+ this.spring.register(PublicKeyConfig::class.java, BaseController::class.java).autowire()
+
+ this.client.get()
+ .uri("/")
+ .headers { headers: HttpHeaders -> headers.setBearerAuth(expired) }
+ .exchange()
+ .expectStatus().isUnauthorized
+ }
+
+ @EnableWebFluxSecurity
+ @EnableWebFlux
+ open class PublicKeyConfig {
+
+ @Bean
+ open fun springWebFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain {
+ return http {
+ authorizeExchange {
+ authorize(anyExchange, authenticated)
+ }
+ oauth2ResourceServer {
+ jwt {
+ publicKey = publicKey()
+ }
+ }
+ }
+ }
+ }
+
+ @Test
+ fun `jwt when using custom JWT decoded then custom decoded used`() {
+ this.spring.register(CustomDecoderConfig::class.java).autowire()
+
+ this.client.get()
+ .uri("/")
+ .headers { headers: HttpHeaders -> headers.setBearerAuth("token") }
+ .exchange()
+
+ verify(CustomDecoderConfig.JWT_DECODER).decode("token")
+ }
+
+ @EnableWebFluxSecurity
+ @EnableWebFlux
+ open class CustomDecoderConfig {
+ companion object {
+ var JWT_DECODER: ReactiveJwtDecoder = mock(ReactiveJwtDecoder::class.java)
+ }
+
+ @Bean
+ open fun springWebFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain {
+ return http {
+ authorizeExchange {
+ authorize(anyExchange, authenticated)
+ }
+ oauth2ResourceServer {
+ jwt {
+ jwtDecoder = JWT_DECODER
+ }
+ }
+ }
+ }
+ }
+
+ @Test
+ fun `jwt when using custom JWK Set URI then custom URI used`() {
+ this.spring.register(CustomJwkSetUriConfig::class.java).autowire()
+
+ CustomJwkSetUriConfig.MOCK_WEB_SERVER.enqueue(MockResponse().setBody(jwkSet))
+
+ this.client.get()
+ .uri("/")
+ .headers { headers: HttpHeaders -> headers.setBearerAuth(messageReadToken) }
+ .exchange()
+
+ val recordedRequest = CustomJwkSetUriConfig.MOCK_WEB_SERVER.takeRequest()
+ assertThat(recordedRequest.path).isEqualTo("/.well-known/jwks.json")
+ }
+
+ @EnableWebFluxSecurity
+ @EnableWebFlux
+ open class CustomJwkSetUriConfig {
+ companion object {
+ var MOCK_WEB_SERVER: MockWebServer = MockWebServer()
+ }
+
+ @Bean
+ open fun springWebFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain {
+ return http {
+ authorizeExchange {
+ authorize(anyExchange, authenticated)
+ }
+ oauth2ResourceServer {
+ jwt {
+ jwkSetUri = mockWebServer().url("/.well-known/jwks.json").toString()
+ }
+ }
+ }
+ }
+
+ @Bean
+ open fun mockWebServer(): MockWebServer {
+ return MOCK_WEB_SERVER
+ }
+
+ @PreDestroy
+ open fun shutdown() {
+ MOCK_WEB_SERVER.shutdown()
+ }
+ }
+
+
+ @Test
+ fun `opaque token when custom JWT authentication converter then converter used`() {
+ this.spring.register(CustomJwtAuthenticationConverterConfig::class.java).autowire()
+ `when`(CustomJwtAuthenticationConverterConfig.DECODER.decode(anyString())).thenReturn(
+ Mono.just(Jwt.withTokenValue("token")
+ .header("alg", "none")
+ .claim(IdTokenClaimNames.SUB, "user")
+ .build()))
+ `when`(CustomJwtAuthenticationConverterConfig.CONVERTER.convert(any()))
+ .thenReturn(Mono.just(TestingAuthenticationToken("test", "this", "ROLE")))
+
+ this.client.get()
+ .uri("/")
+ .headers { headers: HttpHeaders -> headers.setBearerAuth("token") }
+ .exchange()
+
+ verify(CustomJwtAuthenticationConverterConfig.CONVERTER).convert(any())
+ }
+
+ @EnableWebFluxSecurity
+ @EnableWebFlux
+ open class CustomJwtAuthenticationConverterConfig {
+ companion object {
+ var CONVERTER: Converter> = mock(Converter::class.java) as Converter>
+ var DECODER: ReactiveJwtDecoder = mock(ReactiveJwtDecoder::class.java)
+ }
+
+ @Bean
+ open fun springWebFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain {
+ return http {
+ authorizeExchange {
+ authorize(anyExchange, authenticated)
+ }
+ oauth2ResourceServer {
+ jwt {
+ jwtAuthenticationConverter = CONVERTER
+ }
+ }
+ }
+ }
+
+ @Bean
+ open fun jwtDecoder(): ReactiveJwtDecoder {
+ return DECODER
+ }
+ }
+
+ @RestController
+ internal class BaseController {
+ @GetMapping
+ fun index() {
+ }
+ }
+
+ companion object {
+ private fun publicKey(): RSAPublicKey {
+ val modulus = "26323220897278656456354815752829448539647589990395639665273015355787577386000316054335559633864476469390247312823732994485311378484154955583861993455004584140858982659817218753831620205191028763754231454775026027780771426040997832758235764611119743390612035457533732596799927628476322029280486807310749948064176545712270582940917249337311592011920620009965129181413510845780806191965771671528886508636605814099711121026468495328702234901200169245493126030184941412539949521815665744267183140084667383643755535107759061065656273783542590997725982989978433493861515415520051342321336460543070448417126615154138673620797"
+ val exponent = "65537"
+ val spec = RSAPublicKeySpec(BigInteger(modulus), BigInteger(exponent))
+ val factory = KeyFactory.getInstance("RSA")
+ return factory.generatePublic(spec) as RSAPublicKey
+ }
+ }
+}
diff --git a/config/src/test/kotlin/org/springframework/security/config/web/server/ServerLogoutDslTests.kt b/config/src/test/kotlin/org/springframework/security/config/web/server/ServerLogoutDslTests.kt
new file mode 100644
index 00000000000..4acdeea943e
--- /dev/null
+++ b/config/src/test/kotlin/org/springframework/security/config/web/server/ServerLogoutDslTests.kt
@@ -0,0 +1,244 @@
+/*
+ * Copyright 2002-2020 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.server
+
+import org.assertj.core.api.Assertions.assertThat
+import org.junit.Rule
+import org.junit.Test
+import org.mockito.ArgumentMatchers.any
+import org.mockito.Mockito.*
+import org.springframework.beans.factory.annotation.Autowired
+import org.springframework.context.ApplicationContext
+import org.springframework.context.annotation.Bean
+import org.springframework.security.config.annotation.web.reactive.EnableWebFluxSecurity
+import org.springframework.security.config.test.SpringTestRule
+import org.springframework.security.test.web.reactive.server.SecurityMockServerConfigurers.csrf
+import org.springframework.security.web.server.SecurityWebFilterChain
+import org.springframework.security.web.server.authentication.logout.ServerLogoutHandler
+import org.springframework.security.web.server.authentication.logout.ServerLogoutSuccessHandler
+import org.springframework.security.web.server.util.matcher.PathPatternParserServerWebExchangeMatcher
+import org.springframework.test.web.reactive.server.WebTestClient
+import org.springframework.web.reactive.config.EnableWebFlux
+import reactor.core.publisher.Mono
+
+/**
+ * Tests for [ServerLogoutDsl]
+ *
+ * @author Eleftheria Stein
+ */
+class ServerLogoutDslTests {
+ @Rule
+ @JvmField
+ val spring = SpringTestRule()
+
+ private lateinit var client: WebTestClient
+
+ @Autowired
+ fun setup(context: ApplicationContext) {
+ this.client = WebTestClient
+ .bindToApplicationContext(context)
+ .configureClient()
+ .build()
+ }
+
+ @Test
+ fun `logout when defaults used then redirects to login page`() {
+ this.spring.register(LogoutConfig::class.java).autowire()
+
+ val result = this.client
+ .mutateWith(csrf())
+ .post()
+ .uri("/logout")
+ .exchange()
+ .expectStatus().is3xxRedirection
+ .returnResult(String::class.java)
+
+ result.assertWithDiagnostics {
+ assertThat(result.responseHeaders.location)
+ .hasPath("/login")
+ .hasParameter("logout")
+ }
+ }
+
+ @EnableWebFluxSecurity
+ @EnableWebFlux
+ open class LogoutConfig {
+ @Bean
+ open fun springWebFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain {
+ return http {
+ logout { }
+ }
+ }
+ }
+
+ @Test
+ fun `logout when custom logout URL then custom URL redirects to login page`() {
+ this.spring.register(CustomUrlConfig::class.java).autowire()
+
+ val result = this.client
+ .mutateWith(csrf())
+ .post()
+ .uri("/custom-logout")
+ .exchange()
+ .expectStatus().is3xxRedirection
+ .returnResult(String::class.java)
+
+ result.assertWithDiagnostics {
+ assertThat(result.responseHeaders.location)
+ .hasPath("/login")
+ .hasParameter("logout")
+ }
+ }
+
+ @EnableWebFluxSecurity
+ @EnableWebFlux
+ open class CustomUrlConfig {
+ @Bean
+ open fun springWebFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain {
+ return http {
+ logout {
+ logoutUrl = "/custom-logout"
+ }
+ }
+ }
+ }
+
+ @Test
+ fun `logout when custom requires logout matcher then matching request redirects to login page`() {
+ this.spring.register(RequiresLogoutConfig::class.java).autowire()
+
+ val result = this.client
+ .mutateWith(csrf())
+ .post()
+ .uri("/custom-logout")
+ .exchange()
+ .expectStatus().is3xxRedirection
+ .returnResult(String::class.java)
+
+ result.assertWithDiagnostics {
+ assertThat(result.responseHeaders.location)
+ .hasPath("/login")
+ .hasParameter("logout")
+ }
+ }
+
+ @EnableWebFluxSecurity
+ @EnableWebFlux
+ open class RequiresLogoutConfig {
+ @Bean
+ open fun springWebFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain {
+ return http {
+ logout {
+ requiresLogout = PathPatternParserServerWebExchangeMatcher("/custom-logout")
+ }
+ }
+ }
+ }
+
+ @Test
+ fun `logout when custom logout handler then custom handler invoked`() {
+ this.spring.register(CustomLogoutHandlerConfig::class.java).autowire()
+
+ `when`(CustomLogoutHandlerConfig.LOGOUT_HANDLER.logout(any(), any()))
+ .thenReturn(Mono.empty())
+
+ this.client
+ .mutateWith(csrf())
+ .post()
+ .uri("/logout")
+ .exchange()
+
+ verify(CustomLogoutHandlerConfig.LOGOUT_HANDLER)
+ .logout(any(), any())
+ }
+
+ @EnableWebFluxSecurity
+ @EnableWebFlux
+ open class CustomLogoutHandlerConfig {
+ companion object {
+ var LOGOUT_HANDLER: ServerLogoutHandler = mock(ServerLogoutHandler::class.java)
+ }
+
+ @Bean
+ open fun springWebFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain {
+ return http {
+ logout {
+ logoutHandler = LOGOUT_HANDLER
+ }
+ }
+ }
+ }
+
+ @Test
+ fun `logout when custom logout success handler then custom handler invoked`() {
+ this.spring.register(CustomLogoutSuccessHandlerConfig::class.java).autowire()
+
+ this.client
+ .mutateWith(csrf())
+ .post()
+ .uri("/logout")
+ .exchange()
+
+ verify(CustomLogoutSuccessHandlerConfig.LOGOUT_HANDLER)
+ .onLogoutSuccess(any(), any())
+ }
+
+ @EnableWebFluxSecurity
+ @EnableWebFlux
+ open class CustomLogoutSuccessHandlerConfig {
+ companion object {
+ var LOGOUT_HANDLER: ServerLogoutSuccessHandler = mock(ServerLogoutSuccessHandler::class.java)
+ }
+
+ @Bean
+ open fun springWebFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain {
+ return http {
+ logout {
+ logoutSuccessHandler = LOGOUT_HANDLER
+ }
+ }
+ }
+ }
+
+ @Test
+ fun `logout when disabled then logout URL not found`() {
+ this.spring.register(LogoutDisabledConfig::class.java).autowire()
+
+ this.client
+ .mutateWith(csrf())
+ .post()
+ .uri("/logout")
+ .exchange()
+ .expectStatus().isNotFound
+ }
+
+ @EnableWebFluxSecurity
+ @EnableWebFlux
+ open class LogoutDisabledConfig {
+ @Bean
+ open fun springWebFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain {
+ return http {
+ authorizeExchange {
+ authorize(anyExchange, permitAll)
+ }
+ logout {
+ disable()
+ }
+ }
+ }
+ }
+}
diff --git a/config/src/test/kotlin/org/springframework/security/config/web/server/ServerOAuth2ClientDslTests.kt b/config/src/test/kotlin/org/springframework/security/config/web/server/ServerOAuth2ClientDslTests.kt
new file mode 100644
index 00000000000..de317e59ee2
--- /dev/null
+++ b/config/src/test/kotlin/org/springframework/security/config/web/server/ServerOAuth2ClientDslTests.kt
@@ -0,0 +1,223 @@
+/*
+ * Copyright 2002-2020 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.server
+
+import org.junit.Rule
+import org.junit.Test
+import org.mockito.ArgumentMatchers.any
+import org.mockito.Mockito.*
+import org.springframework.beans.factory.annotation.Autowired
+import org.springframework.context.ApplicationContext
+import org.springframework.context.annotation.Bean
+import org.springframework.context.annotation.Configuration
+import org.springframework.security.authentication.ReactiveAuthenticationManager
+import org.springframework.security.authentication.TestingAuthenticationToken
+import org.springframework.security.config.annotation.web.reactive.EnableWebFluxSecurity
+import org.springframework.security.config.oauth2.client.CommonOAuth2Provider
+import org.springframework.security.config.test.SpringTestRule
+import org.springframework.security.oauth2.client.registration.InMemoryReactiveClientRegistrationRepository
+import org.springframework.security.oauth2.client.registration.ReactiveClientRegistrationRepository
+import org.springframework.security.oauth2.client.web.server.ServerAuthorizationRequestRepository
+import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationRequest
+import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames
+import org.springframework.security.web.server.SecurityWebFilterChain
+import org.springframework.security.web.server.authentication.ServerAuthenticationConverter
+import org.springframework.test.web.reactive.server.WebTestClient
+import org.springframework.web.reactive.config.EnableWebFlux
+import reactor.core.publisher.Mono
+
+/**
+ * Tests for [ServerOAuth2ClientDsl]
+ *
+ * @author Eleftheria Stein
+ */
+class ServerOAuth2ClientDslTests {
+ @Rule
+ @JvmField
+ val spring = SpringTestRule()
+
+ private lateinit var client: WebTestClient
+
+ @Autowired
+ fun setup(context: ApplicationContext) {
+ this.client = WebTestClient
+ .bindToApplicationContext(context)
+ .configureClient()
+ .build()
+ }
+
+ @Test
+ fun `OAuth2 client when custom client registration repository then bean is not required`() {
+ this.spring.register(ClientRepoConfig::class.java).autowire()
+ }
+
+ @EnableWebFluxSecurity
+ @EnableWebFlux
+ open class ClientRepoConfig {
+ @Bean
+ open fun springWebFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain {
+ return http {
+ authorizeExchange {
+ authorize(anyExchange, authenticated)
+ }
+ oauth2Client {
+ clientRegistrationRepository = InMemoryReactiveClientRegistrationRepository(
+ CommonOAuth2Provider.GOOGLE
+ .getBuilder("google").clientId("clientId").clientSecret("clientSecret")
+ .build()
+ )
+ }
+ }
+ }
+ }
+
+ @Test
+ fun `OAuth2 client when authorization request repository configured then custom repository used`() {
+ this.spring.register(AuthorizationRequestRepositoryConfig::class.java, ClientConfig::class.java).autowire()
+
+ this.client.get()
+ .uri {
+ it.path("/")
+ .queryParam(OAuth2ParameterNames.CODE, "code")
+ .queryParam(OAuth2ParameterNames.STATE, "state")
+ .build()
+ }
+ .exchange()
+
+ verify(AuthorizationRequestRepositoryConfig.AUTHORIZATION_REQUEST_REPOSITORY).loadAuthorizationRequest(any())
+ }
+
+ @EnableWebFluxSecurity
+ @EnableWebFlux
+ open class AuthorizationRequestRepositoryConfig {
+ companion object {
+ var AUTHORIZATION_REQUEST_REPOSITORY = mock(ServerAuthorizationRequestRepository::class.java)
+ as ServerAuthorizationRequestRepository
+ }
+
+ @Bean
+ open fun springWebFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain {
+ return http {
+ oauth2Client {
+ authorizationRequestRepository = AUTHORIZATION_REQUEST_REPOSITORY
+ }
+ }
+ }
+ }
+
+ @Test
+ fun `OAuth2 client when authentication converter configured then custom converter used`() {
+ this.spring.register(AuthenticationConverterConfig::class.java, ClientConfig::class.java).autowire()
+
+ `when`(AuthenticationConverterConfig.AUTHORIZATION_REQUEST_REPOSITORY.loadAuthorizationRequest(any()))
+ .thenReturn(Mono.just(OAuth2AuthorizationRequest.authorizationCode()
+ .authorizationUri("https://example.com/login/oauth/authorize")
+ .clientId("clientId")
+ .redirectUri("/authorize/oauth2/code/google")
+ .build()))
+
+ this.client.get()
+ .uri {
+ it.path("/authorize/oauth2/code/google")
+ .queryParam(OAuth2ParameterNames.CODE, "code")
+ .queryParam(OAuth2ParameterNames.STATE, "state")
+ .build()
+ }
+ .exchange()
+
+ verify(AuthenticationConverterConfig.AUTHENTICATION_CONVERTER).convert(any())
+ }
+
+ @EnableWebFluxSecurity
+ @EnableWebFlux
+ open class AuthenticationConverterConfig {
+ companion object {
+ var AUTHORIZATION_REQUEST_REPOSITORY = mock(ServerAuthorizationRequestRepository::class.java)
+ as ServerAuthorizationRequestRepository
+ var AUTHENTICATION_CONVERTER: ServerAuthenticationConverter = mock(ServerAuthenticationConverter::class.java)
+ }
+
+ @Bean
+ open fun springWebFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain {
+ return http {
+ oauth2Client {
+ authorizationRequestRepository = AUTHORIZATION_REQUEST_REPOSITORY
+ authenticationConverter = AUTHENTICATION_CONVERTER
+ }
+ }
+ }
+ }
+
+ @Test
+ fun `OAuth2 client when authentication manager configured then custom manager used`() {
+ this.spring.register(AuthenticationManagerConfig::class.java, ClientConfig::class.java).autowire()
+
+ `when`(AuthenticationManagerConfig.AUTHORIZATION_REQUEST_REPOSITORY.loadAuthorizationRequest(any()))
+ .thenReturn(Mono.just(OAuth2AuthorizationRequest.authorizationCode()
+ .authorizationUri("https://example.com/login/oauth/authorize")
+ .clientId("clientId")
+ .redirectUri("/authorize/oauth2/code/google")
+ .build()))
+ `when`(AuthenticationManagerConfig.AUTHENTICATION_CONVERTER.convert(any()))
+ .thenReturn(Mono.just(TestingAuthenticationToken("a", "b", "c")))
+
+ this.client.get()
+ .uri {
+ it.path("/authorize/oauth2/code/google")
+ .queryParam(OAuth2ParameterNames.CODE, "code")
+ .queryParam(OAuth2ParameterNames.STATE, "state")
+ .build()
+ }
+ .exchange()
+
+ verify(AuthenticationManagerConfig.AUTHENTICATION_MANAGER).authenticate(any())
+ }
+
+ @EnableWebFluxSecurity
+ @EnableWebFlux
+ open class AuthenticationManagerConfig {
+ companion object {
+ var AUTHORIZATION_REQUEST_REPOSITORY = mock(ServerAuthorizationRequestRepository::class.java)
+ as ServerAuthorizationRequestRepository
+ var AUTHENTICATION_CONVERTER: ServerAuthenticationConverter = mock(ServerAuthenticationConverter::class.java)
+ var AUTHENTICATION_MANAGER: ReactiveAuthenticationManager = mock(ReactiveAuthenticationManager::class.java)
+ }
+
+ @Bean
+ open fun springWebFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain {
+ return http {
+ oauth2Client {
+ authorizationRequestRepository = AUTHORIZATION_REQUEST_REPOSITORY
+ authenticationConverter = AUTHENTICATION_CONVERTER
+ authenticationManager = AUTHENTICATION_MANAGER
+ }
+ }
+ }
+ }
+
+ @Configuration
+ open class ClientConfig {
+ @Bean
+ open fun clientRegistrationRepository(): ReactiveClientRegistrationRepository {
+ return InMemoryReactiveClientRegistrationRepository(
+ CommonOAuth2Provider.GOOGLE
+ .getBuilder("google").clientId("clientId").clientSecret("clientSecret")
+ .build()
+ )
+ }
+ }
+}
diff --git a/config/src/test/kotlin/org/springframework/security/config/web/server/ServerOAuth2LoginDslTests.kt b/config/src/test/kotlin/org/springframework/security/config/web/server/ServerOAuth2LoginDslTests.kt
new file mode 100644
index 00000000000..43aae3aecea
--- /dev/null
+++ b/config/src/test/kotlin/org/springframework/security/config/web/server/ServerOAuth2LoginDslTests.kt
@@ -0,0 +1,201 @@
+/*
+ * Copyright 2002-2020 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.server
+
+import org.junit.Rule
+import org.junit.Test
+import org.mockito.Mockito.*
+import org.springframework.beans.factory.annotation.Autowired
+import org.springframework.context.ApplicationContext
+import org.springframework.context.annotation.Bean
+import org.springframework.context.annotation.Configuration
+import org.springframework.security.config.annotation.web.reactive.EnableWebFluxSecurity
+import org.springframework.security.config.oauth2.client.CommonOAuth2Provider
+import org.springframework.security.config.test.SpringTestRule
+import org.springframework.security.oauth2.client.registration.InMemoryReactiveClientRegistrationRepository
+import org.springframework.security.oauth2.client.registration.ReactiveClientRegistrationRepository
+import org.springframework.security.oauth2.client.web.server.ServerAuthorizationRequestRepository
+import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationRequest
+import org.springframework.security.web.server.SecurityWebFilterChain
+import org.springframework.security.web.server.authentication.ServerAuthenticationConverter
+import org.springframework.security.web.server.util.matcher.ServerWebExchangeMatcher
+import org.springframework.test.web.reactive.server.WebTestClient
+import org.springframework.web.reactive.config.EnableWebFlux
+
+/**
+ * Tests for [ServerOAuth2LoginDsl]
+ *
+ * @author Eleftheria Stein
+ */
+class ServerOAuth2LoginDslTests {
+ @Rule
+ @JvmField
+ val spring = SpringTestRule()
+
+ private lateinit var client: WebTestClient
+
+ @Autowired
+ fun setup(context: ApplicationContext) {
+ this.client = WebTestClient
+ .bindToApplicationContext(context)
+ .configureClient()
+ .build()
+ }
+
+ @Test
+ fun `oauth2Login when custom client registration repository then bean is not required`() {
+ this.spring.register(ClientRepoConfig::class.java).autowire()
+ }
+
+ @EnableWebFluxSecurity
+ @EnableWebFlux
+ open class ClientRepoConfig {
+ @Bean
+ open fun springWebFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain {
+ return http {
+ authorizeExchange {
+ authorize(anyExchange, authenticated)
+ }
+ oauth2Login {
+ clientRegistrationRepository = InMemoryReactiveClientRegistrationRepository(
+ CommonOAuth2Provider.GOOGLE
+ .getBuilder("google").clientId("clientId").clientSecret("clientSecret")
+ .build()
+ )
+ }
+ }
+ }
+ }
+
+ @Test
+ fun `login page when OAuth2 login configured then default login page created`() {
+ this.spring.register(OAuth2LoginConfig::class.java, ClientConfig::class.java).autowire()
+
+ this.client.get()
+ .uri("/login")
+ .exchange()
+ .expectStatus().isOk
+ }
+
+ @EnableWebFluxSecurity
+ @EnableWebFlux
+ open class OAuth2LoginConfig {
+ @Bean
+ open fun springWebFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain {
+ return http {
+ oauth2Login { }
+ }
+ }
+ }
+
+ @Test
+ fun `OAuth2 login when authorization request repository configured then custom repository used`() {
+ this.spring.register(AuthorizationRequestRepositoryConfig::class.java, ClientConfig::class.java).autowire()
+
+ this.client.get()
+ .uri("/login/oauth2/code/google")
+ .exchange()
+
+ verify(AuthorizationRequestRepositoryConfig.AUTHORIZATION_REQUEST_REPOSITORY).removeAuthorizationRequest(any())
+ }
+
+ @EnableWebFluxSecurity
+ @EnableWebFlux
+ open class AuthorizationRequestRepositoryConfig {
+ companion object {
+ var AUTHORIZATION_REQUEST_REPOSITORY = mock(ServerAuthorizationRequestRepository::class.java)
+ as ServerAuthorizationRequestRepository
+ }
+
+ @Bean
+ open fun springWebFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain {
+ return http {
+ oauth2Login {
+ authorizationRequestRepository = AUTHORIZATION_REQUEST_REPOSITORY
+ }
+ }
+ }
+ }
+
+ @Test
+ fun `OAuth2 login when authentication matcher configured then custom matcher used`() {
+ this.spring.register(AuthenticationMatcherConfig::class.java, ClientConfig::class.java).autowire()
+
+ this.client.get()
+ .uri("/")
+ .exchange()
+
+ verify(AuthenticationMatcherConfig.AUTHENTICATION_MATCHER).matches(any())
+ }
+
+ @EnableWebFluxSecurity
+ @EnableWebFlux
+ open class AuthenticationMatcherConfig {
+ companion object {
+ var AUTHENTICATION_MATCHER: ServerWebExchangeMatcher = mock(ServerWebExchangeMatcher::class.java)
+ }
+
+ @Bean
+ open fun springWebFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain {
+ return http {
+ oauth2Login {
+ authenticationMatcher = AUTHENTICATION_MATCHER
+ }
+ }
+ }
+ }
+
+ @Test
+ fun `OAuth2 login when authentication converter configured then custom converter used`() {
+ this.spring.register(AuthenticationConverterConfig::class.java, ClientConfig::class.java).autowire()
+
+ this.client.get()
+ .uri("/login/oauth2/code/google")
+ .exchange()
+
+ verify(AuthenticationConverterConfig.AUTHENTICATION_CONVERTER).convert(any())
+ }
+
+ @EnableWebFluxSecurity
+ @EnableWebFlux
+ open class AuthenticationConverterConfig {
+ companion object {
+ var AUTHENTICATION_CONVERTER: ServerAuthenticationConverter = mock(ServerAuthenticationConverter::class.java)
+ }
+
+ @Bean
+ open fun springWebFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain {
+ return http {
+ oauth2Login {
+ authenticationConverter = AUTHENTICATION_CONVERTER
+ }
+ }
+ }
+ }
+
+ @Configuration
+ open class ClientConfig {
+ @Bean
+ open fun clientRegistrationRepository(): ReactiveClientRegistrationRepository {
+ return InMemoryReactiveClientRegistrationRepository(
+ CommonOAuth2Provider.GOOGLE
+ .getBuilder("google").clientId("clientId").clientSecret("clientSecret")
+ .build()
+ )
+ }
+ }
+}
diff --git a/config/src/test/kotlin/org/springframework/security/config/web/server/ServerOAuth2ResourceServerDslTests.kt b/config/src/test/kotlin/org/springframework/security/config/web/server/ServerOAuth2ResourceServerDslTests.kt
new file mode 100644
index 00000000000..2ddfa2b107c
--- /dev/null
+++ b/config/src/test/kotlin/org/springframework/security/config/web/server/ServerOAuth2ResourceServerDslTests.kt
@@ -0,0 +1,204 @@
+/*
+ * Copyright 2002-2020 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.server
+
+import org.junit.Rule
+import org.junit.Test
+import org.mockito.ArgumentMatchers.any
+import org.mockito.Mockito.mock
+import org.mockito.Mockito.verify
+import org.springframework.beans.factory.annotation.Autowired
+import org.springframework.context.ApplicationContext
+import org.springframework.context.annotation.Bean
+import org.springframework.http.HttpStatus
+import org.springframework.http.server.reactive.ServerHttpRequest
+import org.springframework.security.authentication.ReactiveAuthenticationManagerResolver
+import org.springframework.security.config.annotation.web.reactive.EnableWebFluxSecurity
+import org.springframework.security.config.test.SpringTestRule
+import org.springframework.security.oauth2.server.resource.web.server.ServerBearerTokenAuthenticationConverter
+import org.springframework.security.web.server.SecurityWebFilterChain
+import org.springframework.security.web.server.authentication.HttpStatusServerEntryPoint
+import org.springframework.security.web.server.authorization.HttpStatusServerAccessDeniedHandler
+import org.springframework.test.web.reactive.server.WebTestClient
+import org.springframework.web.reactive.config.EnableWebFlux
+import org.springframework.web.server.ServerWebExchange
+import java.math.BigInteger
+import java.security.KeyFactory
+import java.security.interfaces.RSAPublicKey
+import java.security.spec.RSAPublicKeySpec
+
+/**
+ * Tests for [ServerOAuth2ResourceServerDsl]
+ *
+ * @author Eleftheria Stein
+ */
+class ServerOAuth2ResourceServerDslTests {
+ private val validJwt = "eyJhbGciOiJSUzI1NiJ9.eyJzdWIiOiJtb2NrLXN1YmplY3QiLCJzY29wZSI6Im1lc3NhZ2U6cmVhZCIsImV4cCI6NDY4ODY0MTQxM30.cRl1bv_dDYcAN5U4NlIVKj8uu4mLMwjABF93P4dShiq-GQ-owzaqTSlB4YarNFgV3PKQvT9wxN1jBpGribvISljakoC0E8wDV-saDi8WxN-qvImYsn1zLzYFiZXCfRIxCmonJpydeiAPRxMTPtwnYDS9Ib0T_iA80TBGd-INhyxUUfrwRW5sqKRbjUciRJhpp7fW2ZYXmi9iPt3HDjRQA4IloJZ7f4-spt5Q9wl5HcQTv1t4XrX4eqhVbE5cCoIkFQnKPOc-jhVM44_eazLU6Xk-CCXP8C_UT5pX0luRS2cJrVFfHp2IR_AWxC-shItg6LNEmNFD4Zc-JLZcr0Q86Q"
+
+ @Rule
+ @JvmField
+ val spring = SpringTestRule()
+
+ private lateinit var client: WebTestClient
+
+ @Autowired
+ fun setup(context: ApplicationContext) {
+ this.client = WebTestClient
+ .bindToApplicationContext(context)
+ .configureClient()
+ .build()
+ }
+
+ @Test
+ fun `request when custom access denied handler configured then custom handler used`() {
+ this.spring.register(AccessDeniedHandlerConfig::class.java).autowire()
+
+ this.client.get()
+ .uri("/")
+ .headers { it.setBearerAuth(validJwt) }
+ .exchange()
+ .expectStatus().isSeeOther
+ }
+
+ @EnableWebFluxSecurity
+ @EnableWebFlux
+ open class AccessDeniedHandlerConfig {
+
+ @Bean
+ open fun springWebFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain {
+ return http {
+ authorizeExchange {
+ authorize(anyExchange, hasAuthority("ADMIN"))
+ }
+ oauth2ResourceServer {
+ accessDeniedHandler = HttpStatusServerAccessDeniedHandler(HttpStatus.SEE_OTHER)
+ jwt {
+ publicKey = publicKey()
+ }
+ }
+ }
+ }
+ }
+
+ @Test
+ fun `request when custom entry point configured then custom entry point used`() {
+ this.spring.register(AuthenticationEntryPointConfig::class.java).autowire()
+
+ this.client.get()
+ .uri("/")
+ .exchange()
+ .expectStatus().isSeeOther
+ }
+
+ @EnableWebFluxSecurity
+ @EnableWebFlux
+ open class AuthenticationEntryPointConfig {
+
+ @Bean
+ open fun springWebFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain {
+ return http {
+ authorizeExchange {
+ authorize(anyExchange, authenticated)
+ }
+ oauth2ResourceServer {
+ authenticationEntryPoint = HttpStatusServerEntryPoint(HttpStatus.SEE_OTHER)
+ jwt {
+ publicKey = publicKey()
+ }
+ }
+ }
+ }
+ }
+
+ @Test
+ fun `request when custom bearer token converter configured then custom converter used`() {
+ this.spring.register(BearerTokenConverterConfig::class.java).autowire()
+
+ this.client.get()
+ .uri("/")
+ .headers { it.setBearerAuth(validJwt) }
+ .exchange()
+
+ verify(BearerTokenConverterConfig.CONVERTER).convert(any())
+ }
+
+ @EnableWebFluxSecurity
+ @EnableWebFlux
+ open class BearerTokenConverterConfig {
+ companion object {
+ val CONVERTER: ServerBearerTokenAuthenticationConverter = mock(ServerBearerTokenAuthenticationConverter::class.java)
+ }
+
+ @Bean
+ open fun springWebFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain {
+ return http {
+ authorizeExchange {
+ authorize(anyExchange, authenticated)
+ }
+ oauth2ResourceServer {
+ bearerTokenConverter = CONVERTER
+ jwt {
+ publicKey = publicKey()
+ }
+ }
+ }
+ }
+ }
+
+ @Test
+ fun `request when custom authentication manager resolver configured then custom resolver used`() {
+ this.spring.register(AuthenticationManagerResolverConfig::class.java).autowire()
+
+ this.client.get()
+ .uri("/")
+ .headers { it.setBearerAuth(validJwt) }
+ .exchange()
+
+ verify(AuthenticationManagerResolverConfig.RESOLVER).resolve(any())
+ }
+
+ @EnableWebFluxSecurity
+ @EnableWebFlux
+ open class AuthenticationManagerResolverConfig {
+ companion object {
+ val RESOLVER: ReactiveAuthenticationManagerResolver =
+ mock(ReactiveAuthenticationManagerResolver::class.java) as ReactiveAuthenticationManagerResolver
+ }
+
+ @Bean
+ open fun springWebFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain {
+ return http {
+ authorizeExchange {
+ authorize(anyExchange, authenticated)
+ }
+ oauth2ResourceServer {
+ authenticationManagerResolver = RESOLVER
+ }
+ }
+ }
+ }
+
+ companion object {
+ private fun publicKey(): RSAPublicKey {
+ val modulus = "26323220897278656456354815752829448539647589990395639665273015355787577386000316054335559633864476469390247312823732994485311378484154955583861993455004584140858982659817218753831620205191028763754231454775026027780771426040997832758235764611119743390612035457533732596799927628476322029280486807310749948064176545712270582940917249337311592011920620009965129181413510845780806191965771671528886508636605814099711121026468495328702234901200169245493126030184941412539949521815665744267183140084667383643755535107759061065656273783542590997725982989978433493861515415520051342321336460543070448417126615154138673620797"
+ val exponent = "65537"
+ val spec = RSAPublicKeySpec(BigInteger(modulus), BigInteger(exponent))
+ val factory = KeyFactory.getInstance("RSA")
+ return factory.generatePublic(spec) as RSAPublicKey
+ }
+ }
+}
diff --git a/config/src/test/kotlin/org/springframework/security/config/web/server/ServerOpaqueTokenDslTests.kt b/config/src/test/kotlin/org/springframework/security/config/web/server/ServerOpaqueTokenDslTests.kt
new file mode 100644
index 00000000000..d7807de6fc1
--- /dev/null
+++ b/config/src/test/kotlin/org/springframework/security/config/web/server/ServerOpaqueTokenDslTests.kt
@@ -0,0 +1,201 @@
+/*
+ * Copyright 2002-2020 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.server
+
+import okhttp3.mockwebserver.MockResponse
+import okhttp3.mockwebserver.MockWebServer
+import org.assertj.core.api.Assertions.assertThat
+import org.junit.Rule
+import org.junit.Test
+import org.springframework.beans.factory.annotation.Autowired
+import org.springframework.context.ApplicationContext
+import org.springframework.context.annotation.Bean
+import org.springframework.http.HttpHeaders
+import org.springframework.security.config.annotation.web.reactive.EnableWebFluxSecurity
+import org.springframework.security.config.test.SpringTestRule
+import org.springframework.security.oauth2.server.resource.introspection.NimbusReactiveOpaqueTokenIntrospector
+import org.springframework.security.oauth2.server.resource.introspection.ReactiveOpaqueTokenIntrospector
+import org.springframework.security.web.server.SecurityWebFilterChain
+import org.springframework.test.web.reactive.server.WebTestClient
+import org.springframework.web.reactive.config.EnableWebFlux
+import javax.annotation.PreDestroy
+
+/**
+ * Tests for [ServerOpaqueTokenDsl]
+ *
+ * @author Eleftheria Stein
+ */
+class ServerOpaqueTokenDslTests {
+ @Rule
+ @JvmField
+ val spring = SpringTestRule()
+
+ private lateinit var client: WebTestClient
+
+ @Autowired
+ fun setup(context: ApplicationContext) {
+ this.client = WebTestClient
+ .bindToApplicationContext(context)
+ .configureClient()
+ .build()
+ }
+
+ @Test
+ fun `opaque token when using defaults then uses introspector bean`() {
+ this.spring.register(IntrospectorBeanConfig::class.java).autowire()
+
+ IntrospectorBeanConfig.MOCK_WEB_SERVER.enqueue(MockResponse())
+
+ this.client.get()
+ .uri("/")
+ .header(HttpHeaders.AUTHORIZATION, "Bearer token")
+ .exchange()
+
+ val recordedRequest = IntrospectorBeanConfig.MOCK_WEB_SERVER.takeRequest()
+ assertThat(recordedRequest.path).isEqualTo("/introspect")
+ }
+
+ @EnableWebFluxSecurity
+ @EnableWebFlux
+ open class IntrospectorBeanConfig {
+ companion object {
+ var MOCK_WEB_SERVER: MockWebServer = MockWebServer()
+ }
+
+ @Bean
+ open fun springWebFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain {
+ return http {
+ authorizeExchange {
+ authorize(anyExchange, authenticated)
+ }
+ oauth2ResourceServer {
+ opaqueToken { }
+ }
+ }
+ }
+
+ @Bean
+ open fun mockWebServer(): MockWebServer {
+ return MOCK_WEB_SERVER
+ }
+
+ @PreDestroy
+ open fun shutdown() {
+ MOCK_WEB_SERVER.shutdown()
+ }
+
+ @Bean
+ open fun tokenIntrospectionClient(): ReactiveOpaqueTokenIntrospector {
+ return NimbusReactiveOpaqueTokenIntrospector(mockWebServer().url("/introspect").toString(), "client", "secret")
+ }
+ }
+
+ @Test
+ fun `opaque token when using custom introspector then introspector used`() {
+ this.spring.register(CustomIntrospectorConfig::class.java).autowire()
+
+ CustomIntrospectorConfig.MOCK_WEB_SERVER.enqueue(MockResponse())
+
+ this.client.get()
+ .uri("/")
+ .header(HttpHeaders.AUTHORIZATION, "Bearer token")
+ .exchange()
+
+ val recordedRequest = CustomIntrospectorConfig.MOCK_WEB_SERVER.takeRequest()
+ assertThat(recordedRequest.path).isEqualTo("/introspector")
+ }
+
+ @EnableWebFluxSecurity
+ @EnableWebFlux
+ open class CustomIntrospectorConfig {
+ companion object {
+ var MOCK_WEB_SERVER: MockWebServer = MockWebServer()
+ }
+
+ @Bean
+ open fun springWebFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain {
+ return http {
+ authorizeExchange {
+ authorize(anyExchange, authenticated)
+ }
+ oauth2ResourceServer {
+ opaqueToken {
+ introspector = NimbusReactiveOpaqueTokenIntrospector(mockWebServer().url("/introspector").toString(), "client", "secret")
+ }
+ }
+ }
+ }
+
+ @Bean
+ open fun mockWebServer(): MockWebServer {
+ return MOCK_WEB_SERVER
+ }
+
+ @PreDestroy
+ open fun shutdown() {
+ MOCK_WEB_SERVER.shutdown()
+ }
+ }
+
+ @Test
+ fun `opaque token when using custom introspection URI and credentials then custom used`() {
+ this.spring.register(CustomIntrospectionUriAndCredentialsConfig::class.java).autowire()
+
+ CustomIntrospectionUriAndCredentialsConfig.MOCK_WEB_SERVER.enqueue(MockResponse())
+
+ this.client.get()
+ .uri("/")
+ .header(HttpHeaders.AUTHORIZATION, "Bearer token")
+ .exchange()
+
+ val recordedRequest = CustomIntrospectionUriAndCredentialsConfig.MOCK_WEB_SERVER.takeRequest()
+ assertThat(recordedRequest.path).isEqualTo("/introspection-uri")
+ }
+
+ @EnableWebFluxSecurity
+ @EnableWebFlux
+ open class CustomIntrospectionUriAndCredentialsConfig {
+ companion object {
+ var MOCK_WEB_SERVER: MockWebServer = MockWebServer()
+ }
+
+ @Bean
+ open fun springWebFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain {
+ return http {
+ authorizeExchange {
+ authorize(anyExchange, authenticated)
+ }
+ oauth2ResourceServer {
+ opaqueToken {
+ introspectionUri = mockWebServer().url("/introspection-uri").toString()
+ introspectionClientCredentials("client", "secret")
+ }
+ }
+ }
+ }
+
+ @Bean
+ open fun mockWebServer(): MockWebServer {
+ return MOCK_WEB_SERVER
+ }
+
+ @PreDestroy
+ open fun shutdown() {
+ MOCK_WEB_SERVER.shutdown()
+ }
+ }
+}
diff --git a/config/src/test/kotlin/org/springframework/security/config/web/server/ServerReferrerPolicyDslTests.kt b/config/src/test/kotlin/org/springframework/security/config/web/server/ServerReferrerPolicyDslTests.kt
new file mode 100644
index 00000000000..47ac3271654
--- /dev/null
+++ b/config/src/test/kotlin/org/springframework/security/config/web/server/ServerReferrerPolicyDslTests.kt
@@ -0,0 +1,99 @@
+/*
+ * Copyright 2002-2020 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.server
+
+import org.junit.Rule
+import org.junit.Test
+import org.springframework.beans.factory.annotation.Autowired
+import org.springframework.context.ApplicationContext
+import org.springframework.context.annotation.Bean
+import org.springframework.security.config.annotation.web.reactive.EnableWebFluxSecurity
+import org.springframework.security.config.test.SpringTestRule
+import org.springframework.security.web.header.writers.ReferrerPolicyHeaderWriter
+import org.springframework.security.web.server.SecurityWebFilterChain
+import org.springframework.security.web.server.header.ReferrerPolicyServerHttpHeadersWriter
+import org.springframework.test.web.reactive.server.WebTestClient
+import org.springframework.web.reactive.config.EnableWebFlux
+
+/**
+ * Tests for [ServerReferrerPolicyDsl]
+ *
+ * @author Eleftheria Stein
+ */
+class ServerReferrerPolicyDslTests {
+ @Rule
+ @JvmField
+ val spring = SpringTestRule()
+
+ private lateinit var client: WebTestClient
+
+ @Autowired
+ fun setup(context: ApplicationContext) {
+ this.client = WebTestClient
+ .bindToApplicationContext(context)
+ .configureClient()
+ .build()
+ }
+
+ @Test
+ fun `request when referrer policy configured then referrer policy header in response`() {
+ this.spring.register(ReferrerPolicyConfig::class.java).autowire()
+
+ this.client.get()
+ .uri("/")
+ .exchange()
+ .expectHeader().valueEquals("Referrer-Policy", ReferrerPolicyHeaderWriter.ReferrerPolicy.NO_REFERRER.policy)
+ }
+
+ @EnableWebFluxSecurity
+ @EnableWebFlux
+ open class ReferrerPolicyConfig {
+ @Bean
+ open fun springWebFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain {
+ return http {
+ headers {
+ referrerPolicy { }
+ }
+ }
+ }
+ }
+
+ @Test
+ fun `request when custom policy configured then custom policy in response header`() {
+ this.spring.register(CustomPolicyConfig::class.java).autowire()
+
+ this.client.get()
+ .uri("/")
+ .exchange()
+ .expectHeader().valueEquals("Referrer-Policy", ReferrerPolicyServerHttpHeadersWriter.ReferrerPolicy.SAME_ORIGIN.policy)
+ }
+
+ @EnableWebFluxSecurity
+ @EnableWebFlux
+ open class CustomPolicyConfig {
+ @Bean
+ open fun springWebFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain {
+ return http {
+ headers {
+ referrerPolicy {
+ policy = ReferrerPolicyServerHttpHeadersWriter.ReferrerPolicy.SAME_ORIGIN
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/config/src/test/kotlin/org/springframework/security/config/web/server/ServerRequestCacheDslTests.kt b/config/src/test/kotlin/org/springframework/security/config/web/server/ServerRequestCacheDslTests.kt
new file mode 100644
index 00000000000..338d10ebab6
--- /dev/null
+++ b/config/src/test/kotlin/org/springframework/security/config/web/server/ServerRequestCacheDslTests.kt
@@ -0,0 +1,104 @@
+/*
+ * Copyright 2002-2020 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.server
+
+import org.junit.Rule
+import org.junit.Test
+import org.mockito.ArgumentMatchers.any
+import org.mockito.Mockito
+import org.mockito.Mockito.`when`
+import org.mockito.Mockito.verify
+import org.springframework.beans.factory.annotation.Autowired
+import org.springframework.context.ApplicationContext
+import org.springframework.context.annotation.Bean
+import org.springframework.context.annotation.Configuration
+import org.springframework.security.config.annotation.web.reactive.EnableWebFluxSecurity
+import org.springframework.security.core.userdetails.MapReactiveUserDetailsService
+import org.springframework.security.core.userdetails.User
+import org.springframework.security.config.test.SpringTestRule
+import org.springframework.security.web.server.SecurityWebFilterChain
+import org.springframework.security.web.server.savedrequest.ServerRequestCache
+import org.springframework.test.web.reactive.server.WebTestClient
+import org.springframework.web.reactive.config.EnableWebFlux
+import reactor.core.publisher.Mono
+
+/**
+ * Tests for [ServerRequestCacheDsl]
+ *
+ * @author Eleftheria Stein
+ */
+class ServerRequestCacheDslTests {
+ @Rule
+ @JvmField
+ val spring = SpringTestRule()
+
+ private lateinit var client: WebTestClient
+
+ @Autowired
+ fun setup(context: ApplicationContext) {
+ this.client = WebTestClient
+ .bindToApplicationContext(context)
+ .configureClient()
+ .build()
+ }
+
+ @Test
+ fun `GET when request cache enabled then redirected to cached page`() {
+ this.spring.register(RequestCacheConfig::class.java, UserDetailsConfig::class.java).autowire()
+ `when`(RequestCacheConfig.REQUEST_CACHE.removeMatchingRequest(any())).thenReturn(Mono.empty())
+
+ this.client.get()
+ .uri("/test")
+ .exchange()
+
+ verify(RequestCacheConfig.REQUEST_CACHE).saveRequest(any())
+ }
+
+ @EnableWebFluxSecurity
+ @EnableWebFlux
+ open class RequestCacheConfig {
+ companion object {
+ var REQUEST_CACHE: ServerRequestCache = Mockito.mock(ServerRequestCache::class.java)
+ }
+
+ @Bean
+ open fun springWebFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain {
+ return http {
+ authorizeExchange {
+ authorize(anyExchange, authenticated)
+ }
+ formLogin { }
+ requestCache {
+ requestCache = REQUEST_CACHE
+ }
+ }
+ }
+ }
+
+ @Configuration
+ open class UserDetailsConfig {
+ @Bean
+ open fun userDetailsService(): MapReactiveUserDetailsService {
+ val user = User.withDefaultPasswordEncoder()
+ .username("user")
+ .password("password")
+ .roles("USER")
+ .build()
+ return MapReactiveUserDetailsService(user)
+ }
+ }
+}
diff --git a/config/src/test/kotlin/org/springframework/security/config/web/server/ServerX509DslTests.kt b/config/src/test/kotlin/org/springframework/security/config/web/server/ServerX509DslTests.kt
new file mode 100644
index 00000000000..fa46c666da2
--- /dev/null
+++ b/config/src/test/kotlin/org/springframework/security/config/web/server/ServerX509DslTests.kt
@@ -0,0 +1,237 @@
+/*
+ * Copyright 2002-2020 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.server
+
+import org.junit.Rule
+import org.junit.Test
+import org.mockito.Mockito.`when`
+import org.mockito.Mockito.mock
+import org.springframework.beans.factory.annotation.Autowired
+import org.springframework.context.ApplicationContext
+import org.springframework.context.annotation.Bean
+import org.springframework.context.annotation.Configuration
+import org.springframework.core.io.ClassPathResource
+import org.springframework.http.client.reactive.ClientHttpConnector
+import org.springframework.http.server.reactive.ServerHttpRequestDecorator
+import org.springframework.http.server.reactive.SslInfo
+import org.springframework.lang.Nullable
+import org.springframework.security.config.annotation.web.reactive.EnableWebFluxSecurity
+import org.springframework.security.core.annotation.AuthenticationPrincipal
+import org.springframework.security.core.userdetails.MapReactiveUserDetailsService
+import org.springframework.security.core.userdetails.User
+import org.springframework.security.config.test.SpringTestRule
+import org.springframework.security.web.authentication.preauth.x509.SubjectDnX509PrincipalExtractor
+import org.springframework.security.web.server.SecurityWebFilterChain
+import org.springframework.security.web.server.authentication.ReactivePreAuthenticatedAuthenticationManager
+import org.springframework.test.web.reactive.server.MockServerConfigurer
+import org.springframework.test.web.reactive.server.WebTestClient
+import org.springframework.test.web.reactive.server.WebTestClientConfigurer
+import org.springframework.test.web.reactive.server.expectBody
+import org.springframework.web.bind.annotation.GetMapping
+import org.springframework.web.bind.annotation.RestController
+import org.springframework.web.reactive.config.EnableWebFlux
+import org.springframework.web.server.ServerWebExchange
+import org.springframework.web.server.ServerWebExchangeDecorator
+import org.springframework.web.server.WebFilter
+import org.springframework.web.server.WebFilterChain
+import org.springframework.web.server.adapter.WebHttpHandlerBuilder
+import reactor.core.publisher.Mono
+import java.security.cert.Certificate
+import java.security.cert.CertificateFactory
+import java.security.cert.X509Certificate
+
+/**
+ * Tests for [ServerX509Dsl]
+ *
+ * @author Eleftheria Stein
+ */
+class ServerX509DslTests {
+ @Rule
+ @JvmField
+ val spring = SpringTestRule()
+
+ private lateinit var client: WebTestClient
+
+ @Autowired
+ fun setup(context: ApplicationContext) {
+ this.client = WebTestClient
+ .bindToApplicationContext(context)
+ .configureClient()
+ .build()
+ }
+
+ @Test
+ fun `x509 when configured with defaults then user authenticated with expected username`() {
+ this.spring
+ .register(X509DefaultConfig::class.java, UserDetailsConfig::class.java, UsernameController::class.java)
+ .autowire()
+ val certificate = loadCert("rod.cer")
+
+ this.client
+ .mutateWith(mockX509(certificate))
+ .get()
+ .uri("/username")
+ .exchange()
+ .expectStatus().isOk
+ .expectBody().isEqualTo("rod")
+ }
+
+ @EnableWebFluxSecurity
+ @EnableWebFlux
+ open class X509DefaultConfig {
+ @Bean
+ open fun springWebFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain {
+ return http {
+ x509 { }
+ }
+ }
+ }
+
+ @Test
+ fun `x509 when principal extractor customized then custom principal extractor used`() {
+ this.spring
+ .register(PrincipalExtractorConfig::class.java, UserDetailsConfig::class.java, UsernameController::class.java)
+ .autowire()
+ val certificate = loadCert("rodatexampledotcom.cer")
+
+ this.client
+ .mutateWith(mockX509(certificate))
+ .get()
+ .uri("/username")
+ .exchange()
+ .expectStatus().isOk
+ .expectBody().isEqualTo("rod")
+ }
+
+ @EnableWebFluxSecurity
+ @EnableWebFlux
+ open class PrincipalExtractorConfig {
+ @Bean
+ open fun springWebFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain {
+ val customPrincipalExtractor = SubjectDnX509PrincipalExtractor()
+ customPrincipalExtractor.setSubjectDnRegex("CN=(.*?)@example.com(?:,|$)")
+ return http {
+ x509 {
+ principalExtractor = customPrincipalExtractor
+ }
+ }
+ }
+ }
+
+ @Test
+ fun `x509 when authentication manager customized then custom authentication manager used`() {
+ this.spring
+ .register(AuthenticationManagerConfig::class.java, UsernameController::class.java)
+ .autowire()
+ val certificate = loadCert("rod.cer")
+
+ this.client
+ .mutateWith(mockX509(certificate))
+ .get()
+ .uri("/username")
+ .exchange()
+ .expectStatus().isOk
+ .expectBody().isEqualTo("rod")
+ }
+
+ @EnableWebFluxSecurity
+ @EnableWebFlux
+ open class AuthenticationManagerConfig {
+ @Bean
+ open fun springWebFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain {
+ return http {
+ x509 {
+ authenticationManager = ReactivePreAuthenticatedAuthenticationManager(userDetailsService())
+ }
+ }
+ }
+
+ fun userDetailsService(): MapReactiveUserDetailsService {
+ val user = User.withDefaultPasswordEncoder()
+ .username("rod")
+ .password("password")
+ .roles("USER")
+ .build()
+ return MapReactiveUserDetailsService(user)
+ }
+ }
+
+ @RestController
+ class UsernameController {
+ @GetMapping("/username")
+ fun principal(@AuthenticationPrincipal user: User?): String {
+ return user!!.username
+ }
+ }
+
+ @Configuration
+ open class UserDetailsConfig {
+ @Bean
+ open fun userDetailsService(): MapReactiveUserDetailsService {
+ val user = User.withDefaultPasswordEncoder()
+ .username("rod")
+ .password("password")
+ .roles("USER")
+ .build()
+ return MapReactiveUserDetailsService(user)
+ }
+ }
+
+ private fun mockX509(certificate: X509Certificate): X509Mutator {
+ return X509Mutator(certificate)
+ }
+
+ private class X509Mutator internal constructor(private var certificate: X509Certificate) : WebTestClientConfigurer, MockServerConfigurer {
+
+ override fun afterConfigurerAdded(builder: WebTestClient.Builder,
+ @Nullable httpHandlerBuilder: WebHttpHandlerBuilder?,
+ @Nullable connector: ClientHttpConnector?) {
+ val filter = SetSslInfoWebFilter(certificate)
+ httpHandlerBuilder!!.filters { filters: MutableList -> filters.add(0, filter) }
+ }
+ }
+
+ private class SetSslInfoWebFilter(var certificate: X509Certificate) : WebFilter {
+
+ override fun filter(exchange: ServerWebExchange, chain: WebFilterChain): Mono {
+ return chain.filter(decorate(exchange))
+ }
+
+ private fun decorate(exchange: ServerWebExchange): ServerWebExchange {
+ val decorated: ServerHttpRequestDecorator = object : ServerHttpRequestDecorator(exchange.request) {
+ override fun getSslInfo(): SslInfo {
+ val sslInfo = mock(SslInfo::class.java)
+ `when`(sslInfo.sessionId).thenReturn("sessionId")
+ `when`(sslInfo.peerCertificates).thenReturn(arrayOf(certificate))
+ return sslInfo
+ }
+ }
+ return object : ServerWebExchangeDecorator(exchange) {
+ override fun getRequest(): org.springframework.http.server.reactive.ServerHttpRequest {
+ return decorated
+ }
+ }
+ }
+ }
+
+ private fun loadCert(location: String): T {
+ ClassPathResource(location).inputStream.use { inputStream ->
+ val certFactory = CertificateFactory.getInstance("X.509")
+ return certFactory.generateCertificate(inputStream) as T
+ }
+ }
+}
diff --git a/config/src/test/kotlin/org/springframework/security/config/web/server/ServerXssProtectionDslTests.kt b/config/src/test/kotlin/org/springframework/security/config/web/server/ServerXssProtectionDslTests.kt
new file mode 100644
index 00000000000..87a3bdd993a
--- /dev/null
+++ b/config/src/test/kotlin/org/springframework/security/config/web/server/ServerXssProtectionDslTests.kt
@@ -0,0 +1,98 @@
+/*
+ * Copyright 2002-2020 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.server
+
+import org.junit.Rule
+import org.junit.Test
+import org.springframework.beans.factory.annotation.Autowired
+import org.springframework.context.ApplicationContext
+import org.springframework.context.annotation.Bean
+import org.springframework.security.config.annotation.web.reactive.EnableWebFluxSecurity
+import org.springframework.security.config.test.SpringTestRule
+import org.springframework.security.web.server.SecurityWebFilterChain
+import org.springframework.security.web.server.header.XXssProtectionServerHttpHeadersWriter
+import org.springframework.test.web.reactive.server.WebTestClient
+import org.springframework.web.reactive.config.EnableWebFlux
+
+/**
+ * Tests for [ServerXssProtectionDsl]
+ *
+ * @author Eleftheria Stein
+ */
+class ServerXssProtectionDslTests {
+ @Rule
+ @JvmField
+ val spring = SpringTestRule()
+
+ private lateinit var client: WebTestClient
+
+ @Autowired
+ fun setup(context: ApplicationContext) {
+ this.client = WebTestClient
+ .bindToApplicationContext(context)
+ .configureClient()
+ .build()
+ }
+
+ @Test
+ fun `request when xss protection configured then xss header in response`() {
+ this.spring.register(XssConfig::class.java).autowire()
+
+ this.client.get()
+ .uri("/")
+ .exchange()
+ .expectHeader().valueEquals(XXssProtectionServerHttpHeadersWriter.X_XSS_PROTECTION, "1 ; mode=block")
+ }
+
+ @EnableWebFluxSecurity
+ @EnableWebFlux
+ open class XssConfig {
+ @Bean
+ open fun springWebFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain {
+ return http {
+ headers {
+ xssProtection { }
+ }
+ }
+ }
+ }
+
+ @Test
+ fun `request when xss protection disabled then no xss header in response`() {
+ this.spring.register(XssDisabledConfig::class.java).autowire()
+
+ this.client.get()
+ .uri("/")
+ .exchange()
+ .expectHeader().doesNotExist(XXssProtectionServerHttpHeadersWriter.X_XSS_PROTECTION)
+ }
+
+ @EnableWebFluxSecurity
+ @EnableWebFlux
+ open class XssDisabledConfig {
+ @Bean
+ open fun springWebFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain {
+ return http {
+ headers {
+ xssProtection {
+ disable()
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/config/src/test/kotlin/org/springframework/security/config/web/servlet/AuthorizeRequestsDslTests.kt b/config/src/test/kotlin/org/springframework/security/config/web/servlet/AuthorizeRequestsDslTests.kt
index c4508ba8b34..72c5ff57be9 100644
--- a/config/src/test/kotlin/org/springframework/security/config/web/servlet/AuthorizeRequestsDslTests.kt
+++ b/config/src/test/kotlin/org/springframework/security/config/web/servlet/AuthorizeRequestsDslTests.kt
@@ -20,6 +20,8 @@ import org.junit.Rule
import org.junit.Test
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.context.annotation.Bean
+import org.springframework.context.annotation.Configuration
+import org.springframework.http.HttpMethod
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
@@ -27,10 +29,13 @@ import org.springframework.security.config.test.SpringTestRule
import org.springframework.security.core.userdetails.User
import org.springframework.security.core.userdetails.UserDetailsService
import org.springframework.security.provisioning.InMemoryUserDetailsManager
+import org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.csrf
import org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.httpBasic
import org.springframework.security.web.util.matcher.RegexRequestMatcher
import org.springframework.test.web.servlet.MockMvc
import org.springframework.test.web.servlet.get
+import org.springframework.test.web.servlet.post
+import org.springframework.test.web.servlet.put
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders
import org.springframework.test.web.servlet.result.MockMvcResultMatchers.status
import org.springframework.web.bind.annotation.GetMapping
@@ -38,6 +43,8 @@ import org.springframework.web.bind.annotation.PathVariable
import org.springframework.web.bind.annotation.RequestMapping
import org.springframework.web.bind.annotation.RestController
import org.springframework.web.servlet.config.annotation.EnableWebMvc
+import org.springframework.web.servlet.config.annotation.PathMatchConfigurer
+import org.springframework.web.servlet.config.annotation.WebMvcConfigurer
/**
* Tests for [AuthorizeRequestsDsl]
@@ -72,12 +79,29 @@ class AuthorizeRequestsDslTests {
}
}
+ @Test
+ fun `request when allowed by regex matcher with http method then responds based on method`() {
+ this.spring.register(AuthorizeRequestsByRegexConfig::class.java).autowire()
+
+ this.mockMvc.post("/onlyPostPermitted") { with(csrf()) }
+ .andExpect {
+ status { isOk }
+ }
+
+ this.mockMvc.get("/onlyPostPermitted")
+ .andExpect {
+ status { isForbidden }
+ }
+ }
+
@EnableWebSecurity
open class AuthorizeRequestsByRegexConfig : WebSecurityConfigurerAdapter() {
override fun configure(http: HttpSecurity) {
http {
authorizeRequests {
authorize(RegexRequestMatcher("/path", null), permitAll)
+ authorize(RegexRequestMatcher("/onlyPostPermitted", "POST"), permitAll)
+ authorize(RegexRequestMatcher("/onlyPostPermitted", "GET"), denyAll)
authorize(RegexRequestMatcher(".*", null), authenticated)
}
}
@@ -88,6 +112,10 @@ class AuthorizeRequestsDslTests {
@RequestMapping("/path")
fun path() {
}
+
+ @RequestMapping("/onlyPostPermitted")
+ fun onlyPostPermitted() {
+ }
}
}
@@ -103,7 +131,7 @@ class AuthorizeRequestsDslTests {
@Test
fun `request when allowed by mvc then responds with OK`() {
- this.spring.register(AuthorizeRequestsByMvcConfig::class.java).autowire()
+ this.spring.register(AuthorizeRequestsByMvcConfig::class.java, LegacyMvcMatchingConfig::class.java).autowire()
this.mockMvc.get("/path")
.andExpect {
@@ -141,6 +169,13 @@ class AuthorizeRequestsDslTests {
}
}
+ @Configuration
+ open class LegacyMvcMatchingConfig : WebMvcConfigurer {
+ override fun configurePathMatch(configurer: PathMatchConfigurer) {
+ configurer.setUseSuffixPatternMatch(true)
+ }
+ }
+
@Test
fun `request when secured by mvc path variables then responds based on path variable value`() {
this.spring.register(MvcMatcherPathVariablesConfig::class.java).autowire()
@@ -271,4 +306,91 @@ class AuthorizeRequestsDslTests {
}
}
}
+
+ @EnableWebSecurity
+ @EnableWebMvc
+ open class AuthorizeRequestsByMvcConfigWithHttpMethod : WebSecurityConfigurerAdapter() {
+ override fun configure(http: HttpSecurity) {
+ http {
+ authorizeRequests {
+ authorize(HttpMethod.GET, "/path", permitAll)
+ authorize(HttpMethod.PUT, "/path", denyAll)
+ }
+ }
+ }
+
+ @RestController
+ internal class PathController {
+ @RequestMapping("/path")
+ fun path() {
+ }
+ }
+ }
+
+ @Test
+ fun `request when secured by mvc with http method then responds based on http method`() {
+ this.spring.register(AuthorizeRequestsByMvcConfigWithHttpMethod::class.java).autowire()
+
+ this.mockMvc.get("/path")
+ .andExpect {
+ status { isOk }
+ }
+
+ this.mockMvc.put("/path") { with(csrf()) }
+ .andExpect {
+ status { isForbidden }
+ }
+ }
+
+ @EnableWebSecurity
+ @EnableWebMvc
+ open class MvcMatcherServletPathHttpMethodConfig : WebSecurityConfigurerAdapter() {
+ override fun configure(http: HttpSecurity) {
+ http {
+ authorizeRequests {
+ authorize(HttpMethod.GET, "/path", "/spring", denyAll)
+ authorize(HttpMethod.PUT, "/path", "/spring", denyAll)
+ }
+ }
+ }
+
+ @RestController
+ internal class PathController {
+ @RequestMapping("/path")
+ fun path() {
+ }
+ }
+ }
+
+
+
+ @Test
+ fun `request when secured by mvc with servlet path and http method then responds based on path and method`() {
+ this.spring.register(MvcMatcherServletPathConfig::class.java).autowire()
+
+ this.mockMvc.perform(MockMvcRequestBuilders.get("/spring/path")
+ .with { request ->
+ request.apply {
+ servletPath = "/spring"
+ }
+ })
+ .andExpect(status().isForbidden)
+
+ this.mockMvc.perform(MockMvcRequestBuilders.put("/spring/path")
+ .with { request ->
+ request.apply {
+ servletPath = "/spring"
+ csrf()
+ }
+ })
+ .andExpect(status().isForbidden)
+
+ this.mockMvc.perform(MockMvcRequestBuilders.get("/other/path")
+ .with { request ->
+ request.apply {
+ servletPath = "/other"
+ }
+ })
+ .andExpect(status().isOk)
+ }
}
diff --git a/config/src/test/kotlin/org/springframework/security/config/web/servlet/HeadersDslTests.kt b/config/src/test/kotlin/org/springframework/security/config/web/servlet/HeadersDslTests.kt
index 7824a7fafeb..b535e36b357 100644
--- a/config/src/test/kotlin/org/springframework/security/config/web/servlet/HeadersDslTests.kt
+++ b/config/src/test/kotlin/org/springframework/security/config/web/servlet/HeadersDslTests.kt
@@ -91,4 +91,31 @@ class HeadersDslTests {
}
}
}
+
+ @Test
+ fun `request when headers disabled then no security headers are in the response`() {
+ this.spring.register(HeadersDisabledConfig::class.java).autowire()
+
+ this.mockMvc.get("/")
+ .andExpect {
+ header { doesNotExist(ContentTypeOptionsServerHttpHeadersWriter.X_CONTENT_OPTIONS) }
+ header { doesNotExist(XFrameOptionsServerHttpHeadersWriter.X_FRAME_OPTIONS) }
+ header { doesNotExist(StrictTransportSecurityServerHttpHeadersWriter.STRICT_TRANSPORT_SECURITY) }
+ header { doesNotExist(HttpHeaders.CACHE_CONTROL) }
+ header { doesNotExist(HttpHeaders.EXPIRES) }
+ header { doesNotExist(HttpHeaders.PRAGMA) }
+ header { doesNotExist(XXssProtectionServerHttpHeadersWriter.X_XSS_PROTECTION) }
+ }
+ }
+
+ @EnableWebSecurity
+ open class HeadersDisabledConfig : WebSecurityConfigurerAdapter() {
+ override fun configure(http: HttpSecurity) {
+ http {
+ headers {
+ disable()
+ }
+ }
+ }
+ }
}
diff --git a/config/src/test/kotlin/org/springframework/security/config/web/servlet/HttpSecurityDslTests.kt b/config/src/test/kotlin/org/springframework/security/config/web/servlet/HttpSecurityDslTests.kt
index 7dd3de75212..f603dff3a5b 100644
--- a/config/src/test/kotlin/org/springframework/security/config/web/servlet/HttpSecurityDslTests.kt
+++ b/config/src/test/kotlin/org/springframework/security/config/web/servlet/HttpSecurityDslTests.kt
@@ -225,7 +225,7 @@ class HttpSecurityDslTests {
val filters: List = filterChain.getFilters("/")
assertThat(filters).hasSize(1)
- assertThat(filters[0]).isExactlyInstanceOf(CustomFilterConfig.CustomFilter::class.java)
+ assertThat(filters[0]).isExactlyInstanceOf(CustomFilter::class.java)
}
@EnableWebSecurity
@@ -236,7 +236,124 @@ class HttpSecurityDslTests {
addFilterAt(CustomFilter(), UsernamePasswordAuthenticationFilter::class.java)
}
}
+ }
+
+ @Test
+ fun `HTTP security when custom filter configured with reified variant then custom filter added to filter chain`() {
+ this.spring.register(CustomFilterConfigReified::class.java).autowire()
+
+ val filterChain = spring.context.getBean(FilterChainProxy::class.java)
+ val filters: List = filterChain.getFilters("/")
+
+ assertThat(filters).hasSize(1)
+ assertThat(filters[0]).isExactlyInstanceOf(CustomFilter::class.java)
+ }
+
+ @EnableWebSecurity
+ @EnableWebMvc
+ open class CustomFilterConfigReified : WebSecurityConfigurerAdapter(true) {
+ override fun configure(http: HttpSecurity) {
+ http {
+ addFilterAt(CustomFilter())
+ }
+ }
+ }
+
+ @Test
+ fun `HTTP security when custom filter configured then custom filter added after specific filter to filter chain`() {
+ this.spring.register(CustomFilterAfterConfig::class.java).autowire()
+
+ val filterChain = spring.context.getBean(FilterChainProxy::class.java)
+ val filters: List> = filterChain.getFilters("/").map { it.javaClass }
+
+ assertThat(filters).containsSubsequence(
+ UsernamePasswordAuthenticationFilter::class.java,
+ CustomFilter::class.java
+ )
+ }
+
+ @EnableWebSecurity
+ @EnableWebMvc
+ open class CustomFilterAfterConfig : WebSecurityConfigurerAdapter() {
+ override fun configure(http: HttpSecurity) {
+ http {
+ addFilterAfter(CustomFilter(), UsernamePasswordAuthenticationFilter::class.java)
+ formLogin {}
+ }
+ }
+ }
+
+ @Test
+ fun `HTTP security when custom filter configured with reified variant then custom filter added after specific filter to filter chain`() {
+ this.spring.register(CustomFilterAfterConfigReified::class.java).autowire()
+
+ val filterChain = spring.context.getBean(FilterChainProxy::class.java)
+ val filterClasses: List> = filterChain.getFilters("/").map { it.javaClass }
+
+ assertThat(filterClasses).containsSubsequence(
+ UsernamePasswordAuthenticationFilter::class.java,
+ CustomFilter::class.java
+ )
+ }
+
+ @EnableWebSecurity
+ @EnableWebMvc
+ open class CustomFilterAfterConfigReified : WebSecurityConfigurerAdapter() {
+ override fun configure(http: HttpSecurity) {
+ http {
+ addFilterAfter(CustomFilter())
+ formLogin { }
+ }
+ }
+ }
+
+ @Test
+ fun `HTTP security when custom filter configured then custom filter added before specific filter to filter chain`() {
+ this.spring.register(CustomFilterBeforeConfig::class.java).autowire()
+
+ val filterChain = spring.context.getBean(FilterChainProxy::class.java)
+ val filters: List> = filterChain.getFilters("/").map { it.javaClass }
+
+ assertThat(filters).containsSubsequence(
+ CustomFilter::class.java,
+ UsernamePasswordAuthenticationFilter::class.java
+ )
+ }
+
+ @EnableWebSecurity
+ @EnableWebMvc
+ open class CustomFilterBeforeConfig : WebSecurityConfigurerAdapter() {
+ override fun configure(http: HttpSecurity) {
+ http {
+ addFilterBefore(CustomFilter(), UsernamePasswordAuthenticationFilter::class.java)
+ formLogin {}
+ }
+ }
+ }
+
+ @Test
+ fun `HTTP security when custom filter configured with reified variant then custom filter added before specific filter to filter chain`() {
+ this.spring.register(CustomFilterBeforeConfigReified::class.java).autowire()
+
+ val filterChain = spring.context.getBean(FilterChainProxy::class.java)
+ val filterClasses: List> = filterChain.getFilters("/").map { it.javaClass }
- class CustomFilter : UsernamePasswordAuthenticationFilter()
+ assertThat(filterClasses).containsSubsequence(
+ CustomFilter::class.java,
+ UsernamePasswordAuthenticationFilter::class.java
+ )
}
+
+ @EnableWebSecurity
+ @EnableWebMvc
+ open class CustomFilterBeforeConfigReified : WebSecurityConfigurerAdapter() {
+ override fun configure(http: HttpSecurity) {
+ http {
+ addFilterBefore(CustomFilter())
+ formLogin { }
+ }
+ }
+ }
+
+ class CustomFilter : UsernamePasswordAuthenticationFilter()
}
diff --git a/config/src/test/kotlin/org/springframework/security/config/web/servlet/session/SessionFixationDslTest.kt b/config/src/test/kotlin/org/springframework/security/config/web/servlet/session/SessionFixationDslTests.kt
similarity index 99%
rename from config/src/test/kotlin/org/springframework/security/config/web/servlet/session/SessionFixationDslTest.kt
rename to config/src/test/kotlin/org/springframework/security/config/web/servlet/session/SessionFixationDslTests.kt
index 8624d240095..e8709817174 100644
--- a/config/src/test/kotlin/org/springframework/security/config/web/servlet/session/SessionFixationDslTest.kt
+++ b/config/src/test/kotlin/org/springframework/security/config/web/servlet/session/SessionFixationDslTests.kt
@@ -40,7 +40,7 @@ import org.springframework.test.web.servlet.request.MockMvcRequestBuilders
*
* @author Eleftheria Stein
*/
-class SessionFixationDslTest {
+class SessionFixationDslTests {
@Rule
@JvmField
var spring = SpringTestRule()
diff --git a/config/src/test/resources/org/springframework/security/config/http/InterceptUrlConfigTests-MvcMatchers.xml b/config/src/test/resources/org/springframework/security/config/http/InterceptUrlConfigTests-MvcMatchers.xml
index 6e8e79b504d..9edc7c8efa2 100644
--- a/config/src/test/resources/org/springframework/security/config/http/InterceptUrlConfigTests-MvcMatchers.xml
+++ b/config/src/test/resources/org/springframework/security/config/http/InterceptUrlConfigTests-MvcMatchers.xml
@@ -32,7 +32,9 @@
-
+
+
+
diff --git a/config/src/test/resources/org/springframework/security/config/http/InterceptUrlConfigTests-MvcMatchersServletPath.xml b/config/src/test/resources/org/springframework/security/config/http/InterceptUrlConfigTests-MvcMatchersServletPath.xml
index ad9f5d9ad33..c8a4cc42e99 100644
--- a/config/src/test/resources/org/springframework/security/config/http/InterceptUrlConfigTests-MvcMatchersServletPath.xml
+++ b/config/src/test/resources/org/springframework/security/config/http/InterceptUrlConfigTests-MvcMatchersServletPath.xml
@@ -32,7 +32,9 @@
-
+
+
+
diff --git a/config/src/test/resources/org/springframework/security/config/http/MiscHttpConfigTests-RequestRejectedHandler.xml b/config/src/test/resources/org/springframework/security/config/http/MiscHttpConfigTests-RequestRejectedHandler.xml
new file mode 100644
index 00000000000..be62e9a47c5
--- /dev/null
+++ b/config/src/test/resources/org/springframework/security/config/http/MiscHttpConfigTests-RequestRejectedHandler.xml
@@ -0,0 +1,32 @@
+
+
+
+
+
+
+
+
+
+
+
diff --git a/config/src/test/resources/org/springframework/security/config/http/OAuth2ClientBeanDefinitionParserTests-AuthorizedClientArgumentResolver.xml b/config/src/test/resources/org/springframework/security/config/http/OAuth2ClientBeanDefinitionParserTests-AuthorizedClientArgumentResolver.xml
new file mode 100644
index 00000000000..bf596623672
--- /dev/null
+++ b/config/src/test/resources/org/springframework/security/config/http/OAuth2ClientBeanDefinitionParserTests-AuthorizedClientArgumentResolver.xml
@@ -0,0 +1,52 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/config/src/test/resources/org/springframework/security/config/http/OAuth2LoginBeanDefinitionParserTests-AuthorizedClientArgumentResolver.xml b/config/src/test/resources/org/springframework/security/config/http/OAuth2LoginBeanDefinitionParserTests-AuthorizedClientArgumentResolver.xml
new file mode 100644
index 00000000000..dd0afc93510
--- /dev/null
+++ b/config/src/test/resources/org/springframework/security/config/http/OAuth2LoginBeanDefinitionParserTests-AuthorizedClientArgumentResolver.xml
@@ -0,0 +1,45 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/core/src/main/java/org/springframework/security/access/expression/method/DefaultMethodSecurityExpressionHandler.java b/core/src/main/java/org/springframework/security/access/expression/method/DefaultMethodSecurityExpressionHandler.java
index 8958f6089cb..26bdbf154a2 100644
--- a/core/src/main/java/org/springframework/security/access/expression/method/DefaultMethodSecurityExpressionHandler.java
+++ b/core/src/main/java/org/springframework/security/access/expression/method/DefaultMethodSecurityExpressionHandler.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2018 the original author or authors.
+ * Copyright 2002-2020 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.
@@ -19,7 +19,9 @@
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
+import java.util.LinkedHashMap;
import java.util.List;
+import java.util.Map;
import java.util.stream.*;
import org.aopalliance.intercept.MethodInvocation;
@@ -87,10 +89,10 @@ protected MethodSecurityExpressionOperations createSecurityExpressionRoot(
}
/**
- * Filters the {@code filterTarget} object (which must be either a collection, array,
+ * Filters the {@code filterTarget} object (which must be either a collection, array, map
* or stream), by evaluating the supplied expression.
*
- * If a {@code Collection} is used, the original instance will be modified to contain
+ * If a {@code Collection} or {@code Map} is used, the original instance will be modified to contain
* the elements for which the permission expression evaluates to {@code true}. For an
* array, a new array instance will be returned.
*/
@@ -173,6 +175,32 @@ public Object filter(Object filterTarget, Expression filterExpression,
return filtered;
}
+ if (filterTarget instanceof Map) {
+ final Map, ?> map = (Map, ?>) filterTarget;
+ final Map retainMap = new LinkedHashMap(map.size());
+
+ if (debug) {
+ logger.debug("Filtering map with " + map.size() + " elements");
+ }
+
+ for (Map.Entry, ?> filterObject : map.entrySet()) {
+ rootObject.setFilterObject(filterObject);
+
+ if (ExpressionUtils.evaluateAsBoolean(filterExpression, ctx)) {
+ retainMap.put(filterObject.getKey(), filterObject.getValue());
+ }
+ }
+
+ if (debug) {
+ logger.debug("Retaining elements: " + retainMap);
+ }
+
+ map.clear();
+ map.putAll(retainMap);
+
+ return filterTarget;
+ }
+
if (filterTarget instanceof Stream) {
final Stream> original = (Stream>) filterTarget;
@@ -184,7 +212,7 @@ public Object filter(Object filterTarget, Expression filterExpression,
}
throw new IllegalArgumentException(
- "Filter target must be a collection, array, or stream type, but was "
+ "Filter target must be a collection, array, map or stream type, but was "
+ filterTarget);
}
diff --git a/core/src/main/java/org/springframework/security/authentication/AbstractUserDetailsReactiveAuthenticationManager.java b/core/src/main/java/org/springframework/security/authentication/AbstractUserDetailsReactiveAuthenticationManager.java
index d32f7e6e20c..cbc6e3b71bf 100644
--- a/core/src/main/java/org/springframework/security/authentication/AbstractUserDetailsReactiveAuthenticationManager.java
+++ b/core/src/main/java/org/springframework/security/authentication/AbstractUserDetailsReactiveAuthenticationManager.java
@@ -55,7 +55,7 @@ public abstract class AbstractUserDetailsReactiveAuthenticationManager implement
private ReactiveUserDetailsPasswordService userDetailsPasswordService;
- private Scheduler scheduler = Schedulers.newParallel("password-encoder", Schedulers.DEFAULT_POOL_SIZE, true);
+ private Scheduler scheduler = Schedulers.boundedElastic();
private UserDetailsChecker preAuthenticationChecks = user -> {
if (!user.isAccountNonLocked()) {
diff --git a/core/src/main/java/org/springframework/security/authentication/ProviderManager.java b/core/src/main/java/org/springframework/security/authentication/ProviderManager.java
index 418e4850812..959309c95e8 100644
--- a/core/src/main/java/org/springframework/security/authentication/ProviderManager.java
+++ b/core/src/main/java/org/springframework/security/authentication/ProviderManager.java
@@ -30,6 +30,7 @@
import org.springframework.security.core.CredentialsContainer;
import org.springframework.security.core.SpringSecurityMessageSource;
import org.springframework.util.Assert;
+import org.springframework.util.CollectionUtils;
/**
* Iterates an {@link Authentication} request through a list of
@@ -145,7 +146,7 @@ private void checkState() {
throw new IllegalArgumentException(
"A parent AuthenticationManager or a list "
+ "of AuthenticationProviders is required");
- } else if (providers.contains(null)) {
+ } else if (CollectionUtils.contains(providers.iterator(), null)) {
throw new IllegalArgumentException(
"providers list cannot contain null values");
}
@@ -237,7 +238,7 @@ public Authentication authenticate(Authentication authentication)
((CredentialsContainer) result).eraseCredentials();
}
- // If the parent AuthenticationManager was attempted and successful than it will publish an AuthenticationSuccessEvent
+ // If the parent AuthenticationManager was attempted and successful then it will publish an AuthenticationSuccessEvent
// This check prevents a duplicate AuthenticationSuccessEvent if the parent AuthenticationManager already published it
if (parentResult == null) {
eventPublisher.publishAuthenticationSuccess(result);
@@ -254,7 +255,7 @@ public Authentication authenticate(Authentication authentication)
"No AuthenticationProvider found for {0}"));
}
- // If the parent AuthenticationManager was attempted and failed than it will publish an AbstractAuthenticationFailureEvent
+ // If the parent AuthenticationManager was attempted and failed then it will publish an AbstractAuthenticationFailureEvent
// This check prevents a duplicate AbstractAuthenticationFailureEvent if the parent AuthenticationManager already published it
if (parentException == null) {
prepareException(lastException, authentication);
diff --git a/core/src/main/java/org/springframework/security/jackson2/SecurityJackson2Modules.java b/core/src/main/java/org/springframework/security/jackson2/SecurityJackson2Modules.java
index f10fefa8fc4..011ee95b1a7 100644
--- a/core/src/main/java/org/springframework/security/jackson2/SecurityJackson2Modules.java
+++ b/core/src/main/java/org/springframework/security/jackson2/SecurityJackson2Modules.java
@@ -90,7 +90,7 @@ public static void enableDefaultTyping(ObjectMapper mapper) {
if (mapper != null) {
TypeResolverBuilder> typeBuilder = mapper.getDeserializationConfig().getDefaultTyper(null);
if (typeBuilder == null) {
- mapper.setDefaultTyping(createWhitelistedDefaultTyping());
+ mapper.setDefaultTyping(createAllowlistedDefaultTyping());
}
}
}
@@ -148,11 +148,11 @@ private static void addToModulesList(ClassLoader loader, List modules, S
}
/**
- * Creates a TypeResolverBuilder that performs whitelisting.
- * @return a TypeResolverBuilder that performs whitelisting.
+ * Creates a TypeResolverBuilder that restricts allowed types.
+ * @return a TypeResolverBuilder that restricts allowed types.
*/
- private static TypeResolverBuilder extends TypeResolverBuilder> createWhitelistedDefaultTyping() {
- TypeResolverBuilder extends TypeResolverBuilder> result = new WhitelistTypeResolverBuilder(ObjectMapper.DefaultTyping.NON_FINAL);
+ private static TypeResolverBuilder extends TypeResolverBuilder> createAllowlistedDefaultTyping() {
+ TypeResolverBuilder extends TypeResolverBuilder> result = new AllowlistTypeResolverBuilder(ObjectMapper.DefaultTyping.NON_FINAL);
result = result.init(JsonTypeInfo.Id.CLASS, null);
result = result.inclusion(JsonTypeInfo.As.PROPERTY);
return result;
@@ -164,9 +164,9 @@ private static TypeResolverBuilder extends TypeResolverBuilder> createWhitelis
* and overrides the {@code TypeIdResolver}
* @author Rob Winch
*/
- static class WhitelistTypeResolverBuilder extends ObjectMapper.DefaultTypeResolverBuilder {
+ static class AllowlistTypeResolverBuilder extends ObjectMapper.DefaultTypeResolverBuilder {
- WhitelistTypeResolverBuilder(ObjectMapper.DefaultTyping defaultTyping) {
+ AllowlistTypeResolverBuilder(ObjectMapper.DefaultTyping defaultTyping) {
super(
defaultTyping,
//we do explicit validation in the TypeIdResolver
@@ -182,17 +182,17 @@ protected TypeIdResolver idResolver(MapperConfig> config,
PolymorphicTypeValidator subtypeValidator,
Collection subtypes, boolean forSer, boolean forDeser) {
TypeIdResolver result = super.idResolver(config, baseType, subtypeValidator, subtypes, forSer, forDeser);
- return new WhitelistTypeIdResolver(result);
+ return new AllowlistTypeIdResolver(result);
}
}
/**
* A {@link TypeIdResolver} that delegates to an existing implementation and throws an IllegalStateException if the
- * class being looked up is not whitelisted, does not provide an explicit mixin, and is not annotated with Jackson
+ * class being looked up is not in the allowlist, does not provide an explicit mixin, and is not annotated with Jackson
* mappings. See https://github.com/spring-projects/spring-security/issues/4370
*/
- static class WhitelistTypeIdResolver implements TypeIdResolver {
- private static final Set WHITELIST_CLASS_NAMES = Collections.unmodifiableSet(new HashSet(Arrays.asList(
+ static class AllowlistTypeIdResolver implements TypeIdResolver {
+ private static final Set ALLOWLIST_CLASS_NAMES = Collections.unmodifiableSet(new HashSet(Arrays.asList(
"java.util.ArrayList",
"java.util.Collections$EmptyList",
"java.util.Collections$EmptyMap",
@@ -209,7 +209,7 @@ static class WhitelistTypeIdResolver implements TypeIdResolver {
private final TypeIdResolver delegate;
- WhitelistTypeIdResolver(TypeIdResolver delegate) {
+ AllowlistTypeIdResolver(TypeIdResolver delegate) {
this.delegate = delegate;
}
@@ -238,7 +238,7 @@ public JavaType typeFromId(DatabindContext context, String id) throws IOExceptio
DeserializationConfig config = (DeserializationConfig) context.getConfig();
JavaType result = delegate.typeFromId(context, id);
String className = result.getRawClass().getName();
- if (isWhitelisted(className)) {
+ if (isInAllowlist(className)) {
return result;
}
boolean isExplicitMixin = config.findMixInClassFor(result.getRawClass()) != null;
@@ -249,14 +249,14 @@ public JavaType typeFromId(DatabindContext context, String id) throws IOExceptio
if (jacksonAnnotation != null) {
return result;
}
- throw new IllegalArgumentException("The class with " + id + " and name of " + className + " is not whitelisted. " +
+ throw new IllegalArgumentException("The class with " + id + " and name of " + className + " is not in the allowlist. " +
"If you believe this class is safe to deserialize, please provide an explicit mapping using Jackson annotations or by providing a Mixin. " +
"If the serialization is only done by a trusted source, you can also enable default typing. " +
"See https://github.com/spring-projects/spring-security/issues/4370 for details");
}
- private boolean isWhitelisted(String id) {
- return WHITELIST_CLASS_NAMES.contains(id);
+ private boolean isInAllowlist(String id) {
+ return ALLOWLIST_CLASS_NAMES.contains(id);
}
@Override
diff --git a/core/src/main/resources/org/springframework/security/messages.properties b/core/src/main/resources/org/springframework/security/messages.properties
index 665a742002e..a84f259753f 100644
--- a/core/src/main/resources/org/springframework/security/messages.properties
+++ b/core/src/main/resources/org/springframework/security/messages.properties
@@ -31,6 +31,7 @@ DigestAuthenticationFilter.usernameNotFound=Username {0} not found
JdbcDaoImpl.noAuthority=User {0} has no GrantedAuthority
JdbcDaoImpl.notFound=User {0} not found
LdapAuthenticationProvider.badCredentials=Bad credentials
+LdapAuthenticationProvider.badLdapConnection=Connection to LDAP server failed
LdapAuthenticationProvider.credentialsExpired=User credentials have expired
LdapAuthenticationProvider.disabled=User is disabled
LdapAuthenticationProvider.expired=User account has expired
diff --git a/core/src/main/resources/org/springframework/security/messages_zh_TW.properties b/core/src/main/resources/org/springframework/security/messages_zh_TW.properties
index 9050abcc696..7f8f871f1cf 100644
--- a/core/src/main/resources/org/springframework/security/messages_zh_TW.properties
+++ b/core/src/main/resources/org/springframework/security/messages_zh_TW.properties
@@ -31,6 +31,7 @@ DigestAuthenticationFilter.usernameNotFound=\u627E\u4E0D\u5230\u4F7F\u7528\u8005
JdbcDaoImpl.noAuthority=\u4F7F\u7528\u8005 {0} \u6C92\u6709 GrantedAuthority
JdbcDaoImpl.notFound=\u627E\u4E0D\u5230\u4F7F\u7528\u8005 {0}
LdapAuthenticationProvider.badCredentials=\u6191\u8B49\u932F\u8AA4
+LdapAuthenticationProvider.badLdapConnection=\u7121\u6CD5\u9023\u7DDA\u5230 LDAP \u4F3A\u670D\u5668
LdapAuthenticationProvider.credentialsExpired=\u4F7F\u7528\u8005\u6191\u8B49\u5DF2\u904E\u671F
LdapAuthenticationProvider.disabled=\u4F7F\u7528\u8005\u5DF2\u88AB\u505C\u7528
LdapAuthenticationProvider.expired=\u4F7F\u7528\u8005\u5E33\u865F\u5DF2\u904E\u671F
diff --git a/core/src/test/java/org/springframework/security/access/expression/method/DefaultMethodSecurityExpressionHandlerTests.java b/core/src/test/java/org/springframework/security/access/expression/method/DefaultMethodSecurityExpressionHandlerTests.java
index 4fca5a18976..85dd94acd48 100644
--- a/core/src/test/java/org/springframework/security/access/expression/method/DefaultMethodSecurityExpressionHandlerTests.java
+++ b/core/src/test/java/org/springframework/security/access/expression/method/DefaultMethodSecurityExpressionHandlerTests.java
@@ -15,7 +15,9 @@
*/
package org.springframework.security.access.expression.method;
+import java.util.HashMap;
import java.util.List;
+import java.util.Map;
import java.util.stream.Collectors;
import java.util.stream.Stream;
@@ -78,6 +80,72 @@ public void createEvaluationContextCustomTrustResolver() {
verify(trustResolver).isAnonymous(authentication);
}
+ @Test
+ @SuppressWarnings("unchecked")
+ public void filterByKeyWhenUsingMapThenFiltersMap() {
+ final Map map = new HashMap<>();
+ map.put("key1", "value1");
+ map.put("key2", "value2");
+ map.put("key3", "value3");
+
+ Expression expression = handler.getExpressionParser().parseExpression("filterObject.key eq 'key2'");
+
+ EvaluationContext context = handler.createEvaluationContext(authentication,
+ methodInvocation);
+
+ Object filtered = handler.filter(map, expression, context);
+
+ assertThat(filtered == map);
+ Map result = ((Map) filtered);
+ assertThat(result.size() == 1);
+ assertThat(result).containsKey("key2");
+ assertThat(result).containsValue("value2");
+ }
+
+ @Test
+ @SuppressWarnings("unchecked")
+ public void filterByValueWhenUsingMapThenFiltersMap() {
+ final Map map = new HashMap<>();
+ map.put("key1", "value1");
+ map.put("key2", "value2");
+ map.put("key3", "value3");
+
+ Expression expression = handler.getExpressionParser().parseExpression("filterObject.value eq 'value3'");
+
+ EvaluationContext context = handler.createEvaluationContext(authentication,
+ methodInvocation);
+
+ Object filtered = handler.filter(map, expression, context);
+
+ assertThat(filtered == map);
+ Map result = ((Map) filtered);
+ assertThat(result.size() == 1);
+ assertThat(result).containsKey("key3");
+ assertThat(result).containsValue("value3");
+ }
+
+ @Test
+ @SuppressWarnings("unchecked")
+ public void filterByKeyAndValueWhenUsingMapThenFiltersMap() {
+ final Map map = new HashMap<>();
+ map.put("key1", "value1");
+ map.put("key2", "value2");
+ map.put("key3", "value3");
+
+ Expression expression = handler.getExpressionParser().parseExpression("(filterObject.key eq 'key1') or (filterObject.value eq 'value2')");
+
+ EvaluationContext context = handler.createEvaluationContext(authentication,
+ methodInvocation);
+
+ Object filtered = handler.filter(map, expression, context);
+
+ assertThat(filtered == map);
+ Map result = ((Map) filtered);
+ assertThat(result.size() == 2);
+ assertThat(result).containsKeys("key1", "key2");
+ assertThat(result).containsValues("value1", "value2");
+ }
+
@Test
@SuppressWarnings("unchecked")
public void filterWhenUsingStreamThenFiltersStream() {
diff --git a/core/src/test/java/org/springframework/security/authentication/ProviderManagerTests.java b/core/src/test/java/org/springframework/security/authentication/ProviderManagerTests.java
index 8b9249b8181..a28c1c4e414 100644
--- a/core/src/test/java/org/springframework/security/authentication/ProviderManagerTests.java
+++ b/core/src/test/java/org/springframework/security/authentication/ProviderManagerTests.java
@@ -16,19 +16,27 @@
package org.springframework.security.authentication;
-import org.junit.Test;
-import org.springframework.context.MessageSource;
-import org.springframework.security.core.Authentication;
-import org.springframework.security.core.AuthenticationException;
-
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
+import org.junit.Test;
+
+import org.springframework.context.MessageSource;
+import org.springframework.security.core.Authentication;
+import org.springframework.security.core.AuthenticationException;
+
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.fail;
-import static org.mockito.Mockito.*;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoInteractions;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
+import static org.mockito.Mockito.when;
/**
* Tests {@link ProviderManager}.
@@ -102,6 +110,21 @@ public void testStartupFailsIfProvidersNotSetAsVarargs() {
new ProviderManager((AuthenticationProvider) null);
}
+ @Test(expected = IllegalArgumentException.class)
+ public void testStartupFailsIfProvidersContainNullElement() {
+ new ProviderManager(Arrays.asList(mock(AuthenticationProvider.class), null));
+ }
+
+ // gh-8689
+ @Test
+ public void constructorWhenUsingListOfThenNoException() {
+ List providers = spy(ArrayList.class);
+ // List.of(null) in JDK 9 throws a NullPointerException
+ when(providers.contains(eq(null))).thenThrow(NullPointerException.class);
+ providers.add(mock(AuthenticationProvider.class));
+ new ProviderManager(providers);
+ }
+
@Test
public void detailsAreNotSetOnAuthenticationTokenIfAlreadySetByProvider() {
Object requestDetails = "(Request Details)";
diff --git a/core/src/test/java/org/springframework/security/jackson2/SecurityJackson2ModulesTests.java b/core/src/test/java/org/springframework/security/jackson2/SecurityJackson2ModulesTests.java
index da7aac86f0e..e28aaf0ffd8 100644
--- a/core/src/test/java/org/springframework/security/jackson2/SecurityJackson2ModulesTests.java
+++ b/core/src/test/java/org/springframework/security/jackson2/SecurityJackson2ModulesTests.java
@@ -44,20 +44,20 @@ public void setup() {
}
@Test
- public void readValueWhenNotWhitelistedOrMappedThenThrowsException() {
- String content = "{\"@class\":\"org.springframework.security.jackson2.SecurityJackson2ModulesTests$NotWhitelisted\",\"property\":\"bar\"}";
+ public void readValueWhenNotAllowedOrMappedThenThrowsException() {
+ String content = "{\"@class\":\"org.springframework.security.jackson2.SecurityJackson2ModulesTests$NotAllowlisted\",\"property\":\"bar\"}";
assertThatThrownBy(() -> {
mapper.readValue(content, Object.class);
}
- ).hasStackTraceContaining("whitelisted");
+ ).hasStackTraceContaining("allowlist");
}
@Test
public void readValueWhenExplicitDefaultTypingAfterSecuritySetupThenReadsAsSpecificType() throws Exception {
mapper.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL, JsonTypeInfo.As.PROPERTY);
- String content = "{\"@class\":\"org.springframework.security.jackson2.SecurityJackson2ModulesTests$NotWhitelisted\",\"property\":\"bar\"}";
+ String content = "{\"@class\":\"org.springframework.security.jackson2.SecurityJackson2ModulesTests$NotAllowlisted\",\"property\":\"bar\"}";
- assertThat(mapper.readValue(content, Object.class)).isInstanceOf(NotWhitelisted.class);
+ assertThat(mapper.readValue(content, Object.class)).isInstanceOf(NotAllowlisted.class);
}
@Test
@@ -65,29 +65,29 @@ public void readValueWhenExplicitDefaultTypingBeforeSecuritySetupThenReadsAsSpec
mapper = new ObjectMapper();
mapper.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL, JsonTypeInfo.As.PROPERTY);
SecurityJackson2Modules.enableDefaultTyping(mapper);
- String content = "{\"@class\":\"org.springframework.security.jackson2.SecurityJackson2ModulesTests$NotWhitelisted\",\"property\":\"bar\"}";
+ String content = "{\"@class\":\"org.springframework.security.jackson2.SecurityJackson2ModulesTests$NotAllowlisted\",\"property\":\"bar\"}";
- assertThat(mapper.readValue(content, Object.class)).isInstanceOf(NotWhitelisted.class);
+ assertThat(mapper.readValue(content, Object.class)).isInstanceOf(NotAllowlisted.class);
}
@Test
public void readValueWhenAnnotatedThenReadsAsSpecificType() throws Exception {
- String content = "{\"@class\":\"org.springframework.security.jackson2.SecurityJackson2ModulesTests$NotWhitelistedButAnnotated\",\"property\":\"bar\"}";
+ String content = "{\"@class\":\"org.springframework.security.jackson2.SecurityJackson2ModulesTests$NotAllowlistedButAnnotated\",\"property\":\"bar\"}";
- assertThat(mapper.readValue(content, Object.class)).isInstanceOf(NotWhitelistedButAnnotated.class);
+ assertThat(mapper.readValue(content, Object.class)).isInstanceOf(NotAllowlistedButAnnotated.class);
}
@Test
public void readValueWhenMixinProvidedThenReadsAsSpecificType() throws Exception {
- mapper.addMixIn(NotWhitelisted.class, NotWhitelistedMixin.class);
- String content = "{\"@class\":\"org.springframework.security.jackson2.SecurityJackson2ModulesTests$NotWhitelisted\",\"property\":\"bar\"}";
+ mapper.addMixIn(NotAllowlisted.class, NotAllowlistedMixin.class);
+ String content = "{\"@class\":\"org.springframework.security.jackson2.SecurityJackson2ModulesTests$NotAllowlisted\",\"property\":\"bar\"}";
- assertThat(mapper.readValue(content, Object.class)).isInstanceOf(NotWhitelisted.class);
+ assertThat(mapper.readValue(content, Object.class)).isInstanceOf(NotAllowlisted.class);
}
@Test
public void readValueWhenHashMapThenReadsAsSpecificType() throws Exception {
- mapper.addMixIn(NotWhitelisted.class, NotWhitelistedMixin.class);
+ mapper.addMixIn(NotAllowlisted.class, NotAllowlistedMixin.class);
String content = "{\"@class\":\"java.util.HashMap\"}";
assertThat(mapper.readValue(content, Object.class)).isInstanceOf(HashMap.class);
@@ -99,7 +99,7 @@ public void readValueWhenHashMapThenReadsAsSpecificType() throws Exception {
public @interface NotJacksonAnnotation {}
@NotJacksonAnnotation
- static class NotWhitelisted {
+ static class NotAllowlisted {
private String property = "bar";
public String getProperty() {
@@ -111,7 +111,7 @@ public void setProperty(String property) {
}
@JsonIgnoreType(false)
- static class NotWhitelistedButAnnotated {
+ static class NotAllowlistedButAnnotated {
private String property = "bar";
public String getProperty() {
@@ -126,7 +126,7 @@ public void setProperty(String property) {
@JsonAutoDetect(fieldVisibility = JsonAutoDetect.Visibility.ANY, getterVisibility = JsonAutoDetect.Visibility.NONE,
isGetterVisibility = JsonAutoDetect.Visibility.NONE)
@JsonIgnoreProperties(ignoreUnknown = true)
- abstract class NotWhitelistedMixin {
+ abstract class NotAllowlistedMixin {
}
}
diff --git a/crypto/src/main/java/org/springframework/security/crypto/bcrypt/BCryptPasswordEncoder.java b/crypto/src/main/java/org/springframework/security/crypto/bcrypt/BCryptPasswordEncoder.java
index c59246320d6..dd787a9ea42 100644
--- a/crypto/src/main/java/org/springframework/security/crypto/bcrypt/BCryptPasswordEncoder.java
+++ b/crypto/src/main/java/org/springframework/security/crypto/bcrypt/BCryptPasswordEncoder.java
@@ -99,6 +99,10 @@ public BCryptPasswordEncoder(BCryptVersion version, int strength, SecureRandom r
}
public String encode(CharSequence rawPassword) {
+ if (rawPassword == null) {
+ throw new IllegalArgumentException("rawPassword cannot be null");
+ }
+
String salt;
if (random != null) {
salt = BCrypt.gensalt(version.getVersion(), strength, random);
@@ -109,6 +113,10 @@ public String encode(CharSequence rawPassword) {
}
public boolean matches(CharSequence rawPassword, String encodedPassword) {
+ if (rawPassword == null) {
+ throw new IllegalArgumentException("rawPassword cannot be null");
+ }
+
if (encodedPassword == null || encodedPassword.length() == 0) {
logger.warn("Empty encoded password");
return false;
diff --git a/crypto/src/main/java/org/springframework/security/crypto/encrypt/AesBytesEncryptor.java b/crypto/src/main/java/org/springframework/security/crypto/encrypt/AesBytesEncryptor.java
index 2d0008e0a13..95c7b4067fe 100644
--- a/crypto/src/main/java/org/springframework/security/crypto/encrypt/AesBytesEncryptor.java
+++ b/crypto/src/main/java/org/springframework/security/crypto/encrypt/AesBytesEncryptor.java
@@ -36,7 +36,7 @@
import org.springframework.security.crypto.keygen.KeyGenerators;
/**
- * Encryptor that uses 256-bit AES encryption.
+ * Encryptor that uses AES encryption.
*
* @author Keith Donald
* @author Dave Syer
@@ -99,9 +99,19 @@ public AesBytesEncryptor(String password, CharSequence salt,
public AesBytesEncryptor(String password, CharSequence salt,
BytesKeyGenerator ivGenerator, CipherAlgorithm alg) {
- PBEKeySpec keySpec = new PBEKeySpec(password.toCharArray(), Hex.decode(salt),
- 1024, 256);
- SecretKey secretKey = newSecretKey("PBKDF2WithHmacSHA1", keySpec);
+ this(newSecretKey("PBKDF2WithHmacSHA1", new PBEKeySpec(password.toCharArray(), Hex.decode(salt),
+ 1024, 256)), ivGenerator, alg);
+ }
+
+ /**
+ * Constructs an encryptor that uses AES encryption.
+ *
+ * @param secretKey the secret (symmetric) key
+ * @param ivGenerator the generator used to generate the initialization vector. If null,
+ * then a default algorithm will be used based on the provided {@link CipherAlgorithm}
+ * @param alg the {@link CipherAlgorithm} to be used
+ */
+ public AesBytesEncryptor(SecretKey secretKey, BytesKeyGenerator ivGenerator, CipherAlgorithm alg) {
this.secretKey = new SecretKeySpec(secretKey.getEncoded(), "AES");
this.alg = alg;
this.encryptor = alg.createCipher();
diff --git a/crypto/src/main/java/org/springframework/security/crypto/encrypt/Encryptors.java b/crypto/src/main/java/org/springframework/security/crypto/encrypt/Encryptors.java
index 7e0c5e8295e..7ebfb5a356d 100644
--- a/crypto/src/main/java/org/springframework/security/crypto/encrypt/Encryptors.java
+++ b/crypto/src/main/java/org/springframework/security/crypto/encrypt/Encryptors.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2011-2016 the original author or authors.
+ * Copyright 2011-2020 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.
@@ -32,16 +32,13 @@ public class Encryptors {
* (Password-Based Key Derivation Function #2). Salts the password to prevent
* dictionary attacks against the key. The provided salt is expected to be
* hex-encoded; it should be random and at least 8 bytes in length. Also applies a
- * random 16 byte initialization vector to ensure each encrypted message will be
+ * random 16-byte initialization vector to ensure each encrypted message will be
* unique. Requires Java 6.
*
* @param password the password used to generate the encryptor's secret key; should
* not be shared
* @param salt a hex-encoded, random, site-global salt value to use to generate the
* key
- *
- * @see #standard(CharSequence, CharSequence) which uses the slightly weaker CBC mode
- * (instead of GCM)
*/
public static BytesEncryptor stronger(CharSequence password, CharSequence salt) {
return new AesBytesEncryptor(password.toString(), salt,
@@ -53,13 +50,21 @@ public static BytesEncryptor stronger(CharSequence password, CharSequence salt)
* Derives the secret key using PKCS #5's PBKDF2 (Password-Based Key Derivation
* Function #2). Salts the password to prevent dictionary attacks against the key. The
* provided salt is expected to be hex-encoded; it should be random and at least 8
- * bytes in length. Also applies a random 16 byte initialization vector to ensure each
+ * bytes in length. Also applies a random 16-byte initialization vector to ensure each
* encrypted message will be unique. Requires Java 6.
+ * NOTE: This mode is not
+ * authenticated
+ * and does not provide any guarantees about the authenticity of the data.
+ * For a more secure alternative, users should prefer
+ * {@link #stronger(CharSequence, CharSequence)}.
*
* @param password the password used to generate the encryptor's secret key; should
* not be shared
* @param salt a hex-encoded, random, site-global salt value to use to generate the
* key
+ *
+ * @see #stronger(CharSequence, CharSequence), which uses the significatly more secure
+ * GCM (instead of CBC)
*/
public static BytesEncryptor standard(CharSequence password, CharSequence salt) {
return new AesBytesEncryptor(password.toString(), salt,
@@ -100,7 +105,10 @@ public static TextEncryptor text(CharSequence password, CharSequence salt) {
* not be shared
* @param salt a hex-encoded, random, site-global salt value to use to generate the
* secret key
+ * @deprecated This encryptor is not secure. Instead, look to your data store for a
+ * mechanism to query encrypted data.
*/
+ @Deprecated
public static TextEncryptor queryableText(CharSequence password, CharSequence salt) {
return new HexEncodingTextEncryptor(new AesBytesEncryptor(password.toString(),
salt));
diff --git a/crypto/src/main/java/org/springframework/security/crypto/password/Md4PasswordEncoder.java b/crypto/src/main/java/org/springframework/security/crypto/password/Md4PasswordEncoder.java
index a0110e86124..7fe06a979de 100644
--- a/crypto/src/main/java/org/springframework/security/crypto/password/Md4PasswordEncoder.java
+++ b/crypto/src/main/java/org/springframework/security/crypto/password/Md4PasswordEncoder.java
@@ -83,8 +83,6 @@ public class Md4PasswordEncoder implements PasswordEncoder {
private StringKeyGenerator saltGenerator = new Base64StringKeyGenerator();
private boolean encodeHashAsBase64;
- private Digester digester;
-
public void setEncodeHashAsBase64(boolean encodeHashAsBase64) {
this.encodeHashAsBase64 = encodeHashAsBase64;
diff --git a/crypto/src/main/java/org/springframework/security/crypto/password/NoOpPasswordEncoder.java b/crypto/src/main/java/org/springframework/security/crypto/password/NoOpPasswordEncoder.java
index 907eb7c718e..3c28e167419 100644
--- a/crypto/src/main/java/org/springframework/security/crypto/password/NoOpPasswordEncoder.java
+++ b/crypto/src/main/java/org/springframework/security/crypto/password/NoOpPasswordEncoder.java
@@ -26,7 +26,8 @@
* @deprecated This PasswordEncoder is not secure. Instead use an
* adaptive one way function like BCryptPasswordEncoder, Pbkdf2PasswordEncoder, or
* SCryptPasswordEncoder. Even better use {@link DelegatingPasswordEncoder} which supports
- * password upgrades.
+ * password upgrades. There are no plans to remove this support. It is deprecated to indicate that
+ * this is a legacy implementation and using it is considered insecure.
*/
@Deprecated
public final class NoOpPasswordEncoder implements PasswordEncoder {
diff --git a/crypto/src/test/java/org/springframework/security/crypto/bcrypt/BCryptPasswordEncoderTests.java b/crypto/src/test/java/org/springframework/security/crypto/bcrypt/BCryptPasswordEncoderTests.java
index 28ac723bce6..1ae357f0193 100644
--- a/crypto/src/test/java/org/springframework/security/crypto/bcrypt/BCryptPasswordEncoderTests.java
+++ b/crypto/src/test/java/org/springframework/security/crypto/bcrypt/BCryptPasswordEncoderTests.java
@@ -200,4 +200,16 @@ public void upgradeFromNonBCrypt() {
encoder.upgradeEncoding("not-a-bcrypt-password");
}
+ @Test(expected = IllegalArgumentException.class)
+ public void encodeNullRawPassword() {
+ BCryptPasswordEncoder encoder = new BCryptPasswordEncoder();
+ encoder.encode(null);
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void matchNullRawPassword() {
+ BCryptPasswordEncoder encoder = new BCryptPasswordEncoder();
+ encoder.matches(null, "does-not-matter");
+ }
+
}
diff --git a/crypto/src/test/java/org/springframework/security/crypto/encrypt/AesBytesEncryptorTests.java b/crypto/src/test/java/org/springframework/security/crypto/encrypt/AesBytesEncryptorTests.java
index f16928eb478..ce95884e9db 100644
--- a/crypto/src/test/java/org/springframework/security/crypto/encrypt/AesBytesEncryptorTests.java
+++ b/crypto/src/test/java/org/springframework/security/crypto/encrypt/AesBytesEncryptorTests.java
@@ -22,10 +22,15 @@
import org.springframework.security.crypto.codec.Hex;
import org.springframework.security.crypto.keygen.BytesKeyGenerator;
+import javax.crypto.SecretKey;
+import javax.crypto.spec.PBEKeySpec;
+
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
import static org.springframework.security.crypto.encrypt.AesBytesEncryptor.CipherAlgorithm.GCM;
+import static org.springframework.security.crypto.encrypt.CipherUtils.newSecretKey;
+import static org.springframework.security.crypto.password.Pbkdf2PasswordEncoder.SecretKeyFactoryAlgorithm.PBKDF2WithHmacSHA1;
/**
* Tests for {@link AesBytesEncryptor}
@@ -69,6 +74,23 @@ public void roundtripWhenUsingDefaultCipherThenEncryptsAndDecrypts() {
public void roundtripWhenUsingGcmThenEncryptsAndDecrypts() {
CryptoAssumptions.assumeGCMJCE();
AesBytesEncryptor encryptor = new AesBytesEncryptor(this.password, this.hexSalt, this.generator, GCM);
+
+ byte[] encryption = encryptor.encrypt(this.secret.getBytes());
+ assertThat(new String(Hex.encode(encryption)))
+ .isEqualTo("4b0febebd439db7ca77153cb254520c3e4d61ae38207b4e42b820d311dc3d4e0e2f37ed5ee");
+
+ byte[] decryption = encryptor.decrypt(encryption);
+ assertThat(new String(decryption)).isEqualTo(this.secret);
+ }
+
+ @Test
+ public void roundtripWhenUsingSecretKeyThenEncryptsAndDecrypts() {
+ CryptoAssumptions.assumeGCMJCE();
+ PBEKeySpec keySpec = new PBEKeySpec(this.password.toCharArray(), Hex.decode(this.hexSalt),
+ 1024, 256);
+ SecretKey secretKey = newSecretKey(PBKDF2WithHmacSHA1.name(), keySpec);
+ AesBytesEncryptor encryptor = new AesBytesEncryptor(secretKey, this.generator, GCM);
+
byte[] encryption = encryptor.encrypt(this.secret.getBytes());
assertThat(new String(Hex.encode(encryption)))
.isEqualTo("4b0febebd439db7ca77153cb254520c3e4d61ae38207b4e42b820d311dc3d4e0e2f37ed5ee");
diff --git a/docs/articles/src/docbook/codebase-structure.xml b/docs/articles/src/docbook/codebase-structure.xml
index bd2e06999c7..5d55ba1db5b 100644
--- a/docs/articles/src/docbook/codebase-structure.xml
+++ b/docs/articles/src/docbook/codebase-structure.xml
@@ -94,7 +94,7 @@
spring-security-core
- Core authentication and access-contol classes and interfaces.
+ Core authentication and access-control classes and interfaces.
Remoting support and basic provisioning APIs.
Required by any application which uses Spring Security.
Supports standalone applications, remote clients, method
@@ -146,7 +146,7 @@
spring-security-openid
OpenID web authentication support.
If you need to authenticate users against an external OpenID
- server.
+ server. (Deprecated)
org.springframework.security.openid
@@ -174,7 +174,7 @@
The core package and sub packages contain the basic classes and
interfaces which are used throughout the framework and the other two main packages
within the core jar are authentication and
- access . The access package containst
+ access . The access package contains
access-control/authorization code such as the
AccessDecisionManager and related voter-based
implementations, the interception and method security infrastructure, annotation
diff --git a/docs/guides/src/docs/asciidoc/form-javaconfig.asc b/docs/guides/src/docs/asciidoc/form-javaconfig.asc
index 845185f6330..c8281e9b861 100644
--- a/docs/guides/src/docs/asciidoc/form-javaconfig.asc
+++ b/docs/guides/src/docs/asciidoc/form-javaconfig.asc
@@ -199,7 +199,7 @@ Our existing configuration means that all we need to do is create a *login.html*
IMPORTANT: Do not display details about why authentication failed. For example, we do not want to display that the user does not exist as this will tell an attacker that they should try a different username.
-TIP: We use Thymeleaf to automatically add the CSRF token to our form. If we were not using Thymleaf or Spring MVCs taglib we could also manually add the CSRF token using ` `.
+TIP: We use Thymeleaf to automatically add the CSRF token to our form. If we were not using Thymeleaf or Spring MVCs taglib we could also manually add the CSRF token using ` `.
Start up the server and try visiting http://localhost:8080/sample/ to see the updates to our configuration. We now see our login page, but it does not look very pretty. The issue is that we have not granted access to the css files.
diff --git a/docs/guides/src/docs/asciidoc/helloworld-boot.asc b/docs/guides/src/docs/asciidoc/helloworld-boot.asc
index 7cec963d941..cc75981d58e 100644
--- a/docs/guides/src/docs/asciidoc/helloworld-boot.asc
+++ b/docs/guides/src/docs/asciidoc/helloworld-boot.asc
@@ -66,7 +66,7 @@ in order to utilize the _sec:authentication_ and _sec:authorize_ attributes.
<3> Displays the authorities of the currently authenticated principal.
<4> The logout form.
-TIP: Thymeleaf will automatically add the CSRF token to our logout form. If we were not using Thymleaf or Spring MVCs taglib we could also manually add the CSRF token using ` `.
+TIP: Thymeleaf will automatically add the CSRF token to our logout form. If we were not using Thymeleaf or Spring MVCs taglib we could also manually add the CSRF token using ` `.
==== Update the _secured_ page
diff --git a/docs/manual/src/docs/asciidoc/_includes/about/authentication/password-storage.adoc b/docs/manual/src/docs/asciidoc/_includes/about/authentication/password-storage.adoc
index 018fa0dc160..7770eb6b5a1 100644
--- a/docs/manual/src/docs/asciidoc/_includes/about/authentication/password-storage.adoc
+++ b/docs/manual/src/docs/asciidoc/_includes/about/authentication/password-storage.adoc
@@ -68,18 +68,26 @@ You can easily construct an instance of `DelegatingPasswordEncoder` using `Pass
.Create Default DelegatingPasswordEncoder
====
-[source,java]
+.Java
+[source,java,role="primary"]
----
PasswordEncoder passwordEncoder =
PasswordEncoderFactories.createDelegatingPasswordEncoder();
----
+
+.Kotlin
+[source,kotlin,role="secondary"]
+----
+val passwordEncoder: PasswordEncoder = PasswordEncoderFactories.createDelegatingPasswordEncoder()
+----
====
Alternatively, you may create your own custom instance. For example:
.Create Custom DelegatingPasswordEncoder
====
-[source,java]
+.Java
+[source,java,role="primary"]
----
String idForEncode = "bcrypt";
Map encoders = new HashMap<>();
@@ -92,6 +100,20 @@ encoders.put("sha256", new StandardPasswordEncoder());
PasswordEncoder passwordEncoder =
new DelegatingPasswordEncoder(idForEncode, encoders);
----
+
+.Kotlin
+[source,kotlin,role="secondary"]
+----
+val idForEncode = "bcrypt"
+val encoders: MutableMap = mutableMapOf()
+encoders[idForEncode] = BCryptPasswordEncoder()
+encoders["noop"] = NoOpPasswordEncoder.getInstance()
+encoders["pbkdf2"] = Pbkdf2PasswordEncoder()
+encoders["scrypt"] = SCryptPasswordEncoder()
+encoders["sha256"] = StandardPasswordEncoder()
+
+val passwordEncoder: PasswordEncoder = DelegatingPasswordEncoder(idForEncode, encoders)
+----
====
[[authentication-password-storage-dpe-format]]
@@ -180,7 +202,8 @@ There are convenience mechanisms to make this easier, but this is still not inte
.withDefaultPasswordEncoder Example
====
-[source,java,attrs="-attributes"]
+.Java
+[source,java,role="primary",attrs="-attributes"]
----
User user = User.withDefaultPasswordEncoder()
.username("user")
@@ -190,13 +213,26 @@ User user = User.withDefaultPasswordEncoder()
System.out.println(user.getPassword());
// {bcrypt}$2a$10$dXJ3SW6G7P50lGmMkkmwe.20cQQubK3.HZWzG3YB1tlRy.fqvM/BG
----
+
+.Kotlin
+[source,kotlin,role="secondary",attrs="-attributes"]
+----
+val user = User.withDefaultPasswordEncoder()
+ .username("user")
+ .password("password")
+ .roles("user")
+ .build()
+println(user.password)
+// {bcrypt}$2a$10$dXJ3SW6G7P50lGmMkkmwe.20cQQubK3.HZWzG3YB1tlRy.fqvM/BG
+----
====
If you are creating multiple users, you can also reuse the builder.
.withDefaultPasswordEncoder Reusing the Builder
====
-[source,java]
+.Java
+[source,java,role="primary"]
----
UserBuilder users = User.withDefaultPasswordEncoder();
User user = users
@@ -210,6 +246,22 @@ User admin = users
.roles("USER","ADMIN")
.build();
----
+
+.Kotlin
+[source,kotlin,role="secondary"]
+----
+val users = User.withDefaultPasswordEncoder()
+val user = users
+ .username("user")
+ .password("password")
+ .roles("USER")
+ .build()
+val admin = users
+ .username("admin")
+ .password("password")
+ .roles("USER", "ADMIN")
+ .build()
+----
====
This does hash the password that is stored, but the passwords are still exposed in memory and in the compiled source code.
@@ -272,8 +324,13 @@ https://docs.spring.io/spring-security/site/docs/5.0.x/api/org/springframework/s
The `BCryptPasswordEncoder` implementation uses the widely supported https://en.wikipedia.org/wiki/Bcrypt[bcrypt] algorithm to hash the passwords.
In order to make it more resistent to password cracking, bcrypt is deliberately slow.
Like other adaptive one-way functions, it should be tuned to take about 1 second to verify a password on your system.
+The default implementation of `BCryptPasswordEncoder` uses strength 10 as mentioned in the Javadoc of https://docs.spring.io/spring-security/site/docs/current/api/org/springframework/security/crypto/bcrypt/BCryptPasswordEncoder.html[BCryptPasswordEncoder]. You are encouraged to
+tune and test the strength parameter on your own system so that it takes roughly 1 second to verify a password.
-[source,java]
+.BCryptPasswordEncoder
+====
+.Java
+[source,java,role="primary"]
----
// Create an encoder with strength 16
BCryptPasswordEncoder encoder = new BCryptPasswordEncoder(16);
@@ -281,6 +338,16 @@ String result = encoder.encode("myPassword");
assertTrue(encoder.matches("myPassword", result));
----
+.Kotlin
+[source,kotlin,role="secondary"]
+----
+// Create an encoder with strength 16
+val encoder = BCryptPasswordEncoder(16)
+val result: String = encoder.encode("myPassword")
+assertTrue(encoder.matches("myPassword", result))
+----
+====
+
[[authentication-password-storage-argon2]]
== Argon2PasswordEncoder
@@ -288,9 +355,12 @@ The `Argon2PasswordEncoder` implementation uses the https://en.wikipedia.org/wik
Argon2 is the winner of the https://en.wikipedia.org/wiki/Password_Hashing_Competition[Password Hashing Competition].
In order to defeat password cracking on custom hardware, Argon2 is a deliberately slow algorithm that requires large amounts of memory.
Like other adaptive one-way functions, it should be tuned to take about 1 second to verify a password on your system.
-The current implementation if the `Argon2PasswordEncoder` requires BouncyCastle.
+The current implementation of the `Argon2PasswordEncoder` requires BouncyCastle.
-[source,java]
+.Argon2PasswordEncoder
+====
+.Java
+[source,java,role="primary"]
----
// Create an encoder with all the defaults
Argon2PasswordEncoder encoder = new Argon2PasswordEncoder();
@@ -298,6 +368,16 @@ String result = encoder.encode("myPassword");
assertTrue(encoder.matches("myPassword", result));
----
+.Kotlin
+[source,kotlin,role="secondary"]
+----
+// Create an encoder with all the defaults
+val encoder = Argon2PasswordEncoder()
+val result: String = encoder.encode("myPassword")
+assertTrue(encoder.matches("myPassword", result))
+----
+====
+
[[authentication-password-storage-pbkdf2]]
== Pbkdf2PasswordEncoder
@@ -306,7 +386,10 @@ In order to defeat password cracking PBKDF2 is a deliberately slow algorithm.
Like other adaptive one-way functions, it should be tuned to take about 1 second to verify a password on your system.
This algorithm is a good choice when FIPS certification is required.
-[source,java]
+.Pbkdf2PasswordEncoder
+====
+.Java
+[source,java,role="primary"]
----
// Create an encoder with all the defaults
Pbkdf2PasswordEncoder encoder = new Pbkdf2PasswordEncoder();
@@ -314,6 +397,16 @@ String result = encoder.encode("myPassword");
assertTrue(encoder.matches("myPassword", result));
----
+.Kotlin
+[source,kotlin,role="secondary"]
+----
+// Create an encoder with all the defaults
+val encoder = Pbkdf2PasswordEncoder()
+val result: String = encoder.encode("myPassword")
+assertTrue(encoder.matches("myPassword", result))
+----
+====
+
[[authentication-password-storage-scrypt]]
== SCryptPasswordEncoder
@@ -321,7 +414,10 @@ The `SCryptPasswordEncoder` implementation uses https://en.wikipedia.org/wiki/Sc
In order to defeat password cracking on custom hardware scrypt is a deliberately slow algorithm that requires large amounts of memory.
Like other adaptive one-way functions, it should be tuned to take about 1 second to verify a password on your system.
-[source,java]
+.SCryptPasswordEncoder
+====
+.Java
+[source,java,role="primary"]
----
// Create an encoder with all the defaults
SCryptPasswordEncoder encoder = new SCryptPasswordEncoder();
@@ -329,6 +425,16 @@ String result = encoder.encode("myPassword");
assertTrue(encoder.matches("myPassword", result));
----
+.Kotlin
+[source,kotlin,role="secondary"]
+----
+// Create an encoder with all the defaults
+val encoder = SCryptPasswordEncoder()
+val result: String = encoder.encode("myPassword")
+assertTrue(encoder.matches("myPassword", result))
+----
+====
+
[[authentication-password-storage-other]]
== Other PasswordEncoders
diff --git a/docs/manual/src/docs/asciidoc/_includes/about/exploits/csrf.adoc b/docs/manual/src/docs/asciidoc/_includes/about/exploits/csrf.adoc
index 647aaa6a8e4..1b73157804f 100644
--- a/docs/manual/src/docs/asciidoc/_includes/about/exploits/csrf.adoc
+++ b/docs/manual/src/docs/asciidoc/_includes/about/exploits/csrf.adoc
@@ -378,7 +378,7 @@ The first option is to include the actual CSRF token in the body of the request.
By placing the CSRF token in the body, the body will be read before authorization is performed.
This means that anyone can place temporary files on your server.
However, only authorized users will be able to submit a File that is processed by your application.
-In general, this is the recommended approach because the temporary file uplaod should have a negligible impact on most servers.
+In general, this is the recommended approach because the temporary file upload should have a negligible impact on most servers.
[[csrf-considerations-multipart-url]]
==== Include CSRF Token in URL
diff --git a/docs/manual/src/docs/asciidoc/_includes/about/exploits/headers.adoc b/docs/manual/src/docs/asciidoc/_includes/about/exploits/headers.adoc
index 32abe9a31ad..f11a5c1364e 100644
--- a/docs/manual/src/docs/asciidoc/_includes/about/exploits/headers.adoc
+++ b/docs/manual/src/docs/asciidoc/_includes/about/exploits/headers.adoc
@@ -72,7 +72,7 @@ Expires: 0
====
In order to be secure by default, Spring Security adds these headers by default.
-However, if your application provides it's own cache control headers Spring Security will back out of the way.
+However, if your application provides its own cache control headers Spring Security will back out of the way.
This allows for applications to ensure that static resources like CSS and JavaScript can be cached.
@@ -119,7 +119,7 @@ Refer to the relevant sections to see how to customize the defaults for both < {
+ val csrfToken: Mono? = exchange.getAttribute(CsrfToken::class.java.name)
+ return csrfToken!!.doOnSuccess { token ->
+ exchange.attributes[CsrfRequestDataValueProcessor.DEFAULT_CSRF_ATTR_NAME] = token
+ }
+ }
+}
+----
====
Fortunately, Thymeleaf provides <> that works without any additional work.
@@ -130,7 +176,7 @@ Next we will discuss various ways of including the CSRF token in a form as a hid
Spring Security's CSRF support provides integration with Spring's https://docs.spring.io/spring/docs/current/javadoc-api/org/springframework/web/reactive/result/view/RequestDataValueProcessor.html[RequestDataValueProcessor] via its https://docs.spring.io/spring-security/site/docs/current/api/org/springframework/security/web/reactive/result/view/CsrfRequestDataValueProcessor.html[CsrfRequestDataValueProcessor].
In order for `CsrfRequestDataValueProcessor` to work, the `Mono` must be subscribed to and the `CsrfToken` must be <> that matches https://docs.spring.io/spring-security/site/docs/current/api/org/springframework/security/web/reactive/result/view/CsrfRequestDataValueProcessor.html#DEFAULT_CSRF_ATTR_NAME[DEFAULT_CSRF_ATTR_NAME].
-Fortunately, Thymleaf https://www.thymeleaf.org/doc/tutorials/2.1/thymeleafspring.html#integration-with-requestdatavalueprocessor[provides support] to take care of all the boilerplate for you by integrating with `RequestDataValueProcessor` to ensure that forms that have an unsafe HTTP method (i.e. post) will automatically include the actual CSRF token.
+Fortunately, Thymeleaf https://www.thymeleaf.org/doc/tutorials/2.1/thymeleafspring.html#integration-with-requestdatavalueprocessor[provides support] to take care of all the boilerplate for you by integrating with `RequestDataValueProcessor` to ensure that forms that have an unsafe HTTP method (i.e. post) will automatically include the actual CSRF token.
[[webflux-csrf-include-form-attr]]
===== CsrfToken Request Attribute
@@ -253,7 +299,8 @@ For example, the following Java Configuration will perform logout with the URL `
.Log out with HTTP GET
====
-[source,java]
+.Java
+[source,java,role="primary"]
----
@Bean
public SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) {
@@ -262,7 +309,20 @@ public SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http)
.logout(logout -> logout.requiresLogout(new PathPatternParserServerWebExchangeMatcher("/logout")))
return http.build();
}
+----
+.Kotlin
+[source,kotlin,role="secondary"]
+----
+@Bean
+fun springSecurityFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain {
+ return http {
+ // ...
+ logout {
+ requiresLogout = PathPatternParserServerWebExchangeMatcher("/logout")
+ }
+ }
+}
----
====
@@ -301,7 +361,8 @@ In a WebFlux application, this can be configured with the following configuratio
.Enable obtaining CSRF token from multipart/form-data
====
-[source,java]
+.Java
+[source,java,role="primary"]
----
@Bean
public SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) {
@@ -310,7 +371,20 @@ public SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http)
.csrf(csrf -> csrf.tokenFromMultipartDataEnabled(true))
return http.build();
}
+----
+.Kotlin
+[source,kotlin,role="secondary"]
+----
+@Bean
+fun springSecurityFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain {
+ return http {
+ // ...
+ csrf {
+ tokenFromMultipartDataEnabled = true
+ }
+ }
+}
----
====
diff --git a/docs/manual/src/docs/asciidoc/_includes/reactive/oauth2/login.adoc b/docs/manual/src/docs/asciidoc/_includes/reactive/oauth2/login.adoc
index fbc1be05c84..18478b91901 100644
--- a/docs/manual/src/docs/asciidoc/_includes/reactive/oauth2/login.adoc
+++ b/docs/manual/src/docs/asciidoc/_includes/reactive/oauth2/login.adoc
@@ -160,3 +160,21 @@ SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) {
return http.build();
}
----
+
+You may register a `GrantedAuthoritiesMapper` `@Bean` to have it automatically applied to the default configuration, as shown in the following example:
+
+[source,java]
+----
+@Bean
+public GrantedAuthoritiesMapper userAuthoritiesMapper() {
+ ...
+}
+
+@Bean
+SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) {
+ http
+ // ...
+ .oauth2Login(withDefaults());
+ return http.build();
+}
+----
diff --git a/docs/manual/src/docs/asciidoc/_includes/reactive/oauth2/resource-server.adoc b/docs/manual/src/docs/asciidoc/_includes/reactive/oauth2/resource-server.adoc
index 5e45a744271..16413cd94f8 100644
--- a/docs/manual/src/docs/asciidoc/_includes/reactive/oauth2/resource-server.adoc
+++ b/docs/manual/src/docs/asciidoc/_includes/reactive/oauth2/resource-server.adoc
@@ -14,7 +14,8 @@ This authorization server can be consulted by resource servers to authorize requ
A complete working example for {gh-samples-url}/boot/oauth2resourceserver-webflux[*JWTs*] is available in the {gh-samples-url}[Spring Security repository].
====
-== Dependencies
+[[webflux-oauth2resourceserver-jwt-minimaldependencies]]
+== Minimal Dependencies for JWT
Most Resource Server support is collected into `spring-security-oauth2-resource-server`.
However, the support for decoding and verifying JWTs is in `spring-security-oauth2-jose`, meaning that both are necessary in order to have a working resource server that supports JWT-encoded Bearer Tokens.
@@ -549,6 +550,12 @@ ReactiveJwtDecoder jwtDecoder() {
return jwtDecoder;
}
----
+[[webflux-oauth2resourceserver-opaque-minimaldependencies]]
+=== Minimal Dependencies for Introspection
+As described in <> most of Resource Server support is collected in `spring-security-oauth2-resource-server`.
+However unless a custom <> is provided, the Resource Server will fallback to ReactiveOpaqueTokenIntrospector.
+Meaning that both `spring-security-oauth2-resource-server` and `oauth2-oidc-sdk` are necessary in order to have a working minimal Resource Server that supports opaque Bearer Tokens.
+Please refer to `spring-security-oauth2-resource-server` in order to determin the correct version for `oauth2-oidc-sdk`.
[[webflux-oauth2resourceserver-opaque-minimalconfiguration]]
=== Minimal Configuration for Introspection
@@ -1075,7 +1082,30 @@ In this case, you construct `JwtIssuerReactiveAuthenticationManagerResolver` wit
This approach allows us to add and remove elements from the repository (shown as a `Map` in the snippet) at runtime.
NOTE: It would be unsafe to simply take any issuer and construct an `ReactiveAuthenticationManager` from it.
-The issuer should be one that the code can verify from a trusted source like a whitelist.
+The issuer should be one that the code can verify from a trusted source like an allowed list of issuers.
+
+[[webflux-oauth2resourceserver-bearertoken-resolver]]
+== Bearer Token Resolution
+
+By default, Resource Server looks for a bearer token in the `Authorization` header.
+This, however, can be customized.
+
+For example, you may have a need to read the bearer token from a custom header.
+To achieve this, you can wire an instance of `ServerBearerTokenAuthenticationConverter` into the DSL, as you can see in the following example:
+
+.Custom Bearer Token Header
+====
+.Java
+[source,java,role="primary"]
+----
+ServerBearerTokenAuthenticationConverter converter = new ServerBearerTokenAuthenticationConverter();
+converter.setBearerTokenHeaderName(HttpHeaders.PROXY_AUTHORIZATION);
+http
+ .oauth2ResourceServer(oauth2 -> oauth2
+ .bearerTokenConverter(converter)
+ );
+----
+====
== Bearer Token Propagation
diff --git a/docs/manual/src/docs/asciidoc/_includes/reactive/test.adoc b/docs/manual/src/docs/asciidoc/_includes/reactive/test.adoc
index 48901da4f8e..4a9e3a9e390 100644
--- a/docs/manual/src/docs/asciidoc/_includes/reactive/test.adoc
+++ b/docs/manual/src/docs/asciidoc/_includes/reactive/test.adoc
@@ -218,7 +218,7 @@ assertThat(user.getAuthorities()).containsExactly(new SimpleGrantedAuthority("SC
Spring Security does the necessary work to make sure that the `OidcUser` instance is available for <>.
-Further, it also links that `OidcUser` to a simple instance of `OAuth2AuthorizedClient` that it deposits into an `WebSessionOAuth2ServerAuthorizedClientRepository`.
+Further, it also links that `OidcUser` to a simple instance of `OAuth2AuthorizedClient` that it deposits into a mock `ServerOAuth2AuthorizedClientRepository`.
This can be handy if your tests <>..
[[webflux-testing-oidc-login-authorities]]
@@ -339,7 +339,7 @@ assertThat(user.getAuthorities()).containsExactly(new SimpleGrantedAuthority("SC
Spring Security does the necessary work to make sure that the `OAuth2User` instance is available for <>.
-Further, it also links that `OAuth2User` to a simple instance of `OAuth2AuthorizedClient` that it deposits in an `WebSessionOAuth2ServerAuthorizedClientRepository`.
+Further, it also links that `OAuth2User` to a simple instance of `OAuth2AuthorizedClient` that it deposits in a mock `ServerOAuth2AuthorizedClientRepository`.
This can be handy if your tests <>.
[[webflux-testing-oauth2-login-authorities]]
@@ -431,7 +431,7 @@ public Mono foo(@RegisteredOAuth2AuthorizedClient("my-app") OAuth2Author
----
Simulating this handshake with the authorization server could be cumbersome.
-Instead, you can use `SecurityMockServerConfigurers#oauth2Client` to add a `OAuth2AuthorizedClient` into an `WebSessionOAuth2ServerAuthorizedClientRepository`:
+Instead, you can use `SecurityMockServerConfigurers#oauth2Client` to add a `OAuth2AuthorizedClient` into a mock `ServerOAuth2AuthorizedClientRepository`:
[source,java]
----
@@ -440,19 +440,6 @@ client
.get().uri("/endpoint").exchange();
----
-If your application isn't already using an `WebSessionOAuth2ServerAuthorizedClientRepository`, then you can supply one as a `@TestConfiguration`:
-
-[source,java]
-----
-@TestConfiguration
-static class AuthorizedClientConfig {
- @Bean
- OAuth2ServerAuthorizedClientRepository authorizedClientRepository() {
- return new WebSessionOAuth2ServerAuthorizedClientRepository();
- }
-}
-----
-
What this will do is create an `OAuth2AuthorizedClient` that has a simple `ClientRegistration`, `OAuth2AccessToken`, and resource owner name.
Specifically, it will include a `ClientRegistration` with a client id of "test-client" and client secret of "test-secret":
@@ -478,8 +465,7 @@ assertThat(authorizedClient.getAccessToken().getScopes()).hasSize(1);
assertThat(authorizedClient.getAccessToken().getScopes()).containsExactly("read");
----
-Spring Security does the necessary work to make sure that the `OAuth2AuthorizedClient` instance is available in the associated `HttpSession`.
-That means that it can be retrieved from an `WebSessionOAuth2ServerAuthorizedClientRepository`.
+The client can then be retrieved as normal using `@RegisteredOAuth2AuthorizedClient` in a controller method.
[[webflux-testing-oauth2-client-scopes]]
==== Configuring Scopes
diff --git a/docs/manual/src/docs/asciidoc/_includes/reactive/webflux.adoc b/docs/manual/src/docs/asciidoc/_includes/reactive/webflux.adoc
index f39c8163687..da909d6385b 100644
--- a/docs/manual/src/docs/asciidoc/_includes/reactive/webflux.adoc
+++ b/docs/manual/src/docs/asciidoc/_includes/reactive/webflux.adoc
@@ -13,7 +13,10 @@ You can find a few sample applications that demonstrate the code below:
You can find a minimal WebFlux Security configuration below:
-[source,java]
+.Minimal WebFlux Security Configuration
+====
+.Java
+[source,java,role="primary"]
-----
@EnableWebFluxSecurity
@@ -31,13 +34,35 @@ public class HelloWebfluxSecurityConfig {
}
-----
+.Kotlin
+[source,kotlin,role="secondary"]
+-----
+@EnableWebFluxSecurity
+class HelloWebfluxSecurityConfig {
+
+ @Bean
+ fun userDetailsService(): ReactiveUserDetailsService {
+ val userDetails = User.withDefaultPasswordEncoder()
+ .username("user")
+ .password("user")
+ .roles("USER")
+ .build()
+ return MapReactiveUserDetailsService(userDetails)
+ }
+}
+-----
+====
+
This configuration provides form and http basic authentication, sets up authorization to require an authenticated user for accessing any page, sets up a default log in page and a default log out page, sets up security related HTTP headers, CSRF protection, and more.
== Explicit WebFlux Security Configuration
You can find an explicit version of the minimal WebFlux Security configuration below:
-[source,java]
+.Explicit WebFlux Security Configuration
+====
+.Java
+[source,java,role="primary"]
-----
@Configuration
@EnableWebFluxSecurity
@@ -66,5 +91,36 @@ public class HelloWebfluxSecurityConfig {
}
-----
+.Kotlin
+[source,kotlin,role="secondary"]
+-----
+@Configuration
+@EnableWebFluxSecurity
+class HelloWebfluxSecurityConfig {
+
+ @Bean
+ fun userDetailsService(): ReactiveUserDetailsService {
+ val userDetails = User.withDefaultPasswordEncoder()
+ .username("user")
+ .password("user")
+ .roles("USER")
+ .build()
+ return MapReactiveUserDetailsService(userDetails)
+ }
+
+ @Bean
+ fun springSecurityFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain {
+ return http {
+ authorizeExchange {
+ authorize(anyExchange, authenticated)
+ }
+ formLogin { }
+ httpBasic { }
+ }
+ }
+}
+-----
+====
+
This configuration explicitly sets up all the same things as our minimal configuration.
From here you can easily make the changes to the defaults.
diff --git a/docs/manual/src/docs/asciidoc/_includes/servlet/appendix/dependencies.adoc b/docs/manual/src/docs/asciidoc/_includes/servlet/appendix/dependencies.adoc
deleted file mode 100644
index 40bbd8d130a..00000000000
--- a/docs/manual/src/docs/asciidoc/_includes/servlet/appendix/dependencies.adoc
+++ /dev/null
@@ -1,261 +0,0 @@
-
-
-[[appendix-dependencies]]
-== Spring Security Dependencies
-This appendix provides a reference of the modules in Spring Security and the additional dependencies that they require in order to function in a running application.
-We don't include dependencies that are only used when building or testing Spring Security itself.
-Nor do we include transitive dependencies which are required by external dependencies.
-
-The version of Spring required is listed on the project website, so the specific versions are omitted for Spring dependencies below.
-Note that some of the dependencies listed as "optional" below may still be required for other non-security functionality in a Spring application.
-Also dependencies listed as "optional" may not actually be marked as such in the project's Maven POM files if they are used in most applications.
-They are "optional" only in the sense that you don't need them unless you are using the specified functionality.
-
-Where a module depends on another Spring Security module, the non-optional dependencies of the module it depends on are also assumed to be required and are not listed separately.
-
-
-=== spring-security-core
-
-The core module must be included in any project using Spring Security.
-
-.Core Dependencies
-|===
-| Dependency | Version | Description
-
-| ehcache
-| 1.6.2
-| Required if the Ehcache-based user cache implementation is used (optional).
-
-| spring-aop
-|
-| Method security is based on Spring AOP
-
-| spring-beans
-|
-| Required for Spring configuration
-
-| spring-expression
-|
-| Required for expression-based method security (optional)
-
-| spring-jdbc
-|
-| Required if using a database to store user data (optional).
-
-| spring-tx
-|
-| Required if using a database to store user data (optional).
-
-| aspectjrt
-| 1.6.10
-| Required if using AspectJ support (optional).
-
-| jsr250-api
-| 1.0
-| Required if you are using JSR-250 method-security annotations (optional).
-|===
-
-=== spring-security-remoting
-This module is typically required in web applications which use the Servlet API.
-
-.Remoting Dependencies
-|===
-| Dependency | Version | Description
-
-| spring-security-core
-|
-|
-
-| spring-web
-|
-| Required for clients which use HTTP remoting support.
-|===
-
-=== spring-security-web
-This module is typically required in web applications which use the Servlet API.
-
-.Web Dependencies
-|===
-| Dependency | Version | Description
-
-| spring-security-core
-|
-|
-
-| spring-web
-|
-| Spring web support classes are used extensively.
-
-| spring-jdbc
-|
-| Required for JDBC-based persistent remember-me token repository (optional).
-
-| spring-tx
-|
-| Required by remember-me persistent token repository implementations (optional).
-|===
-
-=== spring-security-ldap
-This module is only required if you are using LDAP authentication.
-
-.LDAP Dependencies
-|===
-| Dependency | Version | Description
-
-| spring-security-core
-|
-|
-
-| spring-ldap-core
-| 1.3.0
-| LDAP support is based on Spring LDAP.
-
-| spring-tx
-|
-| Data exception classes are required.
-
-| apache-ds footnote:[The modules `apacheds-core`, `apacheds-core-entry`, `apacheds-protocol-shared`, `apacheds-protocol-ldap` and `apacheds-server-jndi` are required.
-]
-| 1.5.5
-| Required if you are using an embedded LDAP server (optional).
-
-| shared-ldap
-| 0.9.15
-| Required if you are using an embedded LDAP server (optional).
-
-| ldapsdk
-| 4.1
-| Mozilla LdapSDK.
-Used for decoding LDAP password policy controls if you are using password-policy functionality with OpenLDAP, for example.
-|===
-
-
-=== spring-security-config
-This module is required if you are using Spring Security namespace configuration.
-
-.Config Dependencies
-|===
-| Dependency | Version | Description
-
-| spring-security-core
-|
-|
-
-| spring-security-web
-|
-| Required if you are using any web-related namespace configuration (optional).
-
-| spring-security-ldap
-|
-| Required if you are using the LDAP namespace options (optional).
-
-| spring-security-openid
-|
-| Required if you are using OpenID authentication (optional).
-
-| aspectjweaver
-| 1.6.10
-| Required if using the protect-pointcut namespace syntax (optional).
-|===
-
-
-=== spring-security-acl
-The ACL module.
-
-.ACL Dependencies
-|===
-| Dependency | Version | Description
-
-| spring-security-core
-|
-|
-
-| ehcache
-| 1.6.2
-| Required if the Ehcache-based ACL cache implementation is used (optional if you are using your own implementation).
-
-| spring-jdbc
-|
-| Required if you are using the default JDBC-based AclService (optional if you implement your own).
-
-| spring-tx
-|
-| Required if you are using the default JDBC-based AclService (optional if you implement your own).
-|===
-
-=== spring-security-cas
-The CAS module provides integration with JA-SIG CAS.
-
-.CAS Dependencies
-|===
-| Dependency | Version | Description
-
-| spring-security-core
-|
-|
-
-| spring-security-web
-|
-|
-
-| cas-client-core
-| 3.1.12
-| The JA-SIG CAS Client.
-This is the basis of the Spring Security integration.
-
-| ehcache
-| 1.6.2
-| Required if you are using the Ehcache-based ticket cache (optional).
-|===
-
-=== spring-security-openid
-The OpenID module.
-
-.OpenID Dependencies
-|===
-| Dependency | Version | Description
-
-| spring-security-core
-|
-|
-
-| spring-security-web
-|
-|
-
-| openid4java-nodeps
-| 0.9.6
-| Spring Security's OpenID integration uses OpenID4Java.
-
-| httpclient
-| 4.1.1
-| openid4java-nodeps depends on HttpClient 4.
-
-| guice
-| 2.0
-| openid4java-nodeps depends on Guice 2.
-|===
-
-=== spring-security-taglibs
-Provides Spring Security's JSP tag implementations.
-
-.Taglib Dependencies
-|===
-| Dependency | Version | Description
-
-| spring-security-core
-|
-|
-
-| spring-security-web
-|
-|
-
-| spring-security-acl
-|
-| Required if you are using the `accesscontrollist` tag or `hasPermission()` expressions with ACLs (optional).
-
-| spring-expression
-|
-| Required if you are using SPEL expressions in your tag access constraints.
-|===
diff --git a/docs/manual/src/docs/asciidoc/_includes/servlet/appendix/faq.adoc b/docs/manual/src/docs/asciidoc/_includes/servlet/appendix/faq.adoc
index 01c1c23f7df..cb690f54a5a 100644
--- a/docs/manual/src/docs/asciidoc/_includes/servlet/appendix/faq.adoc
+++ b/docs/manual/src/docs/asciidoc/_includes/servlet/appendix/faq.adoc
@@ -20,7 +20,7 @@
Spring Security provides you with a very flexible framework for your authentication and authorization requirements, but there are many other considerations for building a secure application that are outside its scope.
Web applications are vulnerable to all kinds of attacks which you should be familiar with, preferably before you start development so you can design and code with them in mind from the beginning.
-Check out thehttp://www.owasp.org/[OWASP web site] for information on the major issues facing web application developers and the countermeasures you can use against them.
+Check out the https://www.owasp.org/[OWASP web site] for information on the major issues facing web application developers and the countermeasures you can use against them.
[[appendix-faq-web-xml]]
@@ -77,9 +77,9 @@ It should also be compatible with applications using Spring 2.5.x.
==== I'm new to Spring Security and I need to build an application that supports CAS single sign-on over HTTPS, while allowing Basic authentication locally for certain URLs, authenticating against multiple back end user information sources (LDAP and JDBC). I've copied some configuration files I found but it doesn't work.
What could be wrong?
-Or subsititute an alternative complex scenario...
+Or substitute an alternative complex scenario...
-Realistically, you need an understanding of the technolgies you are intending to use before you can successfully build applications with them.
+Realistically, you need an understanding of the technologies you are intending to use before you can successfully build applications with them.
Security is complicated.
Setting up a simple configuration using a login form and some hard-coded users using Spring Security's namespace is reasonably straightforward.
Moving to using a backed JDBC database is also easy enough.
@@ -131,7 +131,7 @@ If you are using hashed passwords, make sure the value stored in your database i
[[appendix-faq-login-loop]]
==== My application goes into an "endless loop" when I try to login, what's going on?
-A common user problem with infinite loop and redirecting to the login page is caused by accidently configuring the login page as a "secured" resource.
+A common user problem with infinite loop and redirecting to the login page is caused by accidentally configuring the login page as a "secured" resource.
Make sure your configuration allows anonymous access to the login page, either by excluding it from the security filter chain or marking it as requiring ROLE_ANONYMOUS.
If your AccessDecisionManager includes an AuthenticatedVoter, you can use the attribute "IS_AUTHENTICATED_ANONYMOUSLY". This is automatically available if you are using the standard namespace configuration setup.
@@ -378,7 +378,7 @@ For third-party jars the situation isn't always quite so obvious.
A good starting point is to copy those from one of the pre-built sample applications WEB-INF/lib directories.
For a basic application, you can start with the tutorial sample.
If you want to use LDAP, with an embedded test server, then use the LDAP sample as a starting point.
-The reference manual also includeshttp://static.springsource.org/spring-security/site/docs/3.1.x/reference/springsecurity-single.html#appendix-dependencies[an appendix] listing the first-level dependencies for each Spring Security module with some information on whether they are optional and what they are required for.
+The reference manual also includes https://static.springsource.org/spring-security/site/docs/3.1.x/reference/springsecurity-single.html#appendix-dependencies[an appendix] listing the first-level dependencies for each Spring Security module with some information on whether they are optional and what they are required for.
If you are building your project with maven, then adding the appropriate Spring Security modules as dependencies to your pom.xml will automatically pull in the core jars that the framework requires.
Any which are marked as "optional" in the Spring Security POM files will have to be added to your own pom.xml file if you need them.
@@ -387,7 +387,7 @@ Any which are marked as "optional" in the Spring Security POM files will have to
[[appendix-faq-apacheds-deps]]
==== What dependencies are needed to run an embedded ApacheDS LDAP server?
-If you are using Maven, you need to add the folowing to your pom dependencies:
+If you are using Maven, you need to add the following to your pom dependencies:
[source]
----
diff --git a/docs/manual/src/docs/asciidoc/_includes/servlet/appendix/index.adoc b/docs/manual/src/docs/asciidoc/_includes/servlet/appendix/index.adoc
index f0a169ecfbc..7ab1808a9ca 100644
--- a/docs/manual/src/docs/asciidoc/_includes/servlet/appendix/index.adoc
+++ b/docs/manual/src/docs/asciidoc/_includes/servlet/appendix/index.adoc
@@ -5,6 +5,4 @@ include::database-schema.adoc[]
include::namespace.adoc[]
-include::dependencies.adoc[]
-
include::faq.adoc[]
diff --git a/docs/manual/src/docs/asciidoc/_includes/servlet/appendix/namespace.adoc b/docs/manual/src/docs/asciidoc/_includes/servlet/appendix/namespace.adoc
index 6ab07162948..02754f1780e 100644
--- a/docs/manual/src/docs/asciidoc/_includes/servlet/appendix/namespace.adoc
+++ b/docs/manual/src/docs/asciidoc/_includes/servlet/appendix/namespace.adoc
@@ -504,43 +504,12 @@ Default false.
** `DENY` The page cannot be displayed in a frame, regardless of the site attempting to do so.
This is the default when frame-options-policy is specified.
** `SAMEORIGIN` The page can only be displayed in a frame on the same origin as the page itself
-** `ALLOW-FROM origin` The page can only be displayed in a frame on the specified origin.
+
In other words, if you specify DENY, not only will attempts to load the page in a frame fail when loaded from other sites, attempts to do so will fail when loaded from the same site.
On the other hand, if you specify SAMEORIGIN, you can still use the page in a frame as long as the site including it in a frame it is the same as the one serving the page.
-[[nsa-frame-options-strategy]]
-* **strategy**
-Select the `AllowFromStrategy` to use when using the ALLOW-FROM policy.
-
-** `static` Use a single static ALLOW-FROM value.
-The value can be set through the <> attribute.
-** `regexp` Use a regelur expression to validate incoming requests and if they are allowed.
-The regular expression can be set through the <> attribute.
-The request parameter used to retrieve the value to validate can be specified using the <>.
-** `whitelist` A comma-seperated list containing the allowed domains.
-The comma-seperated list can be set through the <> attribute.
-The request parameter used to retrieve the value to validate can be specified using the <>.
-
-
-
-
-[[nsa-frame-options-ref]]
-* **ref**
-Instead of using one of the predefined strategies it is also possible to use a custom `AllowFromStrategy`.
-The reference to this bean can be specified through this ref attribute.
-
-
-[[nsa-frame-options-value]]
-* **value**
-The value to use when ALLOW-FROM is used a <>.
-
-
-[[nsa-frame-options-from-parameter]]
-* **from-parameter**
-Specify the name of the request parameter to use when using regexp or whitelist for the ALLOW-FROM strategy.
[[nsa-frame-options-parents]]
diff --git a/docs/manual/src/docs/asciidoc/_includes/servlet/architecture/delegating-filter-proxy.adoc b/docs/manual/src/docs/asciidoc/_includes/servlet/architecture/delegating-filter-proxy.adoc
index d75a5c677dd..d7c3d45ed3b 100644
--- a/docs/manual/src/docs/asciidoc/_includes/servlet/architecture/delegating-filter-proxy.adoc
+++ b/docs/manual/src/docs/asciidoc/_includes/servlet/architecture/delegating-filter-proxy.adoc
@@ -16,7 +16,8 @@ The pseudo code of `DelegatingFilterProxy` can be seen below.
.`DelegatingFilterProxy` Pseudo Code
====
-[source,java,subs="+quotes,+macros"]
+.Java
+[source,java,role="primary",subs="+quotes,+macros"]
----
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) {
// Lazily get Filter that was registered as a Spring Bean
@@ -26,6 +27,18 @@ public void doFilter(ServletRequest request, ServletResponse response, FilterCha
delegate.doFilter(request, response);
}
----
+
+.Kotlin
+[source,kotlin,role="secondary",subs="+quotes,+macros"]
+----
+fun doFilter(request: ServletRequest, response: ServletResponse, chain: FilterChain) {
+ // Lazily get Filter that was registered as a Spring Bean
+ // For the example in <> `delegate` is an instance of __Bean Filter~0~__
+ val delegate: Filter = getFilterBean(someBeanName)
+ // delegate work to the Spring Bean
+ delegate.doFilter(request, response)
+}
+----
====
Another benefit of `DelegatingFilterProxy` is that it allows delaying looking `Filter` bean instances.
diff --git a/docs/manual/src/docs/asciidoc/_includes/servlet/architecture/filters.adoc b/docs/manual/src/docs/asciidoc/_includes/servlet/architecture/filters.adoc
index 212d5ec1ad6..4b63aa4deb9 100644
--- a/docs/manual/src/docs/asciidoc/_includes/servlet/architecture/filters.adoc
+++ b/docs/manual/src/docs/asciidoc/_includes/servlet/architecture/filters.adoc
@@ -21,7 +21,8 @@ The power of the `Filter` comes from the `FilterChain` that is passed into it.
.`FilterChain` Usage Example
====
-[source,java]
+.Java
+[source,java,role="primary"]
----
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) {
// do something before the rest of the application
@@ -29,6 +30,16 @@ public void doFilter(ServletRequest request, ServletResponse response, FilterCha
// do something after the rest of the application
}
----
+
+.Kotlin
+[source,kotlin,role="secondary"]
+----
+fun doFilter(request: ServletRequest, response: ServletResponse, chain: FilterChain) {
+ // do something before the rest of the application
+ chain.doFilter(request, response) // invoke the rest of the application
+ // do something after the rest of the application
+}
+----
====
Since a `Filter` only impacts downstream ``Filter``s and the `Servlet`, the order each `Filter` is invoked is extremely important.
diff --git a/docs/manual/src/docs/asciidoc/_includes/servlet/architecture/security-filter-chain.adoc b/docs/manual/src/docs/asciidoc/_includes/servlet/architecture/security-filter-chain.adoc
index 4af4f0e89c3..508e9e3d218 100644
--- a/docs/manual/src/docs/asciidoc/_includes/servlet/architecture/security-filter-chain.adoc
+++ b/docs/manual/src/docs/asciidoc/_includes/servlet/architecture/security-filter-chain.adoc
@@ -23,7 +23,7 @@ In a Servlet container, ``Filter``s are invoked based upon the URL alone.
However, `FilterChainProxy` can determine invocation based upon anything in the `HttpServletRequest` by leveraging the `RequestMatcher` interface.
In fact, `FilterChainProxy` can be used to determine which `SecurityFilterChain` should be used.
-This allows providing a totally separate configuration for different _slices_ if your application.
+This allows providing a totally separate configuration for different _slices_ of your application.
.Multiple SecurityFilterChain
[[servlet-multi-securityfilterchain-figure]]
diff --git a/docs/manual/src/docs/asciidoc/_includes/servlet/authentication/architecture/provider-manager.adoc b/docs/manual/src/docs/asciidoc/_includes/servlet/authentication/architecture/provider-manager.adoc
index 1075eed6c1f..4ed8cb4615a 100644
--- a/docs/manual/src/docs/asciidoc/_includes/servlet/authentication/architecture/provider-manager.adoc
+++ b/docs/manual/src/docs/asciidoc/_includes/servlet/authentication/architecture/provider-manager.adoc
@@ -6,7 +6,7 @@
`ProviderManager` delegates to a `List` of <>.
// FIXME: link to AuthenticationProvider
Each `AuthenticationProvider` has an opportunity to indicate that authentication should be successful, fail, or indicate it cannot make a decision and allow a downstream `AuthenticationProvider` to decide.
-If none of the configured ``AuthenticationProvider``s can authenticate, then authentication will fail with a `ProviderNotFoundException` which is a special `AuthenticationException` that indicates the `ProviderManager` was not configured support the type of `Authentication` that was passed into it.
+If none of the configured ``AuthenticationProvider``s can authenticate, then authentication will fail with a `ProviderNotFoundException` which is a special `AuthenticationException` that indicates the `ProviderManager` was not configured to support the type of `Authentication` that was passed into it.
image::{figures}/providermanager.png[]
diff --git a/docs/manual/src/docs/asciidoc/_includes/servlet/authentication/architecture/security-context-holder.adoc b/docs/manual/src/docs/asciidoc/_includes/servlet/authentication/architecture/security-context-holder.adoc
index 4c1516a1af9..db2f4c6d4cc 100644
--- a/docs/manual/src/docs/asciidoc/_includes/servlet/authentication/architecture/security-context-holder.adoc
+++ b/docs/manual/src/docs/asciidoc/_includes/servlet/authentication/architecture/security-context-holder.adoc
@@ -16,7 +16,8 @@ The simplest way to indicate a user is authenticated is to set the `SecurityCont
.Setting `SecurityContextHolder`
====
-[source,java]
+.Java
+[source,java,role="primary"]
----
SecurityContext context = SecurityContextHolder.createEmptyContext(); // <1>
Authentication authentication =
@@ -25,6 +26,16 @@ context.setAuthentication(authentication);
SecurityContextHolder.setContext(context); // <3>
----
+
+.Kotlin
+[source,kotlin,role="secondary"]
+----
+val context: SecurityContext = SecurityContextHolder.createEmptyContext() // <1>
+val authentication: Authentication = TestingAuthenticationToken("username", "password", "ROLE_USER") // <2>
+context.authentication = authentication
+
+SecurityContextHolder.setContext(context) // <3>
+----
====
<1> We start by creating an empty `SecurityContext`.
@@ -40,7 +51,8 @@ If you wish to obtain information about the authenticated principal, you can do
.Access Currently Authenticated User
====
-[source,java]
+.Java
+[source,java,role="primary"]
----
SecurityContext context = SecurityContextHolder.getContext();
Authentication authentication = context.getAuthentication();
@@ -48,6 +60,16 @@ String username = authentication.getName();
Object principal = authentication.getPrincipal();
Collection extends GrantedAuthority> authorities = authentication.getAuthorities();
----
+
+.Kotlin
+[source,kotlin,role="secondary"]
+----
+val context = SecurityContextHolder.getContext()
+val authentication = context.authentication
+val username = authentication.name
+val principal = authentication.principal
+val authorities = authentication.authorities
+----
====
// FIXME: add links to HttpServletRequest.getRemoteUser() and @CurrentSecurityContext @AuthenticationPrincipal
diff --git a/docs/manual/src/docs/asciidoc/_includes/servlet/authentication/cas.adoc b/docs/manual/src/docs/asciidoc/_includes/servlet/authentication/cas.adoc
index c78abd0cafe..4f8afa13981 100644
--- a/docs/manual/src/docs/asciidoc/_includes/servlet/authentication/cas.adoc
+++ b/docs/manual/src/docs/asciidoc/_includes/servlet/authentication/cas.adoc
@@ -235,7 +235,7 @@ With the configuration above, the flow of logout would be:
* The logout success page, `/cas-logout.jsp`, should instruct the user to click a link pointing to `/logout/cas` in order to logout out of all applications.
* When the user clicks the link, the user is redirected to the CAS single logout URL (https://localhost:9443/cas/logout).
* On the CAS Server side, the CAS single logout URL then submits single logout requests to all the CAS Services.
-On the CAS Service side, JASIG's `SingleSignOutFilter` processes the logout request by invaliditing the original session.
+On the CAS Service side, JASIG's `SingleSignOutFilter` processes the logout request by invalidating the original session.
diff --git a/docs/manual/src/docs/asciidoc/_includes/servlet/authentication/logout.adoc b/docs/manual/src/docs/asciidoc/_includes/servlet/authentication/logout.adoc
index cff12b34cf4..c824ffe0adb 100644
--- a/docs/manual/src/docs/asciidoc/_includes/servlet/authentication/logout.adoc
+++ b/docs/manual/src/docs/asciidoc/_includes/servlet/authentication/logout.adoc
@@ -2,7 +2,7 @@
== Handling Logouts
[[logout-java-configuration]]
-=== Logout Java Configuration
+=== Logout Java/Kotlin Configuration
When using the `{security-api-url}org/springframework/security/config/annotation/web/configuration/WebSecurityConfigurerAdapter.html[WebSecurityConfigurerAdapter]`, logout capabilities are automatically applied.
The default is that accessing the URL `/logout` will log the user out by:
@@ -14,7 +14,10 @@ The default is that accessing the URL `/logout` will log the user out by:
Similar to configuring login capabilities, however, you also have various options to further customize your logout requirements:
-[source,java]
+.Logout Configuration
+====
+.Java
+[source,java,role="primary"]
----
protected void configure(HttpSecurity http) throws Exception {
http
@@ -30,6 +33,24 @@ protected void configure(HttpSecurity http) throws Exception {
}
----
+.Kotlin
+[source,kotlin,role="secondary"]
+-----
+override fun configure(http: HttpSecurity) {
+ http {
+ logout {
+ logoutUrl = "/my/logout" // <1>
+ logoutSuccessUrl = "/my/index" // <2>
+ logoutSuccessHandler = customLogoutSuccessHandler // <3>
+ invalidateHttpSession = true // <4>
+ addLogoutHandler(logoutHandler) // <5>
+ deleteCookies(cookieNamesToClear) // <6>
+ }
+ }
+}
+-----
+====
+
<1> Provides logout support.
This is automatically applied when using `WebSecurityConfigurerAdapter`.
<2> The URL that triggers log out to occur (default is `/logout`).
diff --git a/docs/manual/src/docs/asciidoc/_includes/servlet/authentication/openid.adoc b/docs/manual/src/docs/asciidoc/_includes/servlet/authentication/openid.adoc
index e3d14137d3c..01362127ffa 100644
--- a/docs/manual/src/docs/asciidoc/_includes/servlet/authentication/openid.adoc
+++ b/docs/manual/src/docs/asciidoc/_includes/servlet/authentication/openid.adoc
@@ -1,5 +1,9 @@
[[servlet-openid]]
== OpenID Support
+
+[NOTE]
+The OpenID 1.0 and 2.0 protocols have been deprecated and users are encouraged to migrate to OpenID Connect, which is supported by spring-security-oauth2.
+
The namespace supports https://openid.net/[OpenID] login either instead of, or in addition to normal form-based login, with a simple change:
[source,xml]
diff --git a/docs/manual/src/docs/asciidoc/_includes/servlet/authentication/preauth.adoc b/docs/manual/src/docs/asciidoc/_includes/servlet/authentication/preauth.adoc
index e68229393c0..c50de6379fb 100644
--- a/docs/manual/src/docs/asciidoc/_includes/servlet/authentication/preauth.adoc
+++ b/docs/manual/src/docs/asciidoc/_includes/servlet/authentication/preauth.adoc
@@ -29,13 +29,25 @@ We just provide an outline here so you should consult the Javadoc and source whe
This class will check the current contents of the security context and, if empty, it will attempt to extract user information from the HTTP request and submit it to the `AuthenticationManager`.
Subclasses override the following methods to obtain this information:
-[source,java]
+.Override AbstractPreAuthenticatedProcessingFilter
+====
+.Java
+[source,java,role="primary"]
----
protected abstract Object getPreAuthenticatedPrincipal(HttpServletRequest request);
protected abstract Object getPreAuthenticatedCredentials(HttpServletRequest request);
----
+.Kotlin
+[source,kotlin,role="secondary"]
+----
+protected abstract fun getPreAuthenticatedPrincipal(request: HttpServletRequest): Any?
+
+protected abstract fun getPreAuthenticatedCredentials(request: HttpServletRequest): Any?
+----
+====
+
After calling these, the filter will create a `PreAuthenticatedAuthenticationToken` containing the returned data and submit it for authentication.
By "authentication" here, we really just mean further processing to perhaps load the user's authorities, but the standard Spring Security authentication architecture is followed.
diff --git a/docs/manual/src/docs/asciidoc/_includes/servlet/authentication/unpwd/basic.adoc b/docs/manual/src/docs/asciidoc/_includes/servlet/authentication/unpwd/basic.adoc
index 849fc278b04..aa2ff02f297 100644
--- a/docs/manual/src/docs/asciidoc/_includes/servlet/authentication/unpwd/basic.adoc
+++ b/docs/manual/src/docs/asciidoc/_includes/servlet/authentication/unpwd/basic.adoc
@@ -23,13 +23,14 @@ The `RequestCache` is typically a `NullRequestCache` that does not save the requ
When a client receives the WWW-Authenticate header it knows it should retry with a username and password.
Below is the flow for the username and password being processed.
+[[servlet-authentication-basicauthenticationfilter]]
.Authenticating Username and Password
image::{figures}/basicauthenticationfilter.png[]
The figure builds off our <> diagram.
-image:{icondir}/number_1.png[] When the user submits their username and password, the `UsernamePasswordAuthenticationFilter` creates a `UsernamePasswordAuthenticationToken` which is a type of <> by extracting the username and password from the `HttpServletRequest`.
+image:{icondir}/number_1.png[] When the user submits their username and password, the `BasicAuthenticationFilter` creates a `UsernamePasswordAuthenticationToken` which is a type of <> by extracting the username and password from the `HttpServletRequest`.
image:{icondir}/number_2.png[] Next, the `UsernamePasswordAuthenticationToken` is passed into the `AuthenticationManager` to be authenticated.
The details of what `AuthenticationManager` look like depend on how the <>.
diff --git a/docs/manual/src/docs/asciidoc/_includes/servlet/authentication/unpwd/digest.adoc b/docs/manual/src/docs/asciidoc/_includes/servlet/authentication/unpwd/digest.adoc
index 90c38bca2d2..603db7d93d3 100644
--- a/docs/manual/src/docs/asciidoc/_includes/servlet/authentication/unpwd/digest.adoc
+++ b/docs/manual/src/docs/asciidoc/_includes/servlet/authentication/unpwd/digest.adoc
@@ -76,7 +76,7 @@ protected void configure(HttpSecurity http) throws Exception {
diff --git a/docs/manual/src/docs/asciidoc/_includes/servlet/authentication/unpwd/in-memory.adoc b/docs/manual/src/docs/asciidoc/_includes/servlet/authentication/unpwd/in-memory.adoc
index faccb03a7e9..a364cee0e1a 100644
--- a/docs/manual/src/docs/asciidoc/_includes/servlet/authentication/unpwd/in-memory.adoc
+++ b/docs/manual/src/docs/asciidoc/_includes/servlet/authentication/unpwd/in-memory.adoc
@@ -65,7 +65,7 @@ The samples above store the passwords in a secure format, but leave a lot to be
In the sample below we leverage <> to ensure that the password stored in memory is protected.
-However, it does not protect the password against obtaining the password by decompiling the source code.
+However, it does not protect against obtaining the password by decompiling the source code.
For this reason, `User.withDefaultPasswordEncoder` should only be used for "getting started" and is not intended for production.
.InMemoryUserDetailsManager with User.withDefaultPasswordEncoder
@@ -82,7 +82,7 @@ public UserDetailsService users() {
.password("password")
.roles("USER")
.build();
- UserDetails user = users
+ UserDetails admin = users
.username("admin")
.password("password")
.roles("USER", "ADMIN")
diff --git a/docs/manual/src/docs/asciidoc/_includes/servlet/authentication/unpwd/jdbc.adoc b/docs/manual/src/docs/asciidoc/_includes/servlet/authentication/unpwd/jdbc.adoc
index ddbdc8a4dea..23198edc99a 100644
--- a/docs/manual/src/docs/asciidoc/_includes/servlet/authentication/unpwd/jdbc.adoc
+++ b/docs/manual/src/docs/asciidoc/_includes/servlet/authentication/unpwd/jdbc.adoc
@@ -35,7 +35,7 @@ The default schema is also exposed as a classpath resource named `org/springfram
----
create table users(
username varchar_ignorecase(50) not null primary key,
- password varchar_ignorecase(50) not null,
+ password varchar_ignorecase(500) not null,
enabled boolean not null
);
@@ -169,7 +169,8 @@ UserDetailsManager users(DataSource dataSource) {
.roles("USER", "ADMIN")
.build();
JdbcUserDetailsManager users = new JdbcUserDetailsManager(dataSource);
- users.createUser()
+ users.createUser(user);
+ users.createUser(admin);
}
----
diff --git a/docs/manual/src/docs/asciidoc/_includes/servlet/authorization/acls.adoc b/docs/manual/src/docs/asciidoc/_includes/servlet/authorization/acls.adoc
index 41eb2763570..f87e83d4ee0 100644
--- a/docs/manual/src/docs/asciidoc/_includes/servlet/authorization/acls.adoc
+++ b/docs/manual/src/docs/asciidoc/_includes/servlet/authorization/acls.adoc
@@ -72,7 +72,7 @@ Columns include the ID, a foreign key to the ACL_CLASS table, a unique identifie
We have a single row for every domain object instance we're storing ACL permissions for.
* Finally, ACL_ENTRY stores the individual permissions assigned to each recipient.
-Columns include a foreign key to the ACL_OBJECT_IDENTITY, the recipient (ie a foreign key to ACL_SID), whether we'll be auditing or not, and the integer bit mask that represents the actual permission being granted or denied.
+Columns include a foreign key to the ACL_OBJECT_IDENTITY, the recipient (i.e. a foreign key to ACL_SID), whether we'll be auditing or not, and the integer bit mask that represents the actual permission being granted or denied.
We have a single row for every recipient that receives a permission to work with a domain object.
@@ -113,7 +113,7 @@ The default implementation is called `ObjectIdentityImpl`.
* `AclService`: Retrieves the `Acl` applicable for a given `ObjectIdentity`.
In the included implementation (`JdbcAclService`), retrieval operations are delegated to a `LookupStrategy`.
-The `LookupStrategy` provides a highly optimized strategy for retrieving ACL information, using batched retrievals `(BasicLookupStrategy`) and supporting custom implementations that leverage materialized views, hierarchical queries and similar performance-centric, non-ANSI SQL capabilities.
+The `LookupStrategy` provides a highly optimized strategy for retrieving ACL information, using batched retrievals (`BasicLookupStrategy`) and supporting custom implementations that leverage materialized views, hierarchical queries and similar performance-centric, non-ANSI SQL capabilities.
* `MutableAclService`: Allows a modified `Acl` to be presented for persistence.
It is not essential to use this interface if you do not wish.
@@ -141,7 +141,7 @@ You'll also need to populate the database with the four ACL-specific tables list
Once you've created the required schema and instantiated `JdbcMutableAclService`, you'll next need to ensure your domain model supports interoperability with the Spring Security ACL package.
Hopefully `ObjectIdentityImpl` will prove sufficient, as it provides a large number of ways in which it can be used.
Most people will have domain objects that contain a `public Serializable getId()` method.
-If the return type is long, or compatible with long (eg an int), you will find you need not give further consideration to `ObjectIdentity` issues.
+If the return type is long, or compatible with long (e.g. an int), you will find you need not give further consideration to `ObjectIdentity` issues.
Many parts of the ACL module rely on long identifiers.
If you're not using long (or an int, byte etc), there is a very good chance you'll need to reimplement a number of classes.
We do not intend to support non-long identifiers in Spring Security's ACL module, as longs are already compatible with all database sequences, the most common identifier data type, and are of sufficient length to accommodate all common usage scenarios.
diff --git a/docs/manual/src/docs/asciidoc/_includes/servlet/authorization/architecture.adoc b/docs/manual/src/docs/asciidoc/_includes/servlet/authorization/architecture.adoc
index 5d2f31d6c93..15de8dbf70d 100644
--- a/docs/manual/src/docs/asciidoc/_includes/servlet/authorization/architecture.adoc
+++ b/docs/manual/src/docs/asciidoc/_includes/servlet/authorization/architecture.adoc
@@ -42,7 +42,7 @@ A pre-invocation decision on whether the invocation is allowed to proceed is mad
[[authz-access-decision-manager]]
=== The AccessDecisionManager
The `AccessDecisionManager` is called by the `AbstractSecurityInterceptor` and is responsible for making final access control decisions.
-the `AccessDecisionManager` interface contains three methods:
+The `AccessDecisionManager` interface contains three methods:
[source,java]
----
diff --git a/docs/manual/src/docs/asciidoc/_includes/servlet/authorization/expression-based.adoc b/docs/manual/src/docs/asciidoc/_includes/servlet/authorization/expression-based.adoc
index 9645a371306..580f57f8efb 100644
--- a/docs/manual/src/docs/asciidoc/_includes/servlet/authorization/expression-based.adoc
+++ b/docs/manual/src/docs/asciidoc/_includes/servlet/authorization/expression-based.adoc
@@ -103,7 +103,7 @@ For example:
Here we have defined that the "admin" area of an application (defined by the URL pattern) should only be available to users who have the granted authority "admin" and whose IP address matches a local subnet.
We've already seen the built-in `hasRole` expression in the previous section.
The expression `hasIpAddress` is an additional built-in expression which is specific to web security.
-It is defined by the `WebSecurityExpressionRoot` class, an instance of which is used as the expression root object when evaluation web-access expressions.
+It is defined by the `WebSecurityExpressionRoot` class, an instance of which is used as the expression root object when evaluating web-access expressions.
This object also directly exposed the `HttpServletRequest` object under the name `request` so you can invoke the request directly in an expression.
If expressions are being used, a `WebExpressionVoter` will be added to the `AccessDecisionManager` which is used by the namespace.
So if you aren't using the namespace and want to use expressions, you will have to add one of these to your configuration.
@@ -125,7 +125,20 @@ public class WebSecurity {
You could refer to the method using:
-[source,xml]
+.Refer to method
+====
+.Java
+[source,java,role="primary"]
+----
+http
+ .authorizeRequests(authorize -> authorize
+ .antMatchers("/user/**").access("@webSecurity.check(authentication,request)")
+ ...
+ )
+----
+
+.XML
+[source,xml,role="secondary"]
----
----
-or in Java configuration
-
-
-[source,java]
+.Kotlin
+[source,kotlin,role="secondary"]
----
-http
- .authorizeRequests(authorize -> authorize
- .antMatchers("/user/**").access("@webSecurity.check(authentication,request)")
- ...
- )
+http {
+ authorizeRequests {
+ authorize("/user/**", "@webSecurity.check(authentication,request)")
+ }
+}
----
+====
[[el-access-web-path-variables]]
==== Path Variables in Web Security Expressions
@@ -166,7 +178,20 @@ public class WebSecurity {
You could refer to the method using:
-[source,xml,attrs="-attributes"]
+.Path Variables
+====
+.Java
+[source,java,role="primary",attrs="-attributes"]
+----
+http
+ .authorizeRequests(authorize -> authorize
+ .antMatchers("/user/{userId}/**").access("@webSecurity.checkUserId(authentication,#userId)")
+ ...
+ );
+----
+
+.XML
+[source,xml,role="secondary",attrs="-attributes"]
----
----
-or in Java configuration
-
-[source,java,attrs="-attributes"]
+.Kotlin
+[source,kotlin,role="secondary",attrs="-attributes"]
----
-http
- .authorizeRequests(authorize -> authorize
- .antMatchers("/user/{userId}/**").access("@webSecurity.checkUserId(authentication,#userId)")
- ...
- );
+http {
+ authorizeRequests {
+ authorize("/user/{userId}/**", "@webSecurity.checkUserId(authentication,#userId)")
+ }
+}
----
+====
-In both configurations URLs that match would pass in the path variable (and convert it) into checkUserId method.
+In this configuration URLs that match would pass in the path variable (and convert it) into checkUserId method.
For example, if the URL were `/user/123/resource`, then the id passed in would be `123`.
=== Method Security Expressions
@@ -207,7 +232,7 @@ Their use is enabled through the `global-method-security` namespace element:
===== Access Control using @PreAuthorize and @PostAuthorize
The most obviously useful annotation is `@PreAuthorize` which decides whether a method can actually be invoked or not.
-For example (from the"Contacts" sample application)
+For example (from the "Contacts" sample application)
[source,java]
----
@@ -226,7 +251,7 @@ public void deletePermission(Contact contact, Sid recipient, Permission permissi
----
Here we're actually using a method argument as part of the expression to decide whether the current user has the "admin"permission for the given contact.
-The built-in `hasPermission()` expression is linked into the Spring Security ACL module through the application context, as we'll<>.
+The built-in `hasPermission()` expression is linked into the Spring Security ACL module through the application context, as we'll <>.
You can access any of the method arguments by name as expression variables.
There are a number of ways in which Spring Security can resolve the method arguments.
@@ -251,7 +276,7 @@ public void doSomething(@P("c") Contact contact);
+
-Behind the scenes this use implemented using `AnnotationParameterNameDiscoverer` which can be customized to support the value attribute of any specified annotation.
+Behind the scenes this is implemented using `AnnotationParameterNameDiscoverer` which can be customized to support the value attribute of any specified annotation.
* If Spring Data's `@Param` annotation is present on at least one parameter for the method, the value will be used.
This is useful for interfaces compiled with a JDK prior to JDK 8 which do not contain any information about the parameter names.
@@ -271,7 +296,7 @@ Contact findContactByName(@Param("n") String name);
+
-Behind the scenes this use implemented using `AnnotationParameterNameDiscoverer` which can be customized to support the value attribute of any specified annotation.
+Behind the scenes this is implemented using `AnnotationParameterNameDiscoverer` which can be customized to support the value attribute of any specified annotation.
* If JDK 8 was used to compile the source with the -parameters argument and Spring 4+ is being used, then the standard JDK reflection API is used to discover the parameter names.
This works on both classes and interfaces.
@@ -304,7 +329,7 @@ To access the return value from a method, use the built-in name `returnObject` i
--
===== Filtering using @PreFilter and @PostFilter
-As you may already be aware, Spring Security supports filtering of collections and arrays and this can now be achieved using expressions.
+Spring Security supports filtering of collections, arrays, maps and streams using expressions.
This is most commonly performed on the return value of a method.
For example:
@@ -315,8 +340,10 @@ For example:
public List getAll();
----
-When using the `@PostFilter` annotation, Spring Security iterates through the returned collection and removes any elements for which the supplied expression is false.
+When using the `@PostFilter` annotation, Spring Security iterates through the returned collection or map and removes any elements for which the supplied expression is false.
+For an array, a new array instance will be returned containing filtered elements.
The name `filterObject` refers to the current object in the collection.
+In case when a map is used it will refer to the current `Map.Entry` object which allows one to use `filterObject.key` or `filterObject.value` in the expresion.
You can also filter before the method call, using `@PreFilter`, though this is a less common requirement.
The syntax is just the same, but if there is more than one argument which is a collection type then you have to select one by name using the `filterTarget` property of this annotation.
diff --git a/docs/manual/src/docs/asciidoc/_includes/servlet/authorization/secure-objects.adoc b/docs/manual/src/docs/asciidoc/_includes/servlet/authorization/secure-objects.adoc
index 3e9140801a5..7370a4202ce 100644
--- a/docs/manual/src/docs/asciidoc/_includes/servlet/authorization/secure-objects.adoc
+++ b/docs/manual/src/docs/asciidoc/_includes/servlet/authorization/secure-objects.adoc
@@ -16,7 +16,7 @@ The interceptor uses a `MethodSecurityMetadataSource` instance to obtain the con
Other implementations will be used to handle annotation-based configuration.
==== Explicit MethodSecurityInterceptor Configuration
-You can of course configure a `MethodSecurityIterceptor` directly in your application context for use with one of Spring AOP's proxying mechanisms:
+You can of course configure a `MethodSecurityInterceptor` directly in your application context for use with one of Spring AOP's proxying mechanisms:
[source,xml]
----
@@ -140,4 +140,4 @@ A bean declaration which achieves this is shown below:
That's it!
-Now you can create your beans from anywhere within your application, using whatever means you think fit (eg `new Person();`) and they will have the security interceptor applied.
+Now you can create your beans from anywhere within your application, using whatever means you think fit (e.g. `new Person();`) and they will have the security interceptor applied.
diff --git a/docs/manual/src/docs/asciidoc/_includes/servlet/crypto/index.adoc b/docs/manual/src/docs/asciidoc/_includes/servlet/crypto/index.adoc
index 753c46c4b15..137b3e8b694 100644
--- a/docs/manual/src/docs/asciidoc/_includes/servlet/crypto/index.adoc
+++ b/docs/manual/src/docs/asciidoc/_includes/servlet/crypto/index.adoc
@@ -17,14 +17,26 @@ Encryptors are thread-safe.
[[spring-security-crypto-encryption-bytes]]
=== BytesEncryptor
-Use the Encryptors.standard factory method to construct a "standard" BytesEncryptor:
+Use the `Encryptors.stronger` factory method to construct a BytesEncryptor:
-[source,java]
+.BytesEncryptor
+====
+.Java
+[source,java,role="primary"]
+----
+Encryptors.stronger("password", "salt");
+----
+
+.Kotlin
+[source,kotlin,role="secondary"]
----
-Encryptors.standard("password", "salt");
+Encryptors.stronger("password", "salt")
----
+====
-The "standard" encryption method is 256-bit AES using PKCS #5's PBKDF2 (Password-Based Key Derivation Function #2).
+The "stronger" encryption method creates an encryptor using 256 bit AES encryption with
+Galois Counter Mode (GCM).
+It derives the secret key using PKCS #5's PBKDF2 (Password-Based Key Derivation Function #2).
This method requires Java 6.
The password used to generate the SecretKey should be kept in a secure place and not be shared.
The salt is used to prevent dictionary attacks against the key in the event your encrypted data is compromised.
@@ -33,31 +45,65 @@ A 16-byte random initialization vector is also applied so each encrypted message
The provided salt should be in hex-encoded String form, be random, and be at least 8 bytes in length.
Such a salt may be generated using a KeyGenerator:
-[source,java]
+.Generating a key
+====
+.Java
+[source,java,role="primary"]
----
String salt = KeyGenerators.string().generateKey(); // generates a random 8-byte salt that is then hex-encoded
----
+.Kotlin
+[source,kotlin,role="secondary"]
+----
+val salt = KeyGenerators.string().generateKey() // generates a random 8-byte salt that is then hex-encoded
+----
+====
+
+Users may also use the `standard` encryption method, which is 256-bit AES in Cipher Block Chaining (CBC) Mode.
+This mode is not https://en.wikipedia.org/wiki/Authenticated_encryption[authenticated] and does not provide any
+guarantees about the authenticity of the data.
+For a more secure alternative, users should prefer `Encryptors.stronger`.
+
[[spring-security-crypto-encryption-text]]
=== TextEncryptor
Use the Encryptors.text factory method to construct a standard TextEncryptor:
-[source,java]
+.TextEncryptor
+====
+.Java
+[source,java,role="primary"]
----
-
Encryptors.text("password", "salt");
----
+.Kotlin
+[source,kotlin,role="secondary"]
+----
+Encryptors.text("password", "salt")
+----
+====
+
A TextEncryptor uses a standard BytesEncryptor to encrypt text data.
Encrypted results are returned as hex-encoded strings for easy storage on the filesystem or in the database.
Use the Encryptors.queryableText factory method to construct a "queryable" TextEncryptor:
-[source,java]
+.Queryable TextEncryptor
+====
+.Java
+[source,java,role="primary"]
----
Encryptors.queryableText("password", "salt");
----
+.Kotlin
+[source,kotlin,role="secondary"]
+----
+Encryptors.queryableText("password", "salt")
+----
+====
+
The difference between a queryable TextEncryptor and a standard TextEncryptor has to do with initialization vector (iv) handling.
The iv used in a queryable TextEncryptor#encrypt operation is shared, or constant, and is not randomly generated.
This means the same text encrypted multiple times will always produce the same encryption result.
@@ -74,35 +120,76 @@ KeyGenerators are thread-safe.
=== BytesKeyGenerator
Use the KeyGenerators.secureRandom factory methods to generate a BytesKeyGenerator backed by a SecureRandom instance:
-[source,java]
+.BytesKeyGenerator
+====
+.Java
+[source,java,role="primary"]
----
BytesKeyGenerator generator = KeyGenerators.secureRandom();
byte[] key = generator.generateKey();
----
+.Kotlin
+[source,kotlin,role="secondary"]
+----
+val generator = KeyGenerators.secureRandom()
+val key = generator.generateKey()
+----
+====
+
The default key length is 8 bytes.
There is also a KeyGenerators.secureRandom variant that provides control over the key length:
-[source,java]
+.KeyGenerators.secureRandom
+====
+.Java
+[source,java,role="primary"]
----
KeyGenerators.secureRandom(16);
----
+.Kotlin
+[source,kotlin,role="secondary"]
+----
+KeyGenerators.secureRandom(16)
+----
+====
+
Use the KeyGenerators.shared factory method to construct a BytesKeyGenerator that always returns the same key on every invocation:
-[source,java]
+.KeyGenerators.shared
+====
+.Java
+[source,java,role="primary"]
----
KeyGenerators.shared(16);
----
+.Kotlin
+[source,kotlin,role="secondary"]
+----
+KeyGenerators.shared(16)
+----
+====
+
=== StringKeyGenerator
Use the KeyGenerators.string factory method to construct a 8-byte, SecureRandom KeyGenerator that hex-encodes each key as a String:
-[source,java]
+.StringKeyGenerator
+====
+.Java
+[source,java,role="primary"]
----
KeyGenerators.string();
----
+.Kotlin
+[source,kotlin,role="secondary"]
+----
+KeyGenerators.string()
+----
+====
+
[[spring-security-crypto-passwordencoders]]
== Password Encoding
The password package of the spring-security-crypto module provides support for encoding passwords.
@@ -128,7 +215,10 @@ The higher the value, the more work has to be done to calculate the hash.
The default value is 10.
You can change this value in your deployed system without affecting existing passwords, as the value is also stored in the encoded hash.
-[source,java]
+.BCryptPasswordEncoder
+====
+.Java
+[source,java,role="primary"]
----
// Create an encoder with strength 16
@@ -137,15 +227,38 @@ String result = encoder.encode("myPassword");
assertTrue(encoder.matches("myPassword", result));
----
+.Kotlin
+[source,kotlin,role="secondary"]
+----
+
+// Create an encoder with strength 16
+val encoder = BCryptPasswordEncoder(16)
+val result: String = encoder.encode("myPassword")
+assertTrue(encoder.matches("myPassword", result))
+----
+====
+
The `Pbkdf2PasswordEncoder` implementation uses PBKDF2 algorithm to hash the passwords.
In order to defeat password cracking PBKDF2 is a deliberately slow algorithm and should be tuned to take about .5 seconds to verify a password on your system.
-[source,java]
+.Pbkdf2PasswordEncoder
+====
+.Java
+[source,java,role="primary"]
----
-
// Create an encoder with all the defaults
Pbkdf2PasswordEncoder encoder = new Pbkdf2PasswordEncoder();
String result = encoder.encode("myPassword");
assertTrue(encoder.matches("myPassword", result));
----
+
+.Kotlin
+[source,kotlin,role="secondary"]
+----
+// Create an encoder with all the defaults
+val encoder = Pbkdf2PasswordEncoder()
+val result: String = encoder.encode("myPassword")
+assertTrue(encoder.matches("myPassword", result))
+----
+====
diff --git a/docs/manual/src/docs/asciidoc/_includes/servlet/exploits/csrf.adoc b/docs/manual/src/docs/asciidoc/_includes/servlet/exploits/csrf.adoc
index a7bcbc3a529..54f8b6c1ed0 100644
--- a/docs/manual/src/docs/asciidoc/_includes/servlet/exploits/csrf.adoc
+++ b/docs/manual/src/docs/asciidoc/_includes/servlet/exploits/csrf.adoc
@@ -155,7 +155,7 @@ Next we will discuss various ways of including the CSRF token in a form as a hid
===== Automatic CSRF Token Inclusion
Spring Security's CSRF support provides integration with Spring's https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/web/servlet/support/RequestDataValueProcessor.html[RequestDataValueProcessor] via its https://docs.spring.io/spring-security/site/docs/current/api/org/springframework/security/web/servlet/support/csrf/CsrfRequestDataValueProcessor.html[CsrfRequestDataValueProcessor].
-This means that if you leverage https://docs.spring.io/spring/docs/current/spring-framework-reference/web.html#mvc-view-jsp-formtaglib[Spring’s form tag library], https://www.thymeleaf.org/doc/tutorials/2.1/thymeleafspring.html#integration-with-requestdatavalueprocessor[Thymleaf], or any other view technology that integrates with `RequestDataValueProcessor`, then forms that have an unsafe HTTP method (i.e. post) will automatically include the actual CSRF token.
+This means that if you leverage https://docs.spring.io/spring/docs/current/spring-framework-reference/web.html#mvc-view-jsp-formtaglib[Spring’s form tag library], https://www.thymeleaf.org/doc/tutorials/2.1/thymeleafspring.html#integration-with-requestdatavalueprocessor[Thymeleaf], or any other view technology that integrates with `RequestDataValueProcessor`, then forms that have an unsafe HTTP method (i.e. post) will automatically include the actual CSRF token.
[[servlet-csrf-include-form-tag]]
===== csrfInput Tag
diff --git a/docs/manual/src/docs/asciidoc/_includes/servlet/integrations/jackson.adoc b/docs/manual/src/docs/asciidoc/_includes/servlet/integrations/jackson.adoc
index 247fc8e6b86..9e6418a1757 100644
--- a/docs/manual/src/docs/asciidoc/_includes/servlet/integrations/jackson.adoc
+++ b/docs/manual/src/docs/asciidoc/_includes/servlet/integrations/jackson.adoc
@@ -1,10 +1,10 @@
[[jackson]]
== Jackson Support
-Spring Security has added Jackson Support for persisting Spring Security related classes.
+Spring Security provides Jackson support for persisting Spring Security related classes.
This can improve the performance of serializing Spring Security related classes when working with distributed sessions (i.e. session replication, Spring Session, etc).
-To use it, register the `SecurityJackson2Modules.getModules(ClassLoader)` as https://wiki.fasterxml.com/JacksonFeatureModules[Jackson Modules].
+To use it, register the `SecurityJackson2Modules.getModules(ClassLoader)` with `ObjectMapper` (https://github.com/FasterXML/jackson-databind[jackson-databind]):
[source,java]
----
@@ -18,3 +18,13 @@ SecurityContext context = new SecurityContextImpl();
// ...
String json = mapper.writeValueAsString(context);
----
+
+[NOTE]
+====
+The following Spring Security modules provide Jackson support:
+
+- spring-security-core (`CoreJackson2Module`)
+- spring-security-web (`WebJackson2Module`, `WebServletJackson2Module`, `WebServerJackson2Module`)
+- <> (`OAuth2ClientJackson2Module`)
+- spring-security-cas (`CasJackson2Module`)
+====
diff --git a/docs/manual/src/docs/asciidoc/_includes/servlet/integrations/jsp-taglibs.adoc b/docs/manual/src/docs/asciidoc/_includes/servlet/integrations/jsp-taglibs.adoc
index f4a0a3ef3c8..3f316832582 100644
--- a/docs/manual/src/docs/asciidoc/_includes/servlet/integrations/jsp-taglibs.adoc
+++ b/docs/manual/src/docs/asciidoc/_includes/servlet/integrations/jsp-taglibs.adoc
@@ -30,7 +30,7 @@ This content will only be visible to users who have the "supervisor" authority i
----
-When used in conjuction with Spring Security's PermissionEvaluator, the tag can also be used to check permissions.
+When used in conjunction with Spring Security's PermissionEvaluator, the tag can also be used to check permissions.
For example:
[source,xml]
diff --git a/docs/manual/src/docs/asciidoc/_includes/servlet/integrations/localization.adoc b/docs/manual/src/docs/asciidoc/_includes/servlet/integrations/localization.adoc
index 6b415e23a7a..0f8f9b402ee 100644
--- a/docs/manual/src/docs/asciidoc/_includes/servlet/integrations/localization.adoc
+++ b/docs/manual/src/docs/asciidoc/_includes/servlet/integrations/localization.adoc
@@ -5,7 +5,7 @@ If your application is designed for English-speaking users, you don't need to do
If you need to support other locales, everything you need to know is contained in this section.
All exception messages can be localized, including messages related to authentication failures and access being denied (authorization failures).
-Exceptions and logging messages that are focused on developers or system deployers (including incorrect attributes, interface contract violations, using incorrect constructors, startup time validation, debug-level logging) are not localized and instead are hard-coded in English within Spring Security's code.
+Exceptions and logging messages that are focused on developers or system deplopers (including incorrect attributes, interface contract violations, using incorrect constructors, startup time validation, debug-level logging) are not localized and instead are hard-coded in English within Spring Security's code.
Shipping in the `spring-security-core-xx.jar` you will find an `org.springframework.security` package that in turn contains a `messages.properties` file, as well as localized versions for some common languages.
This should be referred to by your `ApplicationContext`, as Spring Security classes implement Spring's `MessageSourceAware` interface and expect the message resolver to be dependency injected at application context startup time.
diff --git a/docs/manual/src/docs/asciidoc/_includes/servlet/integrations/mvc.adoc b/docs/manual/src/docs/asciidoc/_includes/servlet/integrations/mvc.adoc
index 6b1f36558e6..7b7b8fcce02 100644
--- a/docs/manual/src/docs/asciidoc/_includes/servlet/integrations/mvc.adoc
+++ b/docs/manual/src/docs/asciidoc/_includes/servlet/integrations/mvc.adoc
@@ -221,7 +221,7 @@ import org.springframework.security.core.annotation.AuthenticationPrincipal;
@RequestMapping("/messages/inbox")
public ModelAndView findMessagesForUser(@AuthenticationPrincipal(expression = "customUser") CustomUser customUser) {
- // .. find messags for this user and return them ...
+ // .. find messages for this user and return them ...
}
----
@@ -356,7 +356,7 @@ Will output HTML that is similar to the following:
Spring Security provides `CsrfTokenArgumentResolver` which can automatically resolve the current `CsrfToken` for Spring MVC arguments.
By using <> you will automatically have this added to your Spring MVC configuration.
-If you use XML based configuraiton, you must add this yourself.
+If you use XML based configuration, you must add this yourself.
Once `CsrfTokenArgumentResolver` is properly configured, you can expose the `CsrfToken` to your static HTML based application.
diff --git a/docs/manual/src/docs/asciidoc/_includes/servlet/integrations/servlet-api.adoc b/docs/manual/src/docs/asciidoc/_includes/servlet/integrations/servlet-api.adoc
index 6c75fd305a5..9e957f39a08 100644
--- a/docs/manual/src/docs/asciidoc/_includes/servlet/integrations/servlet-api.adoc
+++ b/docs/manual/src/docs/asciidoc/_includes/servlet/integrations/servlet-api.adoc
@@ -96,7 +96,7 @@ Typically this would involve a redirect to the welcome page.
[[servletapi-start-runnable]]
==== AsyncContext.start(Runnable)
-The https://docs.oracle.com/javaee/6/api/javax/servlet/AsyncContext.html#start%28java.lang.Runnable%29[AsynchContext.start(Runnable)] method that ensures your credentials will be propagated to the new Thread.
+The https://docs.oracle.com/javaee/6/api/javax/servlet/AsyncContext.html#start%28java.lang.Runnable%29[AsyncContext.start(Runnable)] method that ensures your credentials will be propagated to the new Thread.
Using Spring Security's concurrency support, Spring Security overrides the AsyncContext.start(Runnable) to ensure that the current SecurityContext is used when processing the Runnable.
For example, the following would output the current user's Authentication:
@@ -182,10 +182,10 @@ new Thread("AsyncThread") {
----
The issue is that this Thread is not known to Spring Security, so the SecurityContext is not propagated to it.
-This means when we commit the HttpServletResponse there is no SecuriytContext.
+This means when we commit the HttpServletResponse there is no SecurityContext.
When Spring Security automatically saved the SecurityContext on committing the HttpServletResponse it would lose our logged in user.
-Since version 3.2, Spring Security is smart enough to no longer automatically save the SecurityContext on commiting the HttpServletResponse as soon as HttpServletRequest.startAsync() is invoked.
+Since version 3.2, Spring Security is smart enough to no longer automatically save the SecurityContext on committing the HttpServletResponse as soon as HttpServletRequest.startAsync() is invoked.
[[servletapi-31]]
=== Servlet 3.1+ Integration
diff --git a/docs/manual/src/docs/asciidoc/_includes/servlet/integrations/websocket.adoc b/docs/manual/src/docs/asciidoc/_includes/servlet/integrations/websocket.adoc
index beb39fa04f2..fcd99840d90 100644
--- a/docs/manual/src/docs/asciidoc/_includes/servlet/integrations/websocket.adoc
+++ b/docs/manual/src/docs/asciidoc/_includes/servlet/integrations/websocket.adoc
@@ -4,7 +4,7 @@
Spring Security 4 added support for securing https://docs.spring.io/spring/docs/current/spring-framework-reference/html/websocket.html[Spring's WebSocket support].
This section describes how to use Spring Security's WebSocket support.
-NOTE: You can find a complete working sample of WebSocket security at https://github.com/spring-projects/spring-session/tree/master/samples/boot/websocket.
+NOTE: You can find a complete working sample of WebSocket security at https://github.com/spring-projects/spring-session/tree/master/spring-session-samples/spring-session-sample-boot-websocket.
.Direct JSR-356 Support
****
diff --git a/docs/manual/src/docs/asciidoc/_includes/servlet/java-configuration/index.adoc b/docs/manual/src/docs/asciidoc/_includes/servlet/java-configuration/index.adoc
index 8792c034320..8b762451229 100644
--- a/docs/manual/src/docs/asciidoc/_includes/servlet/java-configuration/index.adoc
+++ b/docs/manual/src/docs/asciidoc/_includes/servlet/java-configuration/index.adoc
@@ -284,7 +284,7 @@ The code is invoked in the following order:
* Code in `MyCustomDsl`s init method is invoked
* Code in `MyCustomDsl`s configure method is invoked
-If you want, you can have `WebSecurityConfiguerAdapter` add `MyCustomDsl` by default by using `SpringFactories`.
+If you want, you can have `WebSecurityConfigurerAdapter` add `MyCustomDsl` by default by using `SpringFactories`.
For example, you would create a resource on the classpath named `META-INF/spring.factories` with the following contents:
.META-INF/spring.factories
diff --git a/docs/manual/src/docs/asciidoc/_includes/servlet/kotlin-configuration/index.adoc b/docs/manual/src/docs/asciidoc/_includes/servlet/kotlin-configuration/index.adoc
index ad02b592fec..2e38e0ec35a 100644
--- a/docs/manual/src/docs/asciidoc/_includes/servlet/kotlin-configuration/index.adoc
+++ b/docs/manual/src/docs/asciidoc/_includes/servlet/kotlin-configuration/index.adoc
@@ -4,7 +4,7 @@
Spring Security Kotlin Configuration support has been available since Spring Security 5.3.
It enables users to easily configure Spring Security using a native Kotlin DSL.
-NOTE: Spring Security provides https://github.com/spring-projects/spring-security/tree/master/samples/boot/kotlin[a sample applications] which demonstrates the use of Spring Security Kotlin Configuration.
+NOTE: Spring Security provides https://github.com/spring-projects/spring-security/tree/master/samples/boot/kotlin[a sample application] which demonstrates the use of Spring Security Kotlin Configuration.
[[kotlin-config-httpsecurity]]
== HttpSecurity
diff --git a/docs/manual/src/docs/asciidoc/_includes/servlet/oauth2/oauth2-client.adoc b/docs/manual/src/docs/asciidoc/_includes/servlet/oauth2/oauth2-client.adoc
index d122839a896..c4ae5ac8ceb 100644
--- a/docs/manual/src/docs/asciidoc/_includes/servlet/oauth2/oauth2-client.adoc
+++ b/docs/manual/src/docs/asciidoc/_includes/servlet/oauth2/oauth2-client.adoc
@@ -68,6 +68,27 @@ class OAuth2ClientSecurityConfig : WebSecurityConfigurerAdapter() {
----
====
+In addition to the `HttpSecurity.oauth2Client()` DSL, XML configuration is also supported.
+
+The following code shows the complete configuration options available in the <>:
+
+.OAuth2 Client XML Configuration Options
+====
+[source,xml]
+----
+
+
+
+
+
+----
+====
+
The `OAuth2AuthorizedClientManager` is responsible for managing the authorization (or re-authorization) of an OAuth 2.0 Client, in collaboration with one or more `OAuth2AuthorizedClientProvider`(s).
The following code shows an example of how to register an `OAuth2AuthorizedClientManager` `@Bean` and associate it with an `OAuth2AuthorizedClientProvider` composite that provides support for the `authorization_code`, `refresh_token`, `client_credentials` and `password` authorization grant types:
@@ -145,12 +166,13 @@ public final class ClientRegistration {
private String tokenUri; <10>
private UserInfoEndpoint userInfoEndpoint;
private String jwkSetUri; <11>
- private Map configurationMetadata; <12>
+ private String issuerUri; <12>
+ private Map configurationMetadata; <13>
public class UserInfoEndpoint {
- private String uri; <13>
- private AuthenticationMethod authenticationMethod; <14>
- private String userNameAttributeName; <15>
+ private String uri; <14>
+ private AuthenticationMethod authenticationMethod; <15>
+ private String userNameAttributeName; <16>
}
}
@@ -172,12 +194,13 @@ The name may be used in certain scenarios, such as when displaying the name of t
<10> `tokenUri`: The Token Endpoint URI for the Authorization Server.
<11> `jwkSetUri`: The URI used to retrieve the https://tools.ietf.org/html/rfc7517[JSON Web Key (JWK)] Set from the Authorization Server,
which contains the cryptographic key(s) used to verify the https://tools.ietf.org/html/rfc7515[JSON Web Signature (JWS)] of the ID Token and optionally the UserInfo Response.
-<12> `configurationMetadata`: The https://openid.net/specs/openid-connect-discovery-1_0.html#ProviderConfig[OpenID Provider Configuration Information].
+<12> `issuerUri`: Returns the issuer identifier uri for the OpenID Connect 1.0 provider or the OAuth 2.0 Authorization Server.
+<13> `configurationMetadata`: The https://openid.net/specs/openid-connect-discovery-1_0.html#ProviderConfig[OpenID Provider Configuration Information].
This information will only be available if the Spring Boot 2.x property `spring.security.oauth2.client.provider.[providerId].issuerUri` is configured.
-<13> `(userInfoEndpoint)uri`: The UserInfo Endpoint URI used to access the claims/attributes of the authenticated end-user.
-<14> `(userInfoEndpoint)authenticationMethod`: The authentication method used when sending the access token to the UserInfo Endpoint.
+<14> `(userInfoEndpoint)uri`: The UserInfo Endpoint URI used to access the claims/attributes of the authenticated end-user.
+<15> `(userInfoEndpoint)authenticationMethod`: The authentication method used when sending the access token to the UserInfo Endpoint.
The supported values are *header*, *form* and *query*.
-<15> `userNameAttributeName`: The name of the attribute returned in the UserInfo Response that references the Name or Identifier of the end-user.
+<16> `userNameAttributeName`: The name of the attribute returned in the UserInfo Response that references the Name or Identifier of the end-user.
A `ClientRegistration` can be initially configured using discovery of an OpenID Connect Provider's https://openid.net/specs/openid-connect-discovery-1_0.html#ProviderConfig[Configuration endpoint] or an Authorization Server's https://tools.ietf.org/html/rfc8414#section-3[Metadata endpoint].
@@ -294,6 +317,8 @@ The primary responsibilities include:
* Authorizing (or re-authorizing) an OAuth 2.0 Client, using an `OAuth2AuthorizedClientProvider`.
* Delegating the persistence of an `OAuth2AuthorizedClient`, typically using an `OAuth2AuthorizedClientService` or `OAuth2AuthorizedClientRepository`.
+* Delegating to an `OAuth2AuthorizationSuccessHandler` when an OAuth 2.0 Client has been successfully authorized (or re-authorized).
+* Delegating to an `OAuth2AuthorizationFailureHandler` when an OAuth 2.0 Client fails to authorize (or re-authorize).
An `OAuth2AuthorizedClientProvider` implements a strategy for authorizing (or re-authorizing) an OAuth 2.0 Client.
Implementations will typically implement an authorization grant type, eg. `authorization_code`, `client_credentials`, etc.
@@ -327,6 +352,10 @@ public OAuth2AuthorizedClientManager authorizedClientManager(
}
----
+When an authorization attempt succeeds, the `DefaultOAuth2AuthorizedClientManager` will delegate to the `OAuth2AuthorizationSuccessHandler`, which (by default) will save the `OAuth2AuthorizedClient` via the `OAuth2AuthorizedClientRepository`.
+In the case of a re-authorization failure, eg. a refresh token is no longer valid, the previously saved `OAuth2AuthorizedClient` will be removed from the `OAuth2AuthorizedClientRepository` via the `RemoveAuthorizedClientOAuth2AuthorizationFailureHandler`.
+The default behaviour may be customized via `setAuthorizationSuccessHandler(OAuth2AuthorizationSuccessHandler)` and `setAuthorizationFailureHandler(OAuth2AuthorizationFailureHandler)`.
+
The `DefaultOAuth2AuthorizedClientManager` is also associated with a `contextAttributesMapper` of type `Function>`, which is responsible for mapping attribute(s) from the `OAuth2AuthorizeRequest` to a `Map` of attributes to be associated to the `OAuth2AuthorizationContext`.
This can be useful when you need to supply an `OAuth2AuthorizedClientProvider` with required (supported) attribute(s), eg. the `PasswordOAuth2AuthorizedClientProvider` requires the resource owner's `username` and `password` to be available in `OAuth2AuthorizationContext.getAttributes()`.
@@ -375,6 +404,36 @@ private Function> contextAttributesM
}
----
+The `DefaultOAuth2AuthorizedClientManager` is designed to be used *_within_* the context of a `HttpServletRequest`.
+When operating *_outside_* of a `HttpServletRequest` context, use `AuthorizedClientServiceOAuth2AuthorizedClientManager` instead.
+
+A _service application_ is a common use case for when to use an `AuthorizedClientServiceOAuth2AuthorizedClientManager`.
+Service applications often run in the background, without any user interaction, and typically run under a system-level account instead of a user account.
+An OAuth 2.0 Client configured with the `client_credentials` grant type can be considered a type of service application.
+
+The following code shows an example of how to configure an `AuthorizedClientServiceOAuth2AuthorizedClientManager` that provides support for the `client_credentials` grant type:
+
+[source,java]
+----
+@Bean
+public OAuth2AuthorizedClientManager authorizedClientManager(
+ ClientRegistrationRepository clientRegistrationRepository,
+ OAuth2AuthorizedClientService authorizedClientService) {
+
+ OAuth2AuthorizedClientProvider authorizedClientProvider =
+ OAuth2AuthorizedClientProviderBuilder.builder()
+ .clientCredentials()
+ .build();
+
+ AuthorizedClientServiceOAuth2AuthorizedClientManager authorizedClientManager =
+ new AuthorizedClientServiceOAuth2AuthorizedClientManager(
+ clientRegistrationRepository, authorizedClientService);
+ authorizedClientManager.setAuthorizedClientProvider(authorizedClientProvider);
+
+ return authorizedClientManager;
+}
+----
+
[[oauth2Client-auth-grant-support]]
=== Authorization Grant Support
@@ -484,7 +543,7 @@ One of those extended parameters is the `prompt` parameter.
[NOTE]
OPTIONAL. Space delimited, case sensitive list of ASCII string values that specifies whether the Authorization Server prompts the End-User for reauthentication and consent. The defined values are: none, login, consent, select_account
-The following example shows how to implement an `OAuth2AuthorizationRequestResolver` that customizes the Authorization Request for `oauth2Login()`, by including the request parameter `prompt=consent`.
+The following example shows how to configure the `DefaultOAuth2AuthorizationRequestResolver` with a `Consumer` that customizes the Authorization Request for `oauth2Login()`, by including the request parameter `prompt=consent`.
[source,java]
----
@@ -503,72 +562,32 @@ public class OAuth2LoginSecurityConfig extends WebSecurityConfigurerAdapter {
.oauth2Login(oauth2 -> oauth2
.authorizationEndpoint(authorization -> authorization
.authorizationRequestResolver(
- new CustomAuthorizationRequestResolver(
- this.clientRegistrationRepository) <1>
+ authorizationRequestResolver(this.clientRegistrationRepository)
)
)
);
}
-}
-
-public class CustomAuthorizationRequestResolver implements OAuth2AuthorizationRequestResolver {
- private final OAuth2AuthorizationRequestResolver defaultAuthorizationRequestResolver;
- public CustomAuthorizationRequestResolver(
+ private OAuth2AuthorizationRequestResolver authorizationRequestResolver(
ClientRegistrationRepository clientRegistrationRepository) {
- this.defaultAuthorizationRequestResolver =
+ DefaultOAuth2AuthorizationRequestResolver authorizationRequestResolver =
new DefaultOAuth2AuthorizationRequestResolver(
clientRegistrationRepository, "/oauth2/authorization");
- }
-
- @Override
- public OAuth2AuthorizationRequest resolve(HttpServletRequest request) {
- OAuth2AuthorizationRequest authorizationRequest =
- this.defaultAuthorizationRequestResolver.resolve(request); <2>
-
- return authorizationRequest != null ? <3>
- customAuthorizationRequest(authorizationRequest) :
- null;
- }
+ authorizationRequestResolver.setAuthorizationRequestCustomizer(
+ authorizationRequestCustomizer());
- @Override
- public OAuth2AuthorizationRequest resolve(
- HttpServletRequest request, String clientRegistrationId) {
-
- OAuth2AuthorizationRequest authorizationRequest =
- this.defaultAuthorizationRequestResolver.resolve(
- request, clientRegistrationId); <2>
-
- return authorizationRequest != null ? <3>
- customAuthorizationRequest(authorizationRequest) :
- null;
+ return authorizationRequestResolver;
}
- private OAuth2AuthorizationRequest customAuthorizationRequest(
- OAuth2AuthorizationRequest authorizationRequest) {
-
- Map additionalParameters =
- new LinkedHashMap<>(authorizationRequest.getAdditionalParameters());
- additionalParameters.put("prompt", "consent"); <4>
-
- return OAuth2AuthorizationRequest.from(authorizationRequest) <5>
- .additionalParameters(additionalParameters) <6>
- .build();
+ private Consumer authorizationRequestCustomizer() {
+ return customizer -> customizer
+ .additionalParameters(params -> params.put("prompt", "consent"));
}
}
----
-<1> Configure the custom `OAuth2AuthorizationRequestResolver`
-<2> Attempt to resolve the `OAuth2AuthorizationRequest` using the `DefaultOAuth2AuthorizationRequestResolver`
-<3> If an `OAuth2AuthorizationRequest` was resolved than return a customized version else return `null`
-<4> Add custom parameters to the existing `OAuth2AuthorizationRequest.additionalParameters`
-<5> Create a copy of the default `OAuth2AuthorizationRequest` which returns an `OAuth2AuthorizationRequest.Builder` for further modifications
-<6> Override the default `additionalParameters`
-[TIP]
-`OAuth2AuthorizationRequest.Builder.build()` constructs the `OAuth2AuthorizationRequest.authorizationRequestUri`, which represents the complete Authorization Request URI including all query parameters using the `application/x-www-form-urlencoded` format.
-
-For the simple use case, where the additional request parameter is always the same for a specific provider, it can be added directly in the `authorization-uri`.
+For the simple use case, where the additional request parameter is always the same for a specific provider, it may be added directly in the `authorization-uri` property.
For example, if the value for the request parameter `prompt` is always `consent` for the provider `okta`, than simply configure as follows:
@@ -584,24 +603,19 @@ spring:
----
The preceding example shows the common use case of adding a custom parameter on top of the standard parameters.
-Alternatively, if your requirements are more advanced, than you can take full control in building the Authorization Request URI by simply overriding the `OAuth2AuthorizationRequest.authorizationRequestUri` property.
+Alternatively, if your requirements are more advanced, you can take full control in building the Authorization Request URI by simply overriding the `OAuth2AuthorizationRequest.authorizationRequestUri` property.
+
+[TIP]
+`OAuth2AuthorizationRequest.Builder.build()` constructs the `OAuth2AuthorizationRequest.authorizationRequestUri`, which represents the Authorization Request URI including all query parameters using the `application/x-www-form-urlencoded` format.
-The following example shows a variation of the `customAuthorizationRequest()` method from the preceding example, and instead overrides the `OAuth2AuthorizationRequest.authorizationRequestUri` property.
+The following example shows a variation of `authorizationRequestCustomizer()` from the preceding example, and instead overrides the `OAuth2AuthorizationRequest.authorizationRequestUri` property.
[source,java]
----
-private OAuth2AuthorizationRequest customAuthorizationRequest(
- OAuth2AuthorizationRequest authorizationRequest) {
-
- String customAuthorizationRequestUri = UriComponentsBuilder
- .fromUriString(authorizationRequest.getAuthorizationRequestUri())
- .queryParam("prompt", "consent")
- .build(true)
- .toUriString();
-
- return OAuth2AuthorizationRequest.from(authorizationRequest)
- .authorizationRequestUri(customAuthorizationRequestUri)
- .build();
+private Consumer authorizationRequestCustomizer() {
+ return customizer -> customizer
+ .authorizationRequestUri(uriBuilder -> uriBuilder
+ .queryParam("prompt", "consent").build());
}
----
@@ -655,8 +669,17 @@ class OAuth2ClientSecurityConfig : WebSecurityConfigurerAdapter() {
}
}
----
-====
+.Xml
+[source,xml,role="secondary"]
+----
+
+
+
+
+
+----
+====
===== Requesting an Access Token
@@ -739,6 +762,16 @@ class OAuth2ClientSecurityConfig : WebSecurityConfigurerAdapter() {
}
}
----
+
+.Xml
+[source,xml,role="secondary"]
+----
+
+
+
+
+
+----
====
diff --git a/docs/manual/src/docs/asciidoc/_includes/servlet/oauth2/oauth2-login.adoc b/docs/manual/src/docs/asciidoc/_includes/servlet/oauth2/oauth2-login.adoc
index 6badf5a7966..fbaff1ee2b2 100644
--- a/docs/manual/src/docs/asciidoc/_includes/servlet/oauth2/oauth2-login.adoc
+++ b/docs/manual/src/docs/asciidoc/_includes/servlet/oauth2/oauth2-login.adoc
@@ -131,14 +131,16 @@ The following table outlines the mapping of the Spring Boot 2.x OAuth Client pro
|`spring.security.oauth2.client.provider._[providerId]_.jwk-set-uri`
|`providerDetails.jwkSetUri`
+|`spring.security.oauth2.client.provider._[providerId]_.issuer-uri`
+|`providerDetails.issuerUri`
+
|`spring.security.oauth2.client.provider._[providerId]_.user-info-uri`
|`providerDetails.userInfoEndpoint.uri`
|`spring.security.oauth2.client.provider._[providerId]_.user-info-authentication-method`
|`providerDetails.userInfoEndpoint.authenticationMethod`
-
-|`spring.security.oauth2.client.provider._[providerId]_.userNameAttribute`
+|`spring.security.oauth2.client.provider._[providerId]_.user-name-attribute`
|`providerDetails.userInfoEndpoint.userNameAttributeName`
|===
diff --git a/docs/manual/src/docs/asciidoc/_includes/servlet/oauth2/oauth2-resourceserver.adoc b/docs/manual/src/docs/asciidoc/_includes/servlet/oauth2/oauth2-resourceserver.adoc
index ae338e60e07..f3844ed84ae 100644
--- a/docs/manual/src/docs/asciidoc/_includes/servlet/oauth2/oauth2-resourceserver.adoc
+++ b/docs/manual/src/docs/asciidoc/_includes/servlet/oauth2/oauth2-resourceserver.adoc
@@ -1,5 +1,7 @@
[[oauth2resourceserver]]
== OAuth 2.0 Resource Server
+:figures: images/servlet/oauth2
+:icondir: images/icons
Spring Security supports protecting endpoints using two forms of OAuth 2.0 https://tools.ietf.org/html/rfc6750.html[Bearer Tokens]:
@@ -9,12 +11,55 @@ Spring Security supports protecting endpoints using two forms of OAuth 2.0 https
This is handy in circumstances where an application has delegated its authority management to an https://tools.ietf.org/html/rfc6749[authorization server] (for example, Okta or Ping Identity).
This authorization server can be consulted by resource servers to authorize requests.
+This section provides details on how Spring Security provides support for OAuth 2.0 https://tools.ietf.org/html/rfc6750.html[Bearer Tokens].
+
[NOTE]
====
Working samples for both {gh-samples-url}/boot/oauth2resourceserver[JWTs] and {gh-samples-url}/boot/oauth2resourceserver-opaque[Opaque Tokens] are available in the {gh-samples-url}[Spring Security repository].
====
-=== Dependencies
+Let's take a look at how Bearer Token Authentication works within Spring Security.
+First, we see that, like <>, the https://tools.ietf.org/html/rfc7235#section-4.1[WWW-Authenticate] header is sent back to an unauthenticated client.
+
+.Sending WWW-Authenticate Header
+image::{figures}/bearerauthenticationentrypoint.png[]
+
+The figure above builds off our <> diagram.
+
+image:{icondir}/number_1.png[] First, a user makes an unauthenticated request to the resource `/private` for which it is not authorized.
+
+image:{icondir}/number_2.png[] Spring Security's <> indicates that the unauthenticated request is __Denied__ by throwing an `AccessDeniedException`.
+
+image:{icondir}/number_3.png[] Since the user is not authenticated, <> initiates __Start Authentication__.
+The configured <> is an instance of {security-api-url}org/springframework/security/oauth2/server/resource/authentication/BearerTokenAuthenticationEntryPoint.html[`BearerTokenAuthenticationEntryPoint`] which sends a WWW-Authenticate header.
+The `RequestCache` is typically a `NullRequestCache` that does not save the request since the client is capable of replaying the requests it originally requested.
+
+When a client receives the `WWW-Authenticate: Bearer` header, it knows it should retry with a bearer token.
+Below is the flow for the bearer token being processed.
+
+[[oauth2resourceserver-authentication-bearertokenauthenticationfilter]]
+.Authenticating Bearer Token
+image::{figures}/bearertokenauthenticationfilter.png[]
+
+The figure builds off our <> diagram.
+
+image:{icondir}/number_1.png[] When the user submits their bearer token, the `BearerTokenAuthenticationFilter` creates a `BearerTokenAuthenticationToken` which is a type of <> by extracting the token from the `HttpServletRequest`.
+
+image:{icondir}/number_2.png[] Next, the `HttpServletRequest` is passed to the `AuthenticationManagerResolver`, which selects the `AuthenticationManager`. The `BearerTokenAuthenticationToken` is passed into the `AuthenticationManager` to be authenticated.
+The details of what `AuthenticationManager` looks like depends on whether you're configured for <> or <>.
+
+image:{icondir}/number_3.png[] If authentication fails, then __Failure__
+
+* The <> is cleared out.
+* The `AuthenticationEntryPoint` is invoked to trigger the WWW-Authenticate header to be sent again.
+
+image:{icondir}/number_4.png[] If authentication is successful, then __Success__.
+
+* The <> is set on the <>.
+* The `BearerTokenAuthenticationFilter` invokes `FilterChain.doFilter(request,response)` to continue with the rest of the application logic.
+
+[[oauth2resourceserver-jwt-minimaldependencies]]
+=== Minimal Dependencies for JWT
Most Resource Server support is collected into `spring-security-oauth2-resource-server`.
However, the support for decoding and verifying JWTs is in `spring-security-oauth2-jose`, meaning that both are necessary in order to have a working resource server that supports JWT-encoded Bearer Tokens.
@@ -77,20 +122,46 @@ So long as this scheme is indicated, Resource Server will attempt to process the
Given a well-formed JWT, Resource Server will:
-1. Validate its signature against a public key obtained from the `jwks_url` endpoint during startup and matched against the JWTs header
-2. Validate the JWTs `exp` and `nbf` timestamps and the JWTs `iss` claim, and
+1. Validate its signature against a public key obtained from the `jwks_url` endpoint during startup and matched against the JWT
+2. Validate the JWT's `exp` and `nbf` timestamps and the JWT's `iss` claim, and
3. Map each scope to an authority with the prefix `SCOPE_`.
[NOTE]
-As the authorization server makes available new keys, Spring Security will automatically rotate the keys used to validate the JWT tokens.
+As the authorization server makes available new keys, Spring Security will automatically rotate the keys used to validate JWTs.
The resulting `Authentication#getPrincipal`, by default, is a Spring Security `Jwt` object, and `Authentication#getName` maps to the JWT's `sub` property, if one is present.
From here, consider jumping to:
-<>
+* <>
+* <>
+* <>
+
+[[oauth2resourceserver-jwt-architecture]]
+=== How JWT Authentication Works
+
+Next, let's see the architectural components that Spring Security uses to support https://tools.ietf.org/html/rfc7519[JWT] Authentication in servlet-based applications, like the one we just saw.
-<>
+{security-api-url}org/springframework/security/oauth2/server/resource/authentication/JwtAuthenticationProvider.html[`JwtAuthenticationProvider`] is an <> implementation that leverages a <> and <> to authenticate a JWT.
+
+Let's take a look at how `JwtAuthenticationProvider` works within Spring Security.
+The figure explains details of how the <> in figures from <> works.
+
+.`JwtAuthenticationProvider` Usage
+image::{figures}/jwtauthenticationprovider.png[]
+
+image:{icondir}/number_1.png[] The authentication `Filter` from <> passes a `BearerTokenAuthenticationToken` to the `AuthenticationManager` which is implemented by <>.
+
+image:{icondir}/number_2.png[] The `ProviderManager` is configured to use an <> of type `JwtAuthenticationProvider`.
+
+[[oauth2resourceserver-jwt-architecture-jwtdecoder]]
+image:{icondir}/number_3.png[] `JwtAuthenticationProvider` decodes, verifies, and validates the `Jwt` using a <>.
+
+[[oauth2resourceserver-jwt-architecture-jwtauthenticationconverter]]
+image:{icondir}/number_4.png[] `JwtAuthenticationProvider` then uses the <> to convert the `Jwt` into a `Collection` of granted authorities.
+
+image:{icondir}/number_5.png[] When authentication is successful, the <> that is returned is of type `JwtAuthenticationToken` and has a principal that is the `Jwt` returned by the configured `JwtDecoder`.
+Ultimately, the returned `JwtAuthenticationToken` will be set on the <> by the authentication `Filter`.
[[oauth2resourceserver-jwt-jwkseturi]]
=== Specifying the Authorization Server JWK Set Uri Directly
@@ -206,8 +277,8 @@ The above requires the scope of `message:read` for any URL that starts with `/me
Methods on the `oauth2ResourceServer` DSL will also override or replace auto configuration.
-For example, the second `@Bean` Spring Boot creates is a `JwtDecoder`, which decodes `String` tokens into validated instances of `Jwt`:
-
+[[oauth2resourceserver-jwt-decoder]]
+For example, the second `@Bean` Spring Boot creates is a `JwtDecoder`, which <>:
.JWT Decoder
====
@@ -323,7 +394,7 @@ Using `jwkSetUri()` takes precedence over any configuration property.
[[oauth2resourceserver-jwt-decoder-dsl]]
==== Using `decoder()`
-More powerful than `jwkSetUri()` is `decoder()`, which will completely replace any Boot auto configuration of `JwtDecoder`:
+More powerful than `jwkSetUri()` is `decoder()`, which will completely replace any Boot auto configuration of <>:
.JWT Decoder Configuration
====
@@ -383,7 +454,7 @@ This is handy when deeper configuration, like <> `@Bean` has the same effect as `decoder()`:
[source,java]
----
@@ -629,65 +700,30 @@ However, there are a number of circumstances where this default is insufficient.
For example, some authorization servers don't use the `scope` attribute, but instead have their own custom attribute.
Or, at other times, the resource server may need to adapt the attribute or a composition of attributes into internalized authorities.
-To this end, the DSL exposes `jwtAuthenticationConverter()`:
+To this end, Spring Security ships with `JwtAuthenticationConverter`, which is responsible for <>.
+By default, Spring Security will wire the `JwtAuthenticationProvider` with a default instance of `JwtAuthenticationConverter`.
+
+As part of configuring a `JwtAuthenticationConverter`, you can supply a subsidiary converter to go from `Jwt` to a `Collection` of granted authorities.
-.Authorities Extractor Configuration
+Let's say that that your authorization server communicates authorities in a custom claim called `authorities`.
+In that case, you can configure the claim that <> should inspect, like so:
+
+.Authorities Claim Configuration
====
.Java
[source,java,role="primary"]
----
-@EnableWebSecurity
-public class DirectlyConfiguredJwkSetUri extends WebSecurityConfigurerAdapter {
- protected void configure(HttpSecurity http) {
- http
- .authorizeRequests(authorize -> authorize
- .anyRequest().authenticated()
- )
- .oauth2ResourceServer(oauth2 -> oauth2
- .jwt(jwt -> jwt
- .jwtAuthenticationConverter(grantedAuthoritiesExtractor())
- )
- );
- }
-}
-
-Converter grantedAuthoritiesExtractor() {
- JwtAuthenticationConverter jwtAuthenticationConverter =
- new JwtAuthenticationConverter();
-
- jwtAuthenticationConverter.setJwtGrantedAuthoritiesConverter
- (new GrantedAuthoritiesExtractor());
+@Bean
+public JwtAuthenticationConverter jwtAuthenticationConverter() {
+ JwtGrantedAuthoritiesConverter grantedAuthoritiesConverter = new JwtGrantedAuthoritiesConverter();
+ grantedAuthoritiesConverter.setAuthoritiesClaimName("authorities");
+ JwtAuthenticationConverter authenticationConverter = new JwtAuthenticationConverter();
+ jwtAuthenticationConverter.setJwtGrantedAuthoritiesConverter(authoritiesConverter);
return jwtAuthenticationConverter;
}
----
-.Kotlin
-[source,kotlin,role="secondary"]
-----
-@EnableWebSecurity
-class DirectlyConfiguredJwkSetUri : WebSecurityConfigurerAdapter() {
- override fun configure(http: HttpSecurity) {
- http {
- authorizeRequests {
- authorize(anyRequest, authenticated)
- }
- oauth2ResourceServer {
- jwt {
- jwtAuthenticationConverter = grantedAuthoritiesExtractor()
- }
- }
- }
- }
-
- private fun grantedAuthoritiesExtractor(): JwtAuthenticationConverter {
- val jwtAuthenticationConverter = JwtAuthenticationConverter()
- jwtAuthenticationConverter.setJwtGrantedAuthoritiesConverter(GrantedAuthoritiesExtractor())
- return jwtAuthenticationConverter
- }
-}
-----
-
.Xml
[source,xml,role="secondary"]
----
@@ -696,40 +732,66 @@ class DirectlyConfiguredJwkSetUri : WebSecurityConfigurerAdapter() {
+ jwt-authentication-converter-ref="jwtAuthenticationConverter"/>
-
+
+
+
----
====
-which is responsible for converting a `Jwt` into an `Authentication`.
-As part of its configuration, we can supply a subsidiary converter to go from `Jwt` to a `Collection` of granted authorities.
+You can also configure the authority prefix to be different as well.
+Instead of prefixing each authority with `SCOPE_`, you can change it to `ROLE_` like so:
-That final converter might be something like `GrantedAuthoritiesExtractor` below:
+.Authorities Prefix Configuration
+====
+.Java
+[source,java,role="primary"]
+----
+@Bean
+public JwtAuthenticationConverter jwtAuthenticationConverter() {
+ JwtGrantedAuthoritiesConverter grantedAuthoritiesConverter = new JwtGrantedAuthoritiesConverter();
+ grantedAuthoritiesConverter.setAuthorityPrefix("ROLE_");
-[source,java]
+ JwtAuthenticationConverter authenticationConverter = new JwtAuthenticationConverter();
+ jwtAuthenticationConverter.setJwtGrantedAuthoritiesConverter(authoritiesConverter);
+ return jwtAuthenticationConverter;
+}
----
-static class GrantedAuthoritiesExtractor
- implements Converter> {
- public Collection convert(Jwt jwt) {
- Collection> authorities = (Collection>)
- jwt.getClaims().getOrDefault("mycustomclaim", Collections.emptyList());
+.Xml
+[source,xml,role="secondary"]
+----
+
+
+
+
+
+
+
- return authorities.stream()
- .map(Object::toString)
- .map(SimpleGrantedAuthority::new)
- .collect(Collectors.toList());
- }
-}
+
+
+
+
+
+
+
----
+====
+
+Or, you can remove the prefix altogether by calling `JwtGrantedAuthoritiesConverter#setAuthorityPrefix("")`.
For more flexibility, the DSL supports entirely replacing the converter with any class that implements `Converter`:
@@ -740,6 +802,23 @@ static class CustomAuthenticationConverter implements Converter authorize
+ .anyRequest().authenticated()
+ )
+ .oauth2ResourceServer(oauth2 -> oauth2
+ .jwt(jwt -> jwt
+ .jwtAuthenticationConverter(new CustomAuthenticationConverter())
+ )
+ );
+ }
+}
----
[[oauth2resourceserver-jwt-validation]]
@@ -815,7 +894,7 @@ OAuth2TokenValidator audienceValidator() {
}
----
-Then, to add into a resource server, it's a matter of specifying the `JwtDecoder` instance:
+Then, to add into a resource server, it's a matter of specifying the <> instance:
[source,java]
----
@@ -944,8 +1023,8 @@ To adjust the way in which Resource Server connects to the authorization server,
@Bean
public JwtDecoder jwtDecoder(RestTemplateBuilder builder) {
RestOperations rest = builder
- .setConnectionTimeout(60000)
- .setReadTimeout(60000)
+ .setConnectTimeout(Duration.ofSeconds(60))
+ .setReadTimeout(Duration.ofSeconds(60))
.build();
NimbusJwtDecoder jwtDecoder = NimbusJwtDecoder.withJwkSetUri(jwkSetUri).restOperations(rest).build();
@@ -953,6 +1032,34 @@ public JwtDecoder jwtDecoder(RestTemplateBuilder builder) {
}
```
+Also by default, Resource Server caches in-memory the authorization server's JWK set for 5 minutes, which you may want to adjust.
+Further, it doesn't take into account more sophisticated caching patterns like eviction or using a shared cache.
+
+To adjust the way in which Resource Server caches the JWK set, `NimbusJwtDecoder` accepts an instance of `Cache`:
+
+```java
+@Bean
+public JwtDecoder jwtDecoder(CacheManager cacheManager) {
+ return NimbusJwtDecoder.withJwtSetUri(jwkSetUri)
+ .cache(cacheManager.getCache("jwks"))
+ .build();
+}
+```
+
+When given a `Cache`, Resource Server will use the JWK Set Uri as the key and the JWK Set JSON as the value.
+
+NOTE: Spring isn't a cache provider, so you'll need to make sure to include the appropriate dependencies, like `spring-boot-starter-cache` and your favorite caching provider.
+
+NOTE: Whether it's socket or cache timeouts, you may instead want to work with Nimbus directly.
+To do so, remember that `NimbusJwtDecoder` ships with a constructor that takes Nimbus's `JWTProcessor`.
+
+[[oauth2resourceserver-opaque-minimaldependencies]]
+=== Minimal Dependencies for Introspection
+As described in <> most of Resource Server support is collected in `spring-security-oauth2-resource-server`.
+However unless a custom <> is provided, the Resource Server will fallback to NimbusOpaqueTokenIntrospector.
+Meaning that both `spring-security-oauth2-resource-server` and `oauth2-oidc-sdk` are necessary in order to have a working minimal Resource Server that supports opaque Bearer Tokens.
+Please refer to `spring-security-oauth2-resource-server` in order to determin the correct version for `oauth2-oidc-sdk`.
+
[[oauth2resourceserver-opaque-minimalconfiguration]]
=== Minimal Configuration for Introspection
@@ -1015,10 +1122,33 @@ The resulting `Authentication#getPrincipal`, by default, is a Spring Security `{
From here, you may want to jump to:
+* <>
* <>
* <>
* <>
+[[oauth2resourceserver-opaque-architecture]]
+=== How Opaque Token Authentication Works
+
+Next, let's see the architectural components that Spring Security uses to support https://tools.ietf.org/html/rfc7662[opaque token] Authentication in servlet-based applications, like the one we just saw.
+
+{security-api-url}org/springframework/security/oauth2/server/resource/authentication/OpaqueTokenAuthenticationProvider.html[`OpaqueTokenAuthenticationProvider`] is an <> implementation that leverages a <> to authenticate an opaque token.
+
+Let's take a look at how `OpaqueTokenAuthenticationProvider` works within Spring Security.
+The figure explains details of how the <> in figures from <> works.
+
+.`OpaqueTokenAuthenticationProvider` Usage
+image::{figures}/opaquetokenauthenticationprovider.png[]
+
+image:{icondir}/number_1.png[] The authentication `Filter` from <> passes a `BearerTokenAuthenticationToken` to the `AuthenticationManager` which is implemented by <>.
+
+image:{icondir}/number_2.png[] The `ProviderManager` is configured to use an <> of type `OpaqueTokenAuthenticationProvider`.
+
+[[oauth2resourceserver-opaque-architecture-introspector]]
+image:{icondir}/number_3.png[] `OpaqueTokenAuthenticationProvider` introspects the opaque token and adds granted authorities using an <>.
+When authentication is successful, the <> that is returned is of type `BearerTokenAuthentication` and has a principal that is the `OAuth2AuthenticatedPrincipal` returned by the configured <>.
+Ultimately, the returned `BearerTokenAuthentication` will be set on the <> by the authentication `Filter`.
+
[[oauth2resourceserver-opaque-attributes]]
=== Looking Up Attributes Post-Authentication
@@ -1147,7 +1277,8 @@ The above requires the scope of `message:read` for any URL that starts with `/me
Methods on the `oauth2ResourceServer` DSL will also override or replace auto configuration.
-For example, the second `@Bean` Spring Boot creates is an `OpaqueTokenIntrospector`, which decodes `String` tokens into validated instances of `OAuth2AuthenticatedPrincipal`:
+[[oauth2resourceserver-opaque-introspector]]
+For example, the second `@Bean` Spring Boot creates is an `OpaqueTokenIntrospector`, <>:
[source,java]
----
@@ -1157,11 +1288,11 @@ public OpaqueTokenIntrospector introspector() {
}
----
-If the application doesn't expose a `OpaqueTokenIntrospector` bean, then Spring Boot will expose the above default one.
+If the application doesn't expose a <> bean, then Spring Boot will expose the above default one.
And its configuration can be overridden using `introspectionUri()` and `introspectionClientCredentials()` or replaced using `introspector()`.
-Or, if you're not using Spring Boot at all, then both of these components - the filter chain and a `OpaqueTokenIntrospector` can be specified in XML.
+Or, if you're not using Spring Boot at all, then both of these components - the filter chain and a <> can be specified in XML.
The filter chain is specified like so:
@@ -1179,7 +1310,7 @@ The filter chain is specified like so:
----
====
-And the `OpaqueTokenIntrospector` like so:
+And the <> like so:
.Opaque Token Introspector
====
@@ -1260,7 +1391,7 @@ Using `introspectionUri()` takes precedence over any configuration property.
[[oauth2resourceserver-opaque-introspector-dsl]]
==== Using `introspector()`
-More powerful than `introspectionUri()` is `introspector()`, which will completely replace any Boot auto configuration of `OpaqueTokenIntrospector`:
+More powerful than `introspectionUri()` is `introspector()`, which will completely replace any Boot auto configuration of <>:
.Introspector Configuration
====
@@ -1320,7 +1451,7 @@ This is handy when deeper configuration, like <> `@Bean` has the same effect as `introspector()`:
[source,java]
----
@@ -1397,7 +1528,7 @@ For example, if the introspection response were:
Then Resource Server would generate an `Authentication` with two authorities, one for `message:read` and the other for `message:write`.
-This can, of course, be customized using a custom `OpaqueTokenIntrospector` that takes a look at the attribute set and converts in its own way:
+This can, of course, be customized using a custom <> that takes a look at the attribute set and converts in its own way:
[source,java]
----
@@ -1442,11 +1573,11 @@ To adjust the way in which Resource Server connects to the authorization server,
```java
@Bean
-public OpaqueTokenIntrospector introspector(RestTemplateBuilder builder) {
+public OpaqueTokenIntrospector introspector(RestTemplateBuilder builder, OAuth2ResourceServerProperties properties) {
RestOperations rest = builder
- .basicAuthentication(clientId, clientSecret)
- .setConnectionTimeout(60000)
- .setReadTimeout(60000)
+ .basicAuthentication(properties.getOpaquetoken().getClientId(), properties.getOpaquetoken().getClientSecret())
+ .setConnectionTimeout(Duration.ofSeconds(60))
+ .setReadTimeout(Duration.ofSeconds(60))
.build();
return new NimbusOpaqueTokenIntrospector(introspectionUri, rest);
@@ -1481,7 +1612,7 @@ Any attributes in the corresponding `OAuth2AuthenticatedPrincipal` would be what
But, let's say that, oddly enough, the introspection endpoint only returns whether or not the token is active.
Now what?
-In this case, you can create a custom `OpaqueTokenIntrospector` that still hits the endpoint, but then updates the returned principal to have the JWTs claims as the attributes:
+In this case, you can create a custom <> that still hits the endpoint, but then updates the returned principal to have the JWTs claims as the attributes:
[source,java]
----
@@ -1526,7 +1657,7 @@ Generally speaking, a Resource Server doesn't care about the underlying user, bu
That said, at times it can be valuable to tie the authorization statement back to a user.
-If an application is also using `spring-security-oauth2-client`, having set up the appropriate `ClientRegistrationRepository`, then this is quite simple with a custom `OpaqueTokenIntrospector`.
+If an application is also using `spring-security-oauth2-client`, having set up the appropriate `ClientRegistrationRepository`, then this is quite simple with a custom <>.
This implementation below does three things:
* Delegates to the introspection endpoint, to affirm the token's validity
@@ -1575,7 +1706,7 @@ public class UserInfoOpaqueTokenIntrospector implements OpaqueTokenIntrospector
}
----
-Either way, having created your `OpaqueTokenIntrospector`, you should publish it as a `@Bean` to override the defaults:
+Either way, having created your <>, you should publish it as a `@Bean` to override the defaults:
[source,java]
----
@@ -1602,8 +1733,7 @@ AuthenticationManagerResolver tokenAuthenticationManagerReso
OpaqueTokenAuthenticationProvider opaqueToken = opaqueToken();
return request -> {
- String token = bearerToken.resolve(request);
- if (isAJwt(token)) {
+ if (useJwt(request)) {
return jwt::authenticate;
} else {
return opaqueToken::authenticate;
@@ -1612,6 +1742,8 @@ AuthenticationManagerResolver tokenAuthenticationManagerReso
}
----
+NOTE: The implementation of `useJwt(HttpServletRequest)` will likely depend on custom request material like the path.
+
And then specify this `AuthenticationManagerResolver` in the DSL:
.Authentication Manager Resolver
@@ -1725,13 +1857,13 @@ In this case, you construct `JwtIssuerAuthenticationManagerResolver` with a stra
This approach allows us to add and remove elements from the repository (shown as a `Map` in the snippet) at runtime.
NOTE: It would be unsafe to simply take any issuer and construct an `AuthenticationManager` from it.
-The issuer should be one that the code can verify from a trusted source like a whitelist.
+The issuer should be one that the code can verify from a trusted source like a list of allowed issuers.
===== Parsing the Claim Only Once
-You may have observed that this strategy, while simple, comes with the trade-off that the JWT is parsed once by the `AuthenticationManagerResolver` and then again by the `JwtDecoder` later on in the request.
+You may have observed that this strategy, while simple, comes with the trade-off that the JWT is parsed once by the `AuthenticationManagerResolver` and then again by the <> later on in the request.
-This extra parsing can be alleviated by configuring the `JwtDecoder` directly with a `JWTClaimSetAwareJWSKeySelector` from Nimbus:
+This extra parsing can be alleviated by configuring the <> directly with a `JWTClaimSetAwareJWSKeySelector` from Nimbus:
[source,java]
----
@@ -1775,7 +1907,7 @@ public class TenantJWSKeySelector
----
<1> A hypothetical source for tenant information
<2> A cache for `JWKKeySelector`s, keyed by tenant identifier
-<3> Looking up the tenant is more secure than simply calculating the JWK Set endpoint on the fly - the lookup acts as a tenant whitelist
+<3> Looking up the tenant is more secure than simply calculating the JWK Set endpoint on the fly - the lookup acts as a list of allowed tenants
<4> Create a `JWSKeySelector` via the types of keys that come back from the JWK Set endpoint - the lazy lookup here means that you don't need to configure all tenants at startup
The above key selector is a composition of many key selectors.
@@ -1833,7 +1965,7 @@ public class TenantJwtIssuerValidator implements OAuth2TokenValidator {
}
----
-Now that we have a tenant-aware processor and a tenant-aware validator, we can proceed with creating our `JwtDecoder`:
+Now that we have a tenant-aware processor and a tenant-aware validator, we can proceed with creating our <>:
[source,java]
----
@@ -1858,22 +1990,24 @@ However, if you resolve it by a claim in the bearer token, read on to learn abou
=== Bearer Token Resolution
By default, Resource Server looks for a bearer token in the `Authorization` header.
-This, however, can be customized in a couple of ways.
+This, however, can be customized in a handful of ways.
==== Reading the Bearer Token from a Custom Header
For example, you may have a need to read the bearer token from a custom header.
-To achieve this, you can wire a `HeaderBearerTokenResolver` instance into the DSL, as you can see in the following example:
+To achieve this, you can expose a `DefaultBearerTokenResolver` as a bean, or wire an instance into the DSL, as you can see in the following example:
.Custom Bearer Token Header
====
.Java
[source,java,role="primary"]
----
-http
- .oauth2ResourceServer(oauth2 -> oauth2
- .bearerTokenResolver(new HeaderBearerTokenResolver("x-goog-iap-jwt-assertion"))
- );
+@Bean
+BearerTokenResolver bearerTokenResolver() {
+ DefaultBearerTokenResolver bearerTokenResolver = new DefaultBearerTokenResolver();
+ bearerTokenResolver.setBearerTokenHeaderName(HttpHeaders.PROXY_AUTHORIZATION);
+ return bearerTokenResolver;
+}
----
.Xml
@@ -1884,12 +2018,14 @@ http
----
====
+Or, in circumstances where a provider is using both a custom header and value, you can use `HeaderBearerTokenResolver` instead.
+
==== Reading the Bearer Token from a Form Parameter
Or, you may wish to read the token from a form parameter, which you can do by configuring the `DefaultBearerTokenResolver`, as you can see below:
@@ -1923,7 +2059,7 @@ http
=== Bearer Token Propagation
-Now that you're in possession of a bearer token, it might be handy to pass that to downstream services.
+Now that you're resource server has validated the token, it might be handy to pass it to downstream services.
This is quite simple with `{security-api-url}org/springframework/security/oauth2/server/resource/web/reactive/function/client/ServletBearerExchangeFilterFunction.html[ServletBearerExchangeFilterFunction]`, which you can see in the following example:
[source,java]
@@ -1967,12 +2103,12 @@ this.rest.get()
In this case, the filter will fall back and simply forward the request onto the rest of the web filter chain.
[NOTE]
-Unlike the https://docs.spring.io/spring-security/site/docs/current-SNAPSHOT/api/org/springframework/security/oauth2/client/web/reactive/function/client/ServletOAuth2AuthorizedClientExchangeFilterFunction.html[OAuth 2.0 Client filter function], this filter function makes no attempt to renew the token, should it be expired.
+Unlike the {security-api-url}org/springframework/security/oauth2/client/web/reactive/function/client/ServletOAuth2AuthorizedClientExchangeFilterFunction.html[OAuth 2.0 Client filter function], this filter function makes no attempt to renew the token, should it be expired.
To obtain this level of support, please use the OAuth 2.0 Client filter.
==== `RestTemplate` support
-There is no dedicated support for `RestTemplate` at the moment, but you can achieve propagation quite simply with your own interceptor:
+There is no `RestTemplate` equivalent for `ServletBearerExchangeFilterFunction` at the moment, but you can propagate the request's bearer token quite simply with your own interceptor:
[source,java]
----
@@ -1997,6 +2133,11 @@ RestTemplate rest() {
}
----
+
+[NOTE]
+Unlike the {security-api-url}org/springframework/security/oauth2/client/OAuth2AuthorizedClientManager.html[OAuth 2.0 Authorized Client Manager], this filter interceptor makes no attempt to renew the token, should it be expired.
+To obtain this level of support, please create an interceptor using the <>.
+
[[oauth2resourceserver-bearertoken-failure]]
=== Bearer Token Failure
diff --git a/docs/manual/src/docs/asciidoc/_includes/servlet/saml2/saml2-login.adoc b/docs/manual/src/docs/asciidoc/_includes/servlet/saml2/saml2-login.adoc
index 1af5c801019..b0112af45df 100644
--- a/docs/manual/src/docs/asciidoc/_includes/servlet/saml2/saml2-login.adoc
+++ b/docs/manual/src/docs/asciidoc/_includes/servlet/saml2/saml2-login.adoc
@@ -139,7 +139,6 @@ For example:
[[servlet-saml2-rpr-relyingparty]]
===== Relying Party
-
* `registrationId` - (required) a unique identifer for this configuration mapping.
This identifier may be used in URI paths, so care should be taken that no URI encoding is required.
* `localEntityIdTemplate` - (optional) A URI pattern that creates an entity ID for this application based on the incoming request. The default is
@@ -150,15 +149,11 @@ http://localhost:8080/saml2/service-provider-metadata/my-test-configuration
```
There is no requirement that this configuration option is a pattern, it can be a fixed URI value.
-* `remoteIdpEntityId` - (required) the entity ID of the Identity Provider. Always a fixed URI value or string,
-no patterns allowed.
* `assertionConsumerServiceUrlTemplate` - (optional) A URI pattern that denotes the assertion
consumer service URI to be sent with any `AuthNRequest` from the SP to the IDP during the SP initiated flow.
While this can be a pattern the actual URI must resolve to the ACS endpoint on the SP.
The default value is `+{baseUrl}/login/saml2/sso/{registrationId}+` and maps directly to the
https://github.com/spring-projects/spring-security/blob/5.2.0.RELEASE/saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/servlet/filter/Saml2WebSsoAuthenticationFilter.java#L42[`Saml2WebSsoAuthenticationFilter`] endpoint
-* `idpWebSsoUrl` - (required) a fixed URI value for the IDP Single Sign On endpoint where
-the SP sends the `AuthNRequest` messages.
* `credentials` - A list of credentials, private keys and x509 certificates, used for
message signing, verification, encryption and decryption.
This list can contain redundant credentials to allow for easy rotation of credentials.
@@ -170,6 +165,12 @@ Encryption is always done using the first `ENCRYPTION` key in the list.
** [2] - PrivateKey/X509Certificate{SIGNING,DECRYPTION} - The SP's first signing and decryption credential.
** [3] - PrivateKey/X509Certificate{SIGNING,DECRYPTION} - The SP's second decryption credential.
Signing is always done using the first `SIGNING` key in the list.
+* `ProviderDetails#entityId` - (required) the entity ID of the Identity Provider. Always a fixed URI value or string,
+no patterns allowed.
+* `ProviderDetails#webSsoUrl` - (required) a fixed URI value for the IDP Single Sign On endpoint where
+the SP sends the `AuthNRequest` messages.
+* `ProviderDetails#signAuthNRequest` - A boolean indicating whether or not to sign the `AuthNRequest` with the SP's private key, defaults to `true`
+* `ProviderDetails#binding` - A `Saml2MessageBinding` indicating what kind of binding to use for the `AuthNRequest`, whether that be `REDIRECT` or `POST`, defaults to `REDIRECT`
When an incoming message is received, signatures are always required, the system will first attempt
to validate the signature using the certificate at index [0] and only move to the second
@@ -216,16 +217,68 @@ credentials must be shared with the Identity Provider
[[servlet-saml2-sp-initiated]]
==== Authentication Requests - SP Initiated Flow
-To initiate an authentication from the web application, a simple redirect to
+To initiate an authentication from the web application, you can redirect to:
`+{baseUrl}/saml2/authenticate/{registrationId}+`
-The endpoint will generate an `AuthNRequest` by invoking the `createAuthenticationRequest` method on a
-configurable factory. Just expose the `Saml2AuthenticationRequestFactory` as a bean in your configuration.
+This endpoint will generate an `AuthNRequest` either as a Redirect or POST depending on your `RelyingPartyRegistration`.
+
+[[servlet-saml2-sp-initiated-factory]]
+==== Customizing the AuthNRequest
+
+To adjust the `AuthNRequest`, you can publish an instance of `Saml2AuthenticationRequestFactory`.
+
+For example, if you wanted to configure the `AuthNRequest` to request the IDP to send the SAML `Assertion` by REDIRECT, you could do:
+
[source,java]
----
-public interface Saml2AuthenticationRequestFactory {
- String createAuthenticationRequest(Saml2AuthenticationRequest request);
+@Bean
+public Saml2AuthenticationRequestFactory authenticationRequestFactory() {
+ OpenSamlAuthenticationRequestFactory authenticationRequestFactory =
+ new OpenSamlAuthenticationRequestFactory();
+ authenticationRequestFactory.setProtocolBinding("urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect");
+ return authenticationRequestFactory;
+}
+----
+
+[[servlet-saml2-sp-initiated-factory-delegate]]
+==== Delegating to an AuthenticationRequestFactory
+
+Or, in circumstances where you need more control over what is sent as parameters to the `AuthenticationRequestFactory`, you can use delegation:
+
+[source,java]
+----
+@Component
+public class IssuerSaml2AuthenticationRequestFactory implements Saml2AuthenticationRequestFactory {
+ private OpenSamlAuthenticationRequestFactory delegate = new OpenSamlAuthenticationRequestFactory();
+
+ @Override
+ public String createAuthenticationRequest(Saml2AuthenticationRequest request) {
+ return this.delegate.createAuthenticationRequest(request);
+ }
+
+ @Override
+ public Saml2PostAuthenticationRequest createPostAuthenticationRequest
+ (Saml2AuthenticationRequestContext context) {
+
+ String issuer = // ... calculate issuer
+
+ Saml2AuthenticationRequestContext customIssuer = Saml2AuthenticationRequestContext.builder()
+ .assertionConsumerServiceUrl(context.getAssertionConsumerServiceUrl())
+ .issuer(issuer)
+ .relayState(context.getRelayState())
+ .relyingPartyRegistration(context.getRelyingPartyRegistration())
+ .build();
+
+ return this.delegate.createPostAuthenticationRequest(customIssuer);
+ }
+
+ @Override
+ public Saml2RedirectAuthenticationRequest createRedirectAuthenticationRequest
+ (Saml2AuthenticationRequestContext context) {
+
+ throw new UnsupportedOperationException("unsupported");
+ }
}
----
diff --git a/docs/manual/src/docs/asciidoc/_includes/servlet/test/mockmvc.adoc b/docs/manual/src/docs/asciidoc/_includes/servlet/test/mockmvc.adoc
index f26caf3b1f3..2a4d4970b89 100644
--- a/docs/manual/src/docs/asciidoc/_includes/servlet/test/mockmvc.adoc
+++ b/docs/manual/src/docs/asciidoc/_includes/servlet/test/mockmvc.adoc
@@ -309,7 +309,7 @@ assertThat(user.getAuthorities()).containsExactly(new SimpleGrantedAuthority("SC
Spring Security does the necessary work to make sure that the `OidcUser` instance is available for <>.
-Further, it also links that `OidcUser` to a simple instance of `OAuth2AuthorizedClient` that it deposits into an `HttpSessionOAuth2AuthorizedClientRepository`.
+Further, it also links that `OidcUser` to a simple instance of `OAuth2AuthorizedClient` that it deposits into an mock `OAuth2AuthorizedClientRepository`.
This can be handy if your tests <>..
[[testing-oidc-login-authorities]]
@@ -432,7 +432,7 @@ assertThat(user.getAuthorities()).containsExactly(new SimpleGrantedAuthority("SC
Spring Security does the necessary work to make sure that the `OAuth2User` instance is available for <>.
-Further, it also links that `OAuth2User` to a simple instance of `OAuth2AuthorizedClient` that it deposits in an `HttpSessionOAuth2AuthorizedClientRepository`.
+Further, it also links that `OAuth2User` to a simple instance of `OAuth2AuthorizedClient` that it deposits in a mock `OAuth2AuthorizedClientRepository`.
This can be handy if your tests <>.
[[testing-oauth2-login-authorities]]
@@ -528,7 +528,7 @@ public String foo(@RegisteredOAuth2AuthorizedClient("my-app") OAuth2AuthorizedCl
----
Simulating this handshake with the authorization server could be cumbersome.
-Instead, you can use `SecurityMockMvcRequestPostProcessor#oauth2Client` to add a `OAuth2AuthorizedClient` into an `HttpSessionOAuth2AuthorizedClientRepository`:
+Instead, you can use `SecurityMockMvcRequestPostProcessor#oauth2Client` to add a `OAuth2AuthorizedClient` into a mock `OAuth2AuthorizedClientRepository`:
[source,java]
----
@@ -536,19 +536,6 @@ mvc
.perform(get("/endpoint").with(oauth2Client("my-app")));
----
-If your application isn't already using an `HttpSessionOAuth2AuthorizedClientRepository`, then you can supply one as a `@TestConfiguration`:
-
-[source,java]
-----
-@TestConfiguration
-static class AuthorizedClientConfig {
- @Bean
- OAuth2AuthorizedClientRepository authorizedClientRepository() {
- return new HttpSessionOAuth2AuthorizedClientRepository();
- }
-}
-----
-
What this will do is create an `OAuth2AuthorizedClient` that has a simple `ClientRegistration`, `OAuth2AccessToken`, and resource owner name.
Specifically, it will include a `ClientRegistration` with a client id of "test-client" and client secret of "test-secret":
@@ -574,8 +561,7 @@ assertThat(authorizedClient.getAccessToken().getScopes()).hasSize(1);
assertThat(authorizedClient.getAccessToken().getScopes()).containsExactly("read");
----
-Spring Security does the necessary work to make sure that the `OAuth2AuthorizedClient` instance is available in the associated `HttpSession`.
-That means that it can be retrieved from an `HttpSessionOAuth2AuthorizedClientRepository`.
+The client can then be retrieved as normal using `@RegisteredOAuth2AuthorizedClient` in a controller method.
[[testing-oauth2-client-scopes]]
===== Configuring Scopes
diff --git a/docs/manual/src/docs/asciidoc/images/servlet/oauth2/beareraccessdeniedhandler.odg b/docs/manual/src/docs/asciidoc/images/servlet/oauth2/beareraccessdeniedhandler.odg
new file mode 100644
index 00000000000..e35bce8c378
Binary files /dev/null and b/docs/manual/src/docs/asciidoc/images/servlet/oauth2/beareraccessdeniedhandler.odg differ
diff --git a/docs/manual/src/docs/asciidoc/images/servlet/oauth2/bearerauthenticationentrypoint.odg b/docs/manual/src/docs/asciidoc/images/servlet/oauth2/bearerauthenticationentrypoint.odg
new file mode 100644
index 00000000000..b4b50fb6e5a
Binary files /dev/null and b/docs/manual/src/docs/asciidoc/images/servlet/oauth2/bearerauthenticationentrypoint.odg differ
diff --git a/docs/manual/src/docs/asciidoc/images/servlet/oauth2/bearerauthenticationentrypoint.png b/docs/manual/src/docs/asciidoc/images/servlet/oauth2/bearerauthenticationentrypoint.png
new file mode 100644
index 00000000000..a022951a6bc
Binary files /dev/null and b/docs/manual/src/docs/asciidoc/images/servlet/oauth2/bearerauthenticationentrypoint.png differ
diff --git a/docs/manual/src/docs/asciidoc/images/servlet/oauth2/bearertokenauthenticationfilter.odg b/docs/manual/src/docs/asciidoc/images/servlet/oauth2/bearertokenauthenticationfilter.odg
new file mode 100644
index 00000000000..5dc20b4ba4c
Binary files /dev/null and b/docs/manual/src/docs/asciidoc/images/servlet/oauth2/bearertokenauthenticationfilter.odg differ
diff --git a/docs/manual/src/docs/asciidoc/images/servlet/oauth2/bearertokenauthenticationfilter.png b/docs/manual/src/docs/asciidoc/images/servlet/oauth2/bearertokenauthenticationfilter.png
new file mode 100644
index 00000000000..7ea79213ec3
Binary files /dev/null and b/docs/manual/src/docs/asciidoc/images/servlet/oauth2/bearertokenauthenticationfilter.png differ
diff --git a/docs/manual/src/docs/asciidoc/images/servlet/oauth2/jwtauthenticationprovider.odg b/docs/manual/src/docs/asciidoc/images/servlet/oauth2/jwtauthenticationprovider.odg
new file mode 100644
index 00000000000..c79b7c33115
Binary files /dev/null and b/docs/manual/src/docs/asciidoc/images/servlet/oauth2/jwtauthenticationprovider.odg differ
diff --git a/docs/manual/src/docs/asciidoc/images/servlet/oauth2/jwtauthenticationprovider.png b/docs/manual/src/docs/asciidoc/images/servlet/oauth2/jwtauthenticationprovider.png
new file mode 100644
index 00000000000..8f0f1450857
Binary files /dev/null and b/docs/manual/src/docs/asciidoc/images/servlet/oauth2/jwtauthenticationprovider.png differ
diff --git a/docs/manual/src/docs/asciidoc/images/servlet/oauth2/opaquetokenauthenticationprovider.odg b/docs/manual/src/docs/asciidoc/images/servlet/oauth2/opaquetokenauthenticationprovider.odg
new file mode 100644
index 00000000000..3f47370f148
Binary files /dev/null and b/docs/manual/src/docs/asciidoc/images/servlet/oauth2/opaquetokenauthenticationprovider.odg differ
diff --git a/docs/manual/src/docs/asciidoc/images/servlet/oauth2/opaquetokenauthenticationprovider.png b/docs/manual/src/docs/asciidoc/images/servlet/oauth2/opaquetokenauthenticationprovider.png
new file mode 100644
index 00000000000..6b1c0f7c36e
Binary files /dev/null and b/docs/manual/src/docs/asciidoc/images/servlet/oauth2/opaquetokenauthenticationprovider.png differ
diff --git a/etc/nohttp/whitelist.lines b/etc/nohttp/allowlist.lines
similarity index 100%
rename from etc/nohttp/whitelist.lines
rename to etc/nohttp/allowlist.lines
diff --git a/gradle.properties b/gradle.properties
index da09dbcb93a..8030f9eddc1 100644
--- a/gradle.properties
+++ b/gradle.properties
@@ -1,8 +1,8 @@
aspectjVersion=1.9.3
-gaeVersion=1.9.78
-springBootVersion=2.2.5.RELEASE
-version=5.4.0.BUILD-SNAPSHOT
-kotlinVersion=1.3.70
+gaeVersion=1.9.80
+springBootVersion=2.4.0-M1
+version=5.4.0-SNAPSHOT
+kotlinVersion=1.3.72
org.gradle.jvmargs=-Xmx3g -XX:MaxPermSize=2048m -XX:+HeapDumpOnOutOfMemoryError
org.gradle.parallel=true
org.gradle.caching=true
diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties
index a2bf1313b8a..a4f0001d203 100644
--- a/gradle/wrapper/gradle-wrapper.properties
+++ b/gradle/wrapper/gradle-wrapper.properties
@@ -1,5 +1,5 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
-distributionUrl=https\://services.gradle.org/distributions/gradle-6.2.2-bin.zip
+distributionUrl=https\://services.gradle.org/distributions/gradle-6.4.1-bin.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
diff --git a/itest/ldap/embedded-ldap-apacheds-default/src/integration-test/resources/applicationContext-security.xml b/itest/ldap/embedded-ldap-apacheds-default/src/integration-test/resources/applicationContext-security.xml
index da547d0b041..b1b80079c5b 100644
--- a/itest/ldap/embedded-ldap-apacheds-default/src/integration-test/resources/applicationContext-security.xml
+++ b/itest/ldap/embedded-ldap-apacheds-default/src/integration-test/resources/applicationContext-security.xml
@@ -4,6 +4,6 @@
xsi:schemaLocation="http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/security https://www.springframework.org/schema/security/spring-security.xsd">
-
+
diff --git a/itest/ldap/embedded-ldap-mode-apacheds/src/integration-test/resources/applicationContext-security.xml b/itest/ldap/embedded-ldap-mode-apacheds/src/integration-test/resources/applicationContext-security.xml
index f980c6ca98a..8e3f4b4380c 100644
--- a/itest/ldap/embedded-ldap-mode-apacheds/src/integration-test/resources/applicationContext-security.xml
+++ b/itest/ldap/embedded-ldap-mode-apacheds/src/integration-test/resources/applicationContext-security.xml
@@ -4,6 +4,6 @@
xsi:schemaLocation="http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/security https://www.springframework.org/schema/security/spring-security.xsd">
-
+
diff --git a/itest/ldap/embedded-ldap-mode-unboundid/src/integration-test/resources/applicationContext-security.xml b/itest/ldap/embedded-ldap-mode-unboundid/src/integration-test/resources/applicationContext-security.xml
index 6254829b430..9ab9bf623f4 100644
--- a/itest/ldap/embedded-ldap-mode-unboundid/src/integration-test/resources/applicationContext-security.xml
+++ b/itest/ldap/embedded-ldap-mode-unboundid/src/integration-test/resources/applicationContext-security.xml
@@ -4,6 +4,6 @@
xsi:schemaLocation="http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/security https://www.springframework.org/schema/security/spring-security.xsd">
-
+
diff --git a/itest/ldap/embedded-ldap-none/src/integration-test/resources/applicationContext-security.xml b/itest/ldap/embedded-ldap-none/src/integration-test/resources/applicationContext-security.xml
index da547d0b041..b1b80079c5b 100644
--- a/itest/ldap/embedded-ldap-none/src/integration-test/resources/applicationContext-security.xml
+++ b/itest/ldap/embedded-ldap-none/src/integration-test/resources/applicationContext-security.xml
@@ -4,6 +4,6 @@
xsi:schemaLocation="http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/security https://www.springframework.org/schema/security/spring-security.xsd">
-
+
diff --git a/itest/ldap/embedded-ldap-unboundid-default/src/integration-test/resources/applicationContext-security.xml b/itest/ldap/embedded-ldap-unboundid-default/src/integration-test/resources/applicationContext-security.xml
index da547d0b041..b1b80079c5b 100644
--- a/itest/ldap/embedded-ldap-unboundid-default/src/integration-test/resources/applicationContext-security.xml
+++ b/itest/ldap/embedded-ldap-unboundid-default/src/integration-test/resources/applicationContext-security.xml
@@ -4,6 +4,6 @@
xsi:schemaLocation="http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/security https://www.springframework.org/schema/security/spring-security.xsd">
-
+
diff --git a/ldap/src/integration-test/java/org/springframework/security/ldap/ApacheDsContainerConfig.java b/ldap/src/integration-test/java/org/springframework/security/ldap/ApacheDsContainerConfig.java
index 29e68a48354..ec6cd0fc7ad 100644
--- a/ldap/src/integration-test/java/org/springframework/security/ldap/ApacheDsContainerConfig.java
+++ b/ldap/src/integration-test/java/org/springframework/security/ldap/ApacheDsContainerConfig.java
@@ -34,13 +34,14 @@ public class ApacheDsContainerConfig {
ApacheDSContainer ldapContainer() throws Exception {
this.container = new ApacheDSContainer("dc=springframework,dc=org",
"classpath:test-server.ldif");
+ this.container.setPort(0);
return this.container;
}
@Bean
- ContextSource contextSource() throws Exception {
+ ContextSource contextSource(ApacheDSContainer ldapContainer) throws Exception {
return new DefaultSpringSecurityContextSource("ldap://127.0.0.1:"
- + ldapContainer().getPort() + "/dc=springframework,dc=org");
+ + ldapContainer.getLocalPort() + "/dc=springframework,dc=org");
}
@PreDestroy
diff --git a/ldap/src/integration-test/java/org/springframework/security/ldap/server/ApacheDSContainerTests.java b/ldap/src/integration-test/java/org/springframework/security/ldap/server/ApacheDSContainerTests.java
index 552410af8f2..add74ddf451 100644
--- a/ldap/src/integration-test/java/org/springframework/security/ldap/server/ApacheDSContainerTests.java
+++ b/ldap/src/integration-test/java/org/springframework/security/ldap/server/ApacheDSContainerTests.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2016 the original author or authors.
+ * Copyright 2002-2020 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.
@@ -39,6 +39,7 @@
* @author Luke Taylor
* @author Rob Winch
* @author Gunnar Hillert
+ * @author Evgeniy Cheban
* @since 3.0
*/
public class ApacheDSContainerTests {
@@ -212,4 +213,20 @@ private List getDefaultPorts(int count) throws IOException {
}
}
}
+
+ @Test
+ public void afterPropertiesSetWhenPortIsZeroThenRandomPortIsSelected() throws Exception {
+ ApacheDSContainer server = new ApacheDSContainer("dc=springframework,dc=org",
+ "classpath:test-server.ldif");
+ server.setPort(0);
+ try {
+ server.afterPropertiesSet();
+
+ assertThat(server.getPort()).isEqualTo(0);
+ assertThat(server.getLocalPort()).isNotEqualTo(0);
+ }
+ finally {
+ server.destroy();
+ }
+ }
}
diff --git a/ldap/src/main/java/org/springframework/security/ldap/authentication/ad/ActiveDirectoryLdapAuthenticationProvider.java b/ldap/src/main/java/org/springframework/security/ldap/authentication/ad/ActiveDirectoryLdapAuthenticationProvider.java
index 91a41a0f29b..afcb2fa87e2 100644
--- a/ldap/src/main/java/org/springframework/security/ldap/authentication/ad/ActiveDirectoryLdapAuthenticationProvider.java
+++ b/ldap/src/main/java/org/springframework/security/ldap/authentication/ad/ActiveDirectoryLdapAuthenticationProvider.java
@@ -16,6 +16,7 @@
package org.springframework.security.ldap.authentication.ad;
import org.springframework.dao.IncorrectResultSizeDataAccessException;
+import org.springframework.ldap.CommunicationException;
import org.springframework.ldap.core.DirContextOperations;
import org.springframework.ldap.core.DistinguishedName;
import org.springframework.ldap.core.support.DefaultDirObjectFactory;
@@ -24,6 +25,7 @@
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.authentication.CredentialsExpiredException;
import org.springframework.security.authentication.DisabledException;
+import org.springframework.security.authentication.InternalAuthenticationServiceException;
import org.springframework.security.authentication.LockedException;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.GrantedAuthority;
@@ -142,12 +144,15 @@ protected DirContextOperations doAuthentication(
UsernamePasswordAuthenticationToken auth) {
String username = auth.getName();
String password = (String) auth.getCredentials();
-
- DirContext ctx = bindAsUser(username, password);
+ DirContext ctx = null;
try {
+ ctx = bindAsUser(username, password);
return searchForUser(ctx, username);
}
+ catch (CommunicationException e) {
+ throw badLdapConnection(e);
+ }
catch (NamingException e) {
logger.error("Failed to locate directory entry for authenticated user: "
+ username, e);
@@ -210,8 +215,7 @@ private DirContext bindAsUser(String username, String password) {
|| (e instanceof OperationNotSupportedException)) {
handleBindException(bindPrincipal, e);
throw badCredentials(e);
- }
- else {
+ } else {
throw LdapUtils.convertLdapException(e);
}
}
@@ -313,6 +317,12 @@ private BadCredentialsException badCredentials(Throwable cause) {
return (BadCredentialsException) badCredentials().initCause(cause);
}
+ private InternalAuthenticationServiceException badLdapConnection(Throwable cause) {
+ return new InternalAuthenticationServiceException(messages.getMessage(
+ "LdapAuthenticationProvider.badLdapConnection",
+ "Connection to LDAP server failed."), cause);
+ }
+
private DirContextOperations searchForUser(DirContext context, String username)
throws NamingException {
SearchControls searchControls = new SearchControls();
@@ -327,6 +337,9 @@ private DirContextOperations searchForUser(DirContext context, String username)
searchControls, searchRoot, searchFilter,
new Object[] { bindPrincipal, username });
}
+ catch (CommunicationException ldapCommunicationException) {
+ throw badLdapConnection(ldapCommunicationException);
+ }
catch (IncorrectResultSizeDataAccessException incorrectResults) {
// Search should never return multiple results if properly configured - just
// rethrow
diff --git a/ldap/src/main/java/org/springframework/security/ldap/server/ApacheDSContainer.java b/ldap/src/main/java/org/springframework/security/ldap/server/ApacheDSContainer.java
index 9aad1749755..f02dd51edc8 100644
--- a/ldap/src/main/java/org/springframework/security/ldap/server/ApacheDSContainer.java
+++ b/ldap/src/main/java/org/springframework/security/ldap/server/ApacheDSContainer.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2016 the original author or authors.
+ * Copyright 2002-2020 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.
@@ -15,6 +15,7 @@
*/
package org.springframework.security.ldap.server;
+import java.net.InetSocketAddress;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
@@ -39,6 +40,7 @@
import org.apache.directory.server.protocol.shared.transport.TcpTransport;
import org.apache.directory.shared.ldap.exception.LdapNameNotFoundException;
import org.apache.directory.shared.ldap.name.LdapDN;
+import org.apache.mina.transport.socket.SocketAcceptor;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.DisposableBean;
import org.springframework.beans.factory.InitializingBean;
@@ -69,6 +71,7 @@
* @author Luke Taylor
* @author Rob Winch
* @author Gunnar Hillert
+ * @author Evgeniy Cheban
* @deprecated Use {@link UnboundIdContainer} instead because ApacheDS 1.x is no longer
* supported with no GA version to replace it.
*/
@@ -80,6 +83,7 @@ public class ApacheDSContainer implements InitializingBean, DisposableBean, Life
final DefaultDirectoryService service;
LdapServer server;
+ private TcpTransport transport;
private ApplicationContext ctxt;
private File workingDir;
@@ -88,6 +92,7 @@ public class ApacheDSContainer implements InitializingBean, DisposableBean, Life
private final JdbmPartition partition;
private final String root;
private int port = 53389;
+ private int localPort;
private boolean ldapOverSslEnabled;
private File keyStoreFile;
@@ -143,7 +148,7 @@ public void afterPropertiesSet() throws Exception {
server.setDirectoryService(service);
// AbstractLdapIntegrationTests assume IPv4, so we specify the same here
- TcpTransport transport = new TcpTransport(port);
+ this.transport = new TcpTransport(port);
if (ldapOverSslEnabled) {
transport.setEnableSSL(true);
server.setKeystoreFile(this.keyStoreFile.getAbsolutePath());
@@ -190,6 +195,15 @@ public int getPort() {
return this.port;
}
+ /**
+ * Returns the port that is resolved by {@link TcpTransport}.
+ *
+ * @return the port that is resolved by {@link TcpTransport}
+ */
+ public int getLocalPort() {
+ return this.localPort;
+ }
+
/**
* If set to {@code true} will enable LDAP over SSL (LDAPs). If set to {@code true}
* {@link ApacheDSContainer#setCertificatePassord(String)} must be set as well.
@@ -262,6 +276,10 @@ public void start() {
logger.error("Lookup failed", e);
}
+ SocketAcceptor socketAcceptor = this.server.getSocketAcceptor(this.transport);
+ InetSocketAddress localAddress = socketAcceptor.getLocalAddress();
+ this.localPort = localAddress.getPort();
+
running = true;
try {
diff --git a/ldap/src/test/java/org/springframework/security/ldap/authentication/ad/ActiveDirectoryLdapAuthenticationProviderTests.java b/ldap/src/test/java/org/springframework/security/ldap/authentication/ad/ActiveDirectoryLdapAuthenticationProviderTests.java
index 934b507d056..27226bd6310 100644
--- a/ldap/src/test/java/org/springframework/security/ldap/authentication/ad/ActiveDirectoryLdapAuthenticationProviderTests.java
+++ b/ldap/src/test/java/org/springframework/security/ldap/authentication/ad/ActiveDirectoryLdapAuthenticationProviderTests.java
@@ -15,6 +15,18 @@
*/
package org.springframework.security.ldap.authentication.ad;
+import java.util.Collections;
+import java.util.Hashtable;
+import javax.naming.AuthenticationException;
+import javax.naming.CommunicationException;
+import javax.naming.Name;
+import javax.naming.NameNotFoundException;
+import javax.naming.NamingEnumeration;
+import javax.naming.NamingException;
+import javax.naming.directory.DirContext;
+import javax.naming.directory.SearchControls;
+import javax.naming.directory.SearchResult;
+
import org.apache.directory.shared.ldap.util.EmptyEnumeration;
import org.hamcrest.BaseMatcher;
import org.hamcrest.CoreMatchers;
@@ -25,6 +37,7 @@
import org.junit.Test;
import org.junit.rules.ExpectedException;
import org.mockito.ArgumentCaptor;
+
import org.springframework.dao.IncorrectResultSizeDataAccessException;
import org.springframework.ldap.core.DirContextAdapter;
import org.springframework.ldap.core.DistinguishedName;
@@ -32,25 +45,18 @@
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.authentication.CredentialsExpiredException;
import org.springframework.security.authentication.DisabledException;
+import org.springframework.security.authentication.InternalAuthenticationServiceException;
import org.springframework.security.authentication.LockedException;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
-import javax.naming.AuthenticationException;
-import javax.naming.CommunicationException;
-import javax.naming.Name;
-import javax.naming.NameNotFoundException;
-import javax.naming.NamingEnumeration;
-import javax.naming.NamingException;
-import javax.naming.directory.DirContext;
-import javax.naming.directory.SearchControls;
-import javax.naming.directory.SearchResult;
-
-import java.util.Hashtable;
-
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.fail;
-import static org.mockito.Mockito.*;
+import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.eq;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
import static org.springframework.security.ldap.authentication.ad.ActiveDirectoryLdapAuthenticationProvider.ContextFactory;
/**
@@ -58,6 +64,9 @@
* @author Rob Winch
*/
public class ActiveDirectoryLdapAuthenticationProviderTests {
+ public static final String EXISTING_LDAP_PROVIDER = "ldap://192.168.1.200/";
+ public static final String NON_EXISTING_LDAP_PROVIDER = "ldap://192.168.1.201/";
+
@Rule
public ExpectedException thrown = ExpectedException.none();
@@ -378,16 +387,31 @@ public void errorWithNoSubcodeIsHandledCleanly() {
}
@Test(expected = org.springframework.ldap.CommunicationException.class)
- public void nonAuthenticationExceptionIsConvertedToSpringLdapException() {
- provider.contextFactory = createContextFactoryThrowing(new CommunicationException(
- msg));
- provider.authenticate(joe);
+ public void nonAuthenticationExceptionIsConvertedToSpringLdapException() throws Throwable {
+ try {
+ provider.contextFactory = createContextFactoryThrowing(new CommunicationException(
+ msg));
+ provider.authenticate(joe);
+ } catch (InternalAuthenticationServiceException e) {
+ // Since GH-8418 ldap communication exception is wrapped into InternalAuthenticationServiceException.
+ // This test is about the wrapped exception, so we throw it.
+ throw e.getCause();
+ }
+ }
+
+ @Test(expected = org.springframework.security.authentication.InternalAuthenticationServiceException.class )
+ public void connectionExceptionIsWrappedInInternalException() throws Exception {
+ ActiveDirectoryLdapAuthenticationProvider noneReachableProvider = new ActiveDirectoryLdapAuthenticationProvider(
+ "mydomain.eu", NON_EXISTING_LDAP_PROVIDER, "dc=ad,dc=eu,dc=mydomain");
+ noneReachableProvider.setContextEnvironmentProperties(
+ Collections.singletonMap("com.sun.jndi.ldap.connect.timeout", "5"));
+ noneReachableProvider.doAuthentication(joe);
}
@Test
public void rootDnProvidedSeparatelyFromDomainAlsoWorks() throws Exception {
ActiveDirectoryLdapAuthenticationProvider provider = new ActiveDirectoryLdapAuthenticationProvider(
- "mydomain.eu", "ldap://192.168.1.200/", "dc=ad,dc=eu,dc=mydomain");
+ "mydomain.eu", EXISTING_LDAP_PROVIDER, "dc=ad,dc=eu,dc=mydomain");
checkAuthentication("dc=ad,dc=eu,dc=mydomain", provider);
}
@@ -413,8 +437,11 @@ public void contextEnvironmentPropertiesUsed() {
provider.authenticate(joe);
fail("CommunicationException was expected with a root cause of ClassNotFoundException");
}
- catch (org.springframework.ldap.CommunicationException expected) {
- assertThat(expected.getRootCause()).isInstanceOf(ClassNotFoundException.class);
+ catch (InternalAuthenticationServiceException expected) {
+ assertThat(expected.getCause()).isInstanceOf(org.springframework.ldap.CommunicationException.class);
+ org.springframework.ldap.CommunicationException cause =
+ (org.springframework.ldap.CommunicationException) expected.getCause();
+ assertThat(cause.getRootCause()).isInstanceOf(ClassNotFoundException.class);
}
}
diff --git a/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/AuthorizedClientServiceOAuth2AuthorizedClientManager.java b/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/AuthorizedClientServiceOAuth2AuthorizedClientManager.java
index 168551ba168..179e4f40a8f 100644
--- a/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/AuthorizedClientServiceOAuth2AuthorizedClientManager.java
+++ b/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/AuthorizedClientServiceOAuth2AuthorizedClientManager.java
@@ -72,9 +72,13 @@
* @see OAuth2AuthorizationFailureHandler
*/
public final class AuthorizedClientServiceOAuth2AuthorizedClientManager implements OAuth2AuthorizedClientManager {
+ private static final OAuth2AuthorizedClientProvider DEFAULT_AUTHORIZED_CLIENT_PROVIDER =
+ OAuth2AuthorizedClientProviderBuilder.builder()
+ .clientCredentials()
+ .build();
private final ClientRegistrationRepository clientRegistrationRepository;
private final OAuth2AuthorizedClientService authorizedClientService;
- private OAuth2AuthorizedClientProvider authorizedClientProvider = context -> null;
+ private OAuth2AuthorizedClientProvider authorizedClientProvider;
private Function> contextAttributesMapper;
private OAuth2AuthorizationSuccessHandler authorizationSuccessHandler;
private OAuth2AuthorizationFailureHandler authorizationFailureHandler;
@@ -91,6 +95,7 @@ public AuthorizedClientServiceOAuth2AuthorizedClientManager(ClientRegistrationRe
Assert.notNull(authorizedClientService, "authorizedClientService cannot be null");
this.clientRegistrationRepository = clientRegistrationRepository;
this.authorizedClientService = authorizedClientService;
+ this.authorizedClientProvider = DEFAULT_AUTHORIZED_CLIENT_PROVIDER;
this.contextAttributesMapper = new DefaultContextAttributesMapper();
this.authorizationSuccessHandler = (authorizedClient, principal, attributes) ->
authorizedClientService.saveAuthorizedClient(authorizedClient, principal);
diff --git a/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/AuthorizedClientServiceReactiveOAuth2AuthorizedClientManager.java b/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/AuthorizedClientServiceReactiveOAuth2AuthorizedClientManager.java
index 70393299b8f..3a446ec2da8 100644
--- a/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/AuthorizedClientServiceReactiveOAuth2AuthorizedClientManager.java
+++ b/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/AuthorizedClientServiceReactiveOAuth2AuthorizedClientManager.java
@@ -69,9 +69,13 @@
public final class AuthorizedClientServiceReactiveOAuth2AuthorizedClientManager
implements ReactiveOAuth2AuthorizedClientManager {
+ private static final ReactiveOAuth2AuthorizedClientProvider DEFAULT_AUTHORIZED_CLIENT_PROVIDER =
+ ReactiveOAuth2AuthorizedClientProviderBuilder.builder()
+ .clientCredentials()
+ .build();
private final ReactiveClientRegistrationRepository clientRegistrationRepository;
private final ReactiveOAuth2AuthorizedClientService authorizedClientService;
- private ReactiveOAuth2AuthorizedClientProvider authorizedClientProvider = context -> Mono.empty();
+ private ReactiveOAuth2AuthorizedClientProvider authorizedClientProvider = DEFAULT_AUTHORIZED_CLIENT_PROVIDER;
private Function>> contextAttributesMapper = new DefaultContextAttributesMapper();
private ReactiveOAuth2AuthorizationSuccessHandler authorizationSuccessHandler;
private ReactiveOAuth2AuthorizationFailureHandler authorizationFailureHandler;
diff --git a/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/JdbcOAuth2AuthorizedClientService.java b/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/JdbcOAuth2AuthorizedClientService.java
index a4ac9071233..cd3da393acd 100644
--- a/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/JdbcOAuth2AuthorizedClientService.java
+++ b/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/JdbcOAuth2AuthorizedClientService.java
@@ -16,6 +16,7 @@
package org.springframework.security.oauth2.client;
import org.springframework.dao.DataRetrievalFailureException;
+import org.springframework.dao.DuplicateKeyException;
import org.springframework.jdbc.core.ArgumentPreparedStatementSetter;
import org.springframework.jdbc.core.JdbcOperations;
import org.springframework.jdbc.core.PreparedStatementSetter;
@@ -52,6 +53,7 @@
* and therefore MUST be defined in the database schema.
*
* @author Joe Grandja
+ * @author Stav Shamir
* @since 5.3
* @see OAuth2AuthorizedClientService
* @see OAuth2AuthorizedClient
@@ -77,6 +79,11 @@ public class JdbcOAuth2AuthorizedClientService implements OAuth2AuthorizedClient
" (" + COLUMN_NAMES + ") VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)";
private static final String REMOVE_AUTHORIZED_CLIENT_SQL = "DELETE FROM " + TABLE_NAME +
" WHERE " + PK_FILTER;
+ private static final String UPDATE_AUTHORIZED_CLIENT_SQL = "UPDATE " + TABLE_NAME +
+ " SET access_token_type = ?, access_token_value = ?, access_token_issued_at = ?," +
+ " access_token_expires_at = ?, access_token_scopes = ?," +
+ " refresh_token_value = ?, refresh_token_issued_at = ?" +
+ " WHERE " + PK_FILTER;
protected final JdbcOperations jdbcOperations;
protected RowMapper authorizedClientRowMapper;
protected Function> authorizedClientParametersMapper;
@@ -120,6 +127,35 @@ public void saveAuthorizedClient(OAuth2AuthorizedClient authorizedClient, Authen
Assert.notNull(authorizedClient, "authorizedClient cannot be null");
Assert.notNull(principal, "principal cannot be null");
+ boolean existsAuthorizedClient = null != this.loadAuthorizedClient(
+ authorizedClient.getClientRegistration().getRegistrationId(), principal.getName());
+
+ if (existsAuthorizedClient) {
+ updateAuthorizedClient(authorizedClient, principal);
+ } else {
+ try {
+ insertAuthorizedClient(authorizedClient, principal);
+ } catch (DuplicateKeyException e) {
+ updateAuthorizedClient(authorizedClient, principal);
+ }
+ }
+ }
+
+ private void updateAuthorizedClient(OAuth2AuthorizedClient authorizedClient, Authentication principal) {
+ List parameters = this.authorizedClientParametersMapper.apply(
+ new OAuth2AuthorizedClientHolder(authorizedClient, principal));
+
+ SqlParameterValue clientRegistrationIdParameter = parameters.remove(0);
+ SqlParameterValue principalNameParameter = parameters.remove(0);
+ parameters.add(clientRegistrationIdParameter);
+ parameters.add(principalNameParameter);
+
+ PreparedStatementSetter pss = new ArgumentPreparedStatementSetter(parameters.toArray());
+
+ this.jdbcOperations.update(UPDATE_AUTHORIZED_CLIENT_SQL, pss);
+ }
+
+ private void insertAuthorizedClient(OAuth2AuthorizedClient authorizedClient, Authentication principal) {
List parameters = this.authorizedClientParametersMapper.apply(
new OAuth2AuthorizedClientHolder(authorizedClient, principal));
PreparedStatementSetter pss = new ArgumentPreparedStatementSetter(parameters.toArray());
diff --git a/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/authentication/OAuth2AuthorizationCodeAuthenticationProvider.java b/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/authentication/OAuth2AuthorizationCodeAuthenticationProvider.java
index 938fd9c2155..4ad62b09ce6 100644
--- a/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/authentication/OAuth2AuthorizationCodeAuthenticationProvider.java
+++ b/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/authentication/OAuth2AuthorizationCodeAuthenticationProvider.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2018 the original author or authors.
+ * Copyright 2002-2020 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.
@@ -20,7 +20,11 @@
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.oauth2.client.endpoint.OAuth2AccessTokenResponseClient;
import org.springframework.security.oauth2.client.endpoint.OAuth2AuthorizationCodeGrantRequest;
+import org.springframework.security.oauth2.core.OAuth2AuthorizationException;
+import org.springframework.security.oauth2.core.OAuth2Error;
import org.springframework.security.oauth2.core.endpoint.OAuth2AccessTokenResponse;
+import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationRequest;
+import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationResponse;
import org.springframework.util.Assert;
/**
@@ -40,6 +44,7 @@
* @see Section 4.1.4 Access Token Response
*/
public class OAuth2AuthorizationCodeAuthenticationProvider implements AuthenticationProvider {
+ private static final String INVALID_STATE_PARAMETER_ERROR_CODE = "invalid_state_parameter";
private final OAuth2AccessTokenResponseClient accessTokenResponseClient;
/**
@@ -59,8 +64,18 @@ public Authentication authenticate(Authentication authentication) throws Authent
OAuth2AuthorizationCodeAuthenticationToken authorizationCodeAuthentication =
(OAuth2AuthorizationCodeAuthenticationToken) authentication;
- OAuth2AuthorizationExchangeValidator.validate(
- authorizationCodeAuthentication.getAuthorizationExchange());
+ OAuth2AuthorizationResponse authorizationResponse = authorizationCodeAuthentication
+ .getAuthorizationExchange().getAuthorizationResponse();
+ if (authorizationResponse.statusError()) {
+ throw new OAuth2AuthorizationException(authorizationResponse.getError());
+ }
+
+ OAuth2AuthorizationRequest authorizationRequest = authorizationCodeAuthentication
+ .getAuthorizationExchange().getAuthorizationRequest();
+ if (!authorizationResponse.getState().equals(authorizationRequest.getState())) {
+ OAuth2Error oauth2Error = new OAuth2Error(INVALID_STATE_PARAMETER_ERROR_CODE);
+ throw new OAuth2AuthorizationException(oauth2Error);
+ }
OAuth2AccessTokenResponse accessTokenResponse =
this.accessTokenResponseClient.getTokenResponse(
@@ -73,7 +88,8 @@ public Authentication authenticate(Authentication authentication) throws Authent
authorizationCodeAuthentication.getClientRegistration(),
authorizationCodeAuthentication.getAuthorizationExchange(),
accessTokenResponse.getAccessToken(),
- accessTokenResponse.getRefreshToken());
+ accessTokenResponse.getRefreshToken(),
+ accessTokenResponse.getAdditionalParameters());
authenticationResult.setDetails(authorizationCodeAuthentication.getDetails());
return authenticationResult;
diff --git a/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/authentication/OAuth2AuthorizationCodeReactiveAuthenticationManager.java b/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/authentication/OAuth2AuthorizationCodeReactiveAuthenticationManager.java
index 4ecdd3b85cc..28e4d7bdfe9 100644
--- a/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/authentication/OAuth2AuthorizationCodeReactiveAuthenticationManager.java
+++ b/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/authentication/OAuth2AuthorizationCodeReactiveAuthenticationManager.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2018 the original author or authors.
+ * Copyright 2002-2020 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.
@@ -22,9 +22,13 @@
import org.springframework.security.oauth2.client.registration.ClientRegistration;
import org.springframework.security.oauth2.client.userinfo.ReactiveOAuth2UserService;
import org.springframework.security.oauth2.core.OAuth2AccessToken;
+import org.springframework.security.oauth2.core.OAuth2AuthorizationException;
+import org.springframework.security.oauth2.core.OAuth2Error;
import org.springframework.security.oauth2.core.OAuth2RefreshToken;
import org.springframework.security.oauth2.core.endpoint.OAuth2AccessTokenResponse;
import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationExchange;
+import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationRequest;
+import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationResponse;
import org.springframework.security.oauth2.core.user.OAuth2User;
import org.springframework.util.Assert;
import reactor.core.publisher.Mono;
@@ -55,8 +59,8 @@
* @see Section 4.1.3 Access Token Request
* @see Section 4.1.4 Access Token Response
*/
-public class OAuth2AuthorizationCodeReactiveAuthenticationManager implements
- ReactiveAuthenticationManager {
+public class OAuth2AuthorizationCodeReactiveAuthenticationManager implements ReactiveAuthenticationManager {
+ private static final String INVALID_STATE_PARAMETER_ERROR_CODE = "invalid_state_parameter";
private final ReactiveOAuth2AccessTokenResponseClient accessTokenResponseClient;
public OAuth2AuthorizationCodeReactiveAuthenticationManager(
@@ -70,7 +74,16 @@ public Mono authenticate(Authentication authentication) {
return Mono.defer(() -> {
OAuth2AuthorizationCodeAuthenticationToken token = (OAuth2AuthorizationCodeAuthenticationToken) authentication;
- OAuth2AuthorizationExchangeValidator.validate(token.getAuthorizationExchange());
+ OAuth2AuthorizationResponse authorizationResponse = token.getAuthorizationExchange().getAuthorizationResponse();
+ if (authorizationResponse.statusError()) {
+ return Mono.error(new OAuth2AuthorizationException(authorizationResponse.getError()));
+ }
+
+ OAuth2AuthorizationRequest authorizationRequest = token.getAuthorizationExchange().getAuthorizationRequest();
+ if (!authorizationResponse.getState().equals(authorizationRequest.getState())) {
+ OAuth2Error oauth2Error = new OAuth2Error(INVALID_STATE_PARAMETER_ERROR_CODE);
+ return Mono.error(new OAuth2AuthorizationException(oauth2Error));
+ }
OAuth2AuthorizationCodeGrantRequest authzRequest = new OAuth2AuthorizationCodeGrantRequest(
token.getClientRegistration(),
diff --git a/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/authentication/OAuth2AuthorizationExchangeValidator.java b/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/authentication/OAuth2AuthorizationExchangeValidator.java
deleted file mode 100644
index a240e0521a8..00000000000
--- a/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/authentication/OAuth2AuthorizationExchangeValidator.java
+++ /dev/null
@@ -1,47 +0,0 @@
-/*
- * Copyright 2002-2019 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.oauth2.client.authentication;
-
-import org.springframework.security.oauth2.core.OAuth2AuthorizationException;
-import org.springframework.security.oauth2.core.OAuth2Error;
-import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationExchange;
-import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationRequest;
-import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationResponse;
-
-/**
- * A validator for an "exchange" of an OAuth 2.0 Authorization Request and Response.
- *
- * @author Joe Grandja
- * @since 5.1
- * @see OAuth2AuthorizationExchange
- */
-final class OAuth2AuthorizationExchangeValidator {
- private static final String INVALID_STATE_PARAMETER_ERROR_CODE = "invalid_state_parameter";
-
- static void validate(OAuth2AuthorizationExchange authorizationExchange) {
- OAuth2AuthorizationRequest authorizationRequest = authorizationExchange.getAuthorizationRequest();
- OAuth2AuthorizationResponse authorizationResponse = authorizationExchange.getAuthorizationResponse();
-
- if (authorizationResponse.statusError()) {
- throw new OAuth2AuthorizationException(authorizationResponse.getError());
- }
-
- if (!authorizationResponse.getState().equals(authorizationRequest.getState())) {
- OAuth2Error oauth2Error = new OAuth2Error(INVALID_STATE_PARAMETER_ERROR_CODE);
- throw new OAuth2AuthorizationException(oauth2Error);
- }
- }
-}
diff --git a/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/authentication/OAuth2LoginAuthenticationProvider.java b/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/authentication/OAuth2LoginAuthenticationProvider.java
index 02d4b5e7b1f..8d3b70234d8 100644
--- a/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/authentication/OAuth2LoginAuthenticationProvider.java
+++ b/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/authentication/OAuth2LoginAuthenticationProvider.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2018 the original author or authors.
+ * Copyright 2002-2020 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.
@@ -28,7 +28,6 @@
import org.springframework.security.oauth2.core.OAuth2AuthenticationException;
import org.springframework.security.oauth2.core.OAuth2AuthorizationException;
import org.springframework.security.oauth2.core.OAuth2Error;
-import org.springframework.security.oauth2.core.endpoint.OAuth2AccessTokenResponse;
import org.springframework.security.oauth2.core.user.OAuth2User;
import org.springframework.util.Assert;
@@ -60,7 +59,7 @@
* @see Section 4.1.4 Access Token Response
*/
public class OAuth2LoginAuthenticationProvider implements AuthenticationProvider {
- private final OAuth2AccessTokenResponseClient accessTokenResponseClient;
+ private final OAuth2AuthorizationCodeAuthenticationProvider authorizationCodeAuthenticationProvider;
private final OAuth2UserService userService;
private GrantedAuthoritiesMapper authoritiesMapper = (authorities -> authorities);
@@ -74,59 +73,54 @@ public OAuth2LoginAuthenticationProvider(
OAuth2AccessTokenResponseClient accessTokenResponseClient,
OAuth2UserService userService) {
- Assert.notNull(accessTokenResponseClient, "accessTokenResponseClient cannot be null");
Assert.notNull(userService, "userService cannot be null");
- this.accessTokenResponseClient = accessTokenResponseClient;
+ this.authorizationCodeAuthenticationProvider = new OAuth2AuthorizationCodeAuthenticationProvider(accessTokenResponseClient);
this.userService = userService;
}
@Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
- OAuth2LoginAuthenticationToken authorizationCodeAuthentication =
+ OAuth2LoginAuthenticationToken loginAuthenticationToken =
(OAuth2LoginAuthenticationToken) authentication;
// Section 3.1.2.1 Authentication Request - https://openid.net/specs/openid-connect-core-1_0.html#AuthRequest
// scope
// REQUIRED. OpenID Connect requests MUST contain the "openid" scope value.
- if (authorizationCodeAuthentication.getAuthorizationExchange()
+ if (loginAuthenticationToken.getAuthorizationExchange()
.getAuthorizationRequest().getScopes().contains("openid")) {
// This is an OpenID Connect Authentication Request so return null
// and let OidcAuthorizationCodeAuthenticationProvider handle it instead
return null;
}
- OAuth2AccessTokenResponse accessTokenResponse;
+ OAuth2AuthorizationCodeAuthenticationToken authorizationCodeAuthenticationToken;
try {
- OAuth2AuthorizationExchangeValidator.validate(
- authorizationCodeAuthentication.getAuthorizationExchange());
-
- accessTokenResponse = this.accessTokenResponseClient.getTokenResponse(
- new OAuth2AuthorizationCodeGrantRequest(
- authorizationCodeAuthentication.getClientRegistration(),
- authorizationCodeAuthentication.getAuthorizationExchange()));
-
+ authorizationCodeAuthenticationToken = (OAuth2AuthorizationCodeAuthenticationToken) this.authorizationCodeAuthenticationProvider
+ .authenticate(new OAuth2AuthorizationCodeAuthenticationToken(
+ loginAuthenticationToken.getClientRegistration(),
+ loginAuthenticationToken.getAuthorizationExchange()));
} catch (OAuth2AuthorizationException ex) {
OAuth2Error oauth2Error = ex.getError();
throw new OAuth2AuthenticationException(oauth2Error, oauth2Error.toString());
}
- OAuth2AccessToken accessToken = accessTokenResponse.getAccessToken();
- Map additionalParameters = accessTokenResponse.getAdditionalParameters();
+ OAuth2AccessToken accessToken = authorizationCodeAuthenticationToken.getAccessToken();
+ Map additionalParameters = authorizationCodeAuthenticationToken.getAdditionalParameters();
OAuth2User oauth2User = this.userService.loadUser(new OAuth2UserRequest(
- authorizationCodeAuthentication.getClientRegistration(), accessToken, additionalParameters));
+ loginAuthenticationToken.getClientRegistration(), accessToken, additionalParameters));
Collection extends GrantedAuthority> mappedAuthorities =
this.authoritiesMapper.mapAuthorities(oauth2User.getAuthorities());
OAuth2LoginAuthenticationToken authenticationResult = new OAuth2LoginAuthenticationToken(
- authorizationCodeAuthentication.getClientRegistration(),
- authorizationCodeAuthentication.getAuthorizationExchange(),
+ loginAuthenticationToken.getClientRegistration(),
+ loginAuthenticationToken.getAuthorizationExchange(),
oauth2User,
mappedAuthorities,
accessToken,
- accessTokenResponse.getRefreshToken());
- authenticationResult.setDetails(authorizationCodeAuthentication.getDetails());
+ authorizationCodeAuthenticationToken.getRefreshToken());
+ authenticationResult.setDetails(loginAuthenticationToken.getDetails());
return authenticationResult;
}
diff --git a/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/authentication/OAuth2LoginReactiveAuthenticationManager.java b/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/authentication/OAuth2LoginReactiveAuthenticationManager.java
index 30c597d9558..9fb1820ff51 100644
--- a/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/authentication/OAuth2LoginReactiveAuthenticationManager.java
+++ b/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/authentication/OAuth2LoginReactiveAuthenticationManager.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2018 the original author or authors.
+ * Copyright 2002-2020 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.
@@ -95,6 +95,18 @@ public Mono authenticate(Authentication authentication) {
});
}
+ /**
+ * Sets the {@link GrantedAuthoritiesMapper} used for mapping {@link OAuth2User#getAuthorities()}
+ * to a new set of authorities which will be associated to the {@link OAuth2LoginAuthenticationToken}.
+ *
+ * @since 5.4
+ * @param authoritiesMapper the {@link GrantedAuthoritiesMapper} used for mapping the user's authorities
+ */
+ public final void setAuthoritiesMapper(GrantedAuthoritiesMapper authoritiesMapper) {
+ Assert.notNull(authoritiesMapper, "authoritiesMapper cannot be null");
+ this.authoritiesMapper = authoritiesMapper;
+ }
+
private Mono onSuccess(OAuth2AuthorizationCodeAuthenticationToken authentication) {
OAuth2AccessToken accessToken = authentication.getAccessToken();
Map additionalParameters = authentication.getAdditionalParameters();
diff --git a/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/jackson2/ClientRegistrationDeserializer.java b/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/jackson2/ClientRegistrationDeserializer.java
index 86895f328de..f1e52113b5a 100644
--- a/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/jackson2/ClientRegistrationDeserializer.java
+++ b/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/jackson2/ClientRegistrationDeserializer.java
@@ -78,6 +78,7 @@ public ClientRegistration deserialize(JsonParser parser, DeserializationContext
findObjectNode(userInfoEndpointNode, "authenticationMethod")))
.userNameAttributeName(findStringValue(userInfoEndpointNode, "userNameAttributeName"))
.jwkSetUri(findStringValue(providerDetailsNode, "jwkSetUri"))
+ .issuerUri(findStringValue(providerDetailsNode, "issuerUri"))
.providerConfigurationMetadata(findValue(providerDetailsNode, "configurationMetadata", MAP_TYPE_REFERENCE, mapper))
.build();
}
diff --git a/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/oidc/authentication/OidcAuthorizationCodeReactiveAuthenticationManager.java b/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/oidc/authentication/OidcAuthorizationCodeReactiveAuthenticationManager.java
index 9852ff14795..a413dca1459 100644
--- a/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/oidc/authentication/OidcAuthorizationCodeReactiveAuthenticationManager.java
+++ b/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/oidc/authentication/OidcAuthorizationCodeReactiveAuthenticationManager.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2019 the original author or authors.
+ * Copyright 2002-2020 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.
@@ -121,13 +121,14 @@ public Mono authenticate(Authentication authentication) {
.getAuthorizationExchange().getAuthorizationResponse();
if (authorizationResponse.statusError()) {
- throw new OAuth2AuthenticationException(
- authorizationResponse.getError(), authorizationResponse.getError().toString());
+ return Mono.error(new OAuth2AuthenticationException(
+ authorizationResponse.getError(), authorizationResponse.getError().toString()));
}
if (!authorizationResponse.getState().equals(authorizationRequest.getState())) {
OAuth2Error oauth2Error = new OAuth2Error(INVALID_STATE_PARAMETER_ERROR_CODE);
- throw new OAuth2AuthenticationException(oauth2Error, oauth2Error.toString());
+ return Mono.error(new OAuth2AuthenticationException(
+ oauth2Error, oauth2Error.toString()));
}
OAuth2AuthorizationCodeGrantRequest authzRequest = new OAuth2AuthorizationCodeGrantRequest(
@@ -139,7 +140,7 @@ public Mono authenticate(Authentication authentication) {
.onErrorMap(OAuth2AuthorizationException.class, e -> new OAuth2AuthenticationException(e.getError(), e.getError().toString()))
.onErrorMap(JwtException.class, e -> {
OAuth2Error invalidIdTokenError = new OAuth2Error(INVALID_ID_TOKEN_ERROR_CODE, e.getMessage(), null);
- throw new OAuth2AuthenticationException(invalidIdTokenError, invalidIdTokenError.toString(), e);
+ return new OAuth2AuthenticationException(invalidIdTokenError, invalidIdTokenError.toString(), e);
});
});
}
@@ -156,6 +157,18 @@ public final void setJwtDecoderFactory(ReactiveJwtDecoderFactory authenticationResult(OAuth2AuthorizationCodeAuthenticationToken authorizationCodeAuthentication, OAuth2AccessTokenResponse accessTokenResponse) {
OAuth2AccessToken accessToken = accessTokenResponse.getAccessToken();
ClientRegistration clientRegistration = authorizationCodeAuthentication.getClientRegistration();
@@ -166,7 +179,7 @@ private Mono authenticationResult(OAuth2Authoriz
INVALID_ID_TOKEN_ERROR_CODE,
"Missing (required) ID Token in Token Response for Client Registration: " + clientRegistration.getRegistrationId(),
null);
- throw new OAuth2AuthenticationException(invalidIdTokenError, invalidIdTokenError.toString());
+ return Mono.error(new OAuth2AuthenticationException(invalidIdTokenError, invalidIdTokenError.toString()));
}
return createOidcToken(clientRegistration, accessTokenResponse)
diff --git a/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/oidc/authentication/OidcIdTokenValidator.java b/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/oidc/authentication/OidcIdTokenValidator.java
index 7ceaee3a70a..03ae739a7b8 100644
--- a/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/oidc/authentication/OidcIdTokenValidator.java
+++ b/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/oidc/authentication/OidcIdTokenValidator.java
@@ -33,6 +33,7 @@
import java.util.HashMap;
import java.util.List;
import java.util.Map;
+import java.util.Objects;
/**
* An {@link OAuth2TokenValidator} responsible for
@@ -68,7 +69,11 @@ public OAuth2TokenValidatorResult validate(Jwt idToken) {
// 2. The Issuer Identifier for the OpenID Provider (which is typically obtained during Discovery)
// MUST exactly match the value of the iss (issuer) Claim.
- // TODO Depends on gh-4413
+ String metadataIssuer = this.clientRegistration.getProviderDetails().getIssuerUri();
+
+ if (metadataIssuer != null && !Objects.equals(metadataIssuer, idToken.getIssuer().toExternalForm())) {
+ invalidClaims.put(IdTokenClaimNames.ISS, idToken.getIssuer());
+ }
// 3. The Client MUST validate that the aud (audience) Claim contains its client_id value
// registered at the Issuer identified by the iss (issuer) Claim as an audience.
diff --git a/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/registration/ClientRegistration.java b/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/registration/ClientRegistration.java
index e2185714562..2051fdcf2d4 100644
--- a/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/registration/ClientRegistration.java
+++ b/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/registration/ClientRegistration.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2019 the original author or authors.
+ * Copyright 2002-2020 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.
@@ -163,6 +163,7 @@ public class ProviderDetails implements Serializable {
private String tokenUri;
private UserInfoEndpoint userInfoEndpoint = new UserInfoEndpoint();
private String jwkSetUri;
+ private String issuerUri;
private Map configurationMetadata = Collections.emptyMap();
private ProviderDetails() {
@@ -204,6 +205,17 @@ public String getJwkSetUri() {
return this.jwkSetUri;
}
+ /**
+ * Returns the issuer identifier uri for the OpenID Connect 1.0 provider
+ * or the OAuth 2.0 Authorization Server.
+ *
+ * @since 5.4
+ * @return the issuer identifier uri for the OpenID Connect 1.0 provider or the OAuth 2.0 Authorization Server
+ */
+ public String getIssuerUri() {
+ return this.issuerUri;
+ }
+
/**
* Returns a {@code Map} of the metadata describing the provider's configuration.
*
@@ -296,6 +308,7 @@ public static class Builder implements Serializable {
private AuthenticationMethod userInfoAuthenticationMethod = AuthenticationMethod.HEADER;
private String userNameAttributeName;
private String jwkSetUri;
+ private String issuerUri;
private Map configurationMetadata = Collections.emptyMap();
private String clientName;
@@ -317,6 +330,7 @@ private Builder(ClientRegistration clientRegistration) {
this.userInfoAuthenticationMethod = clientRegistration.providerDetails.userInfoEndpoint.authenticationMethod;
this.userNameAttributeName = clientRegistration.providerDetails.userInfoEndpoint.userNameAttributeName;
this.jwkSetUri = clientRegistration.providerDetails.jwkSetUri;
+ this.issuerUri = clientRegistration.providerDetails.issuerUri;
Map configurationMetadata = clientRegistration.providerDetails.configurationMetadata;
if (configurationMetadata != EMPTY_MAP) {
this.configurationMetadata = new HashMap<>(configurationMetadata);
@@ -486,6 +500,19 @@ public Builder jwkSetUri(String jwkSetUri) {
return this;
}
+ /**
+ * Sets the issuer identifier uri for the OpenID Connect 1.0 provider
+ * or the OAuth 2.0 Authorization Server.
+ *
+ * @since 5.4
+ * @param issuerUri the issuer identifier uri for the OpenID Connect 1.0 provider or the OAuth 2.0 Authorization Server
+ * @return the {@link Builder}
+ */
+ public Builder issuerUri(String issuerUri) {
+ this.issuerUri = issuerUri;
+ return this;
+ }
+
/**
* Sets the metadata describing the provider's configuration.
*
@@ -554,6 +581,7 @@ private ClientRegistration create() {
providerDetails.userInfoEndpoint.authenticationMethod = this.userInfoAuthenticationMethod;
providerDetails.userInfoEndpoint.userNameAttributeName = this.userNameAttributeName;
providerDetails.jwkSetUri = this.jwkSetUri;
+ providerDetails.issuerUri = this.issuerUri;
providerDetails.configurationMetadata = Collections.unmodifiableMap(this.configurationMetadata);
clientRegistration.providerDetails = providerDetails;
diff --git a/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/registration/ClientRegistrations.java b/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/registration/ClientRegistrations.java
index 57d10aaadca..857b150db09 100644
--- a/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/registration/ClientRegistrations.java
+++ b/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/registration/ClientRegistrations.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2019 the original author or authors.
+ * Copyright 2002-2020 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.
@@ -146,9 +146,12 @@ private static Supplier oidc(URI issuer) {
RequestEntity request = RequestEntity.get(uri).build();
Map configuration = rest.exchange(request, typeReference).getBody();
OIDCProviderMetadata metadata = parse(configuration, OIDCProviderMetadata::parse);
- return withProviderConfiguration(metadata, issuer.toASCIIString())
- .jwkSetUri(metadata.getJWKSetURI().toASCIIString())
- .userInfoUri(metadata.getUserInfoEndpointURI().toASCIIString());
+ ClientRegistration.Builder builder = withProviderConfiguration(metadata, issuer.toASCIIString())
+ .jwkSetUri(metadata.getJWKSetURI().toASCIIString());
+ if (metadata.getUserInfoEndpointURI() != null) {
+ builder.userInfoUri(metadata.getUserInfoEndpointURI().toASCIIString());
+ }
+ return builder;
};
}
@@ -245,6 +248,7 @@ private static ClientRegistration.Builder withProviderConfiguration(Authorizatio
.authorizationUri(metadata.getAuthorizationEndpointURI().toASCIIString())
.providerConfigurationMetadata(configurationMetadata)
.tokenUri(metadata.getTokenEndpointURI().toASCIIString())
+ .issuerUri(issuer)
.clientName(issuer);
}
diff --git a/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/web/DefaultOAuth2AuthorizationRequestResolver.java b/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/web/DefaultOAuth2AuthorizationRequestResolver.java
index a97db4a26be..8060660f2c8 100644
--- a/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/web/DefaultOAuth2AuthorizationRequestResolver.java
+++ b/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/web/DefaultOAuth2AuthorizationRequestResolver.java
@@ -87,6 +87,9 @@ public DefaultOAuth2AuthorizationRequestResolver(ClientRegistrationRepository cl
@Override
public OAuth2AuthorizationRequest resolve(HttpServletRequest request) {
String registrationId = this.resolveRegistrationId(request);
+ if (registrationId == null) {
+ return null;
+ }
String redirectUriAction = getAction(request, "login");
return resolve(request, registrationId, redirectUriAction);
}
diff --git a/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/web/DefaultOAuth2AuthorizedClientManager.java b/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/web/DefaultOAuth2AuthorizedClientManager.java
index 2da4ab8af59..708db52020a 100644
--- a/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/web/DefaultOAuth2AuthorizedClientManager.java
+++ b/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/web/DefaultOAuth2AuthorizedClientManager.java
@@ -25,6 +25,7 @@
import org.springframework.security.oauth2.client.OAuth2AuthorizedClient;
import org.springframework.security.oauth2.client.OAuth2AuthorizedClientManager;
import org.springframework.security.oauth2.client.OAuth2AuthorizedClientProvider;
+import org.springframework.security.oauth2.client.OAuth2AuthorizedClientProviderBuilder;
import org.springframework.security.oauth2.client.RemoveAuthorizedClientOAuth2AuthorizationFailureHandler;
import org.springframework.security.oauth2.client.registration.ClientRegistration;
import org.springframework.security.oauth2.client.registration.ClientRegistrationRepository;
@@ -83,9 +84,16 @@
* @see OAuth2AuthorizationFailureHandler
*/
public final class DefaultOAuth2AuthorizedClientManager implements OAuth2AuthorizedClientManager {
+ private static final OAuth2AuthorizedClientProvider DEFAULT_AUTHORIZED_CLIENT_PROVIDER =
+ OAuth2AuthorizedClientProviderBuilder.builder()
+ .authorizationCode()
+ .refreshToken()
+ .clientCredentials()
+ .password()
+ .build();
private final ClientRegistrationRepository clientRegistrationRepository;
private final OAuth2AuthorizedClientRepository authorizedClientRepository;
- private OAuth2AuthorizedClientProvider authorizedClientProvider = context -> null;
+ private OAuth2AuthorizedClientProvider authorizedClientProvider;
private Function> contextAttributesMapper;
private OAuth2AuthorizationSuccessHandler authorizationSuccessHandler;
private OAuth2AuthorizationFailureHandler authorizationFailureHandler;
@@ -102,6 +110,7 @@ public DefaultOAuth2AuthorizedClientManager(ClientRegistrationRepository clientR
Assert.notNull(authorizedClientRepository, "authorizedClientRepository cannot be null");
this.clientRegistrationRepository = clientRegistrationRepository;
this.authorizedClientRepository = authorizedClientRepository;
+ this.authorizedClientProvider = DEFAULT_AUTHORIZED_CLIENT_PROVIDER;
this.contextAttributesMapper = new DefaultContextAttributesMapper();
this.authorizationSuccessHandler = (authorizedClient, principal, attributes) ->
authorizedClientRepository.saveAuthorizedClient(authorizedClient, principal,
diff --git a/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/web/DefaultReactiveOAuth2AuthorizedClientManager.java b/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/web/DefaultReactiveOAuth2AuthorizedClientManager.java
index 31ba1a2b174..a29a4c7e33c 100644
--- a/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/web/DefaultReactiveOAuth2AuthorizedClientManager.java
+++ b/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/web/DefaultReactiveOAuth2AuthorizedClientManager.java
@@ -23,6 +23,7 @@
import org.springframework.security.oauth2.client.ReactiveOAuth2AuthorizationSuccessHandler;
import org.springframework.security.oauth2.client.ReactiveOAuth2AuthorizedClientManager;
import org.springframework.security.oauth2.client.ReactiveOAuth2AuthorizedClientProvider;
+import org.springframework.security.oauth2.client.ReactiveOAuth2AuthorizedClientProviderBuilder;
import org.springframework.security.oauth2.client.RemoveAuthorizedClientReactiveOAuth2AuthorizationFailureHandler;
import org.springframework.security.oauth2.client.registration.ClientRegistration;
import org.springframework.security.oauth2.client.registration.ReactiveClientRegistrationRepository;
@@ -78,6 +79,13 @@
* @see ReactiveOAuth2AuthorizationFailureHandler
*/
public final class DefaultReactiveOAuth2AuthorizedClientManager implements ReactiveOAuth2AuthorizedClientManager {
+ private static final ReactiveOAuth2AuthorizedClientProvider DEFAULT_AUTHORIZED_CLIENT_PROVIDER =
+ ReactiveOAuth2AuthorizedClientProviderBuilder.builder()
+ .authorizationCode()
+ .refreshToken()
+ .clientCredentials()
+ .password()
+ .build();
private static final Mono currentServerWebExchangeMono = Mono.subscriberContext()
.filter(c -> c.hasKey(ServerWebExchange.class))
@@ -85,7 +93,7 @@ public final class DefaultReactiveOAuth2AuthorizedClientManager implements React
private final ReactiveClientRegistrationRepository clientRegistrationRepository;
private final ServerOAuth2AuthorizedClientRepository authorizedClientRepository;
- private ReactiveOAuth2AuthorizedClientProvider authorizedClientProvider = context -> Mono.empty();
+ private ReactiveOAuth2AuthorizedClientProvider authorizedClientProvider = DEFAULT_AUTHORIZED_CLIENT_PROVIDER;
private Function>> contextAttributesMapper = new DefaultContextAttributesMapper();
private ReactiveOAuth2AuthorizationSuccessHandler authorizationSuccessHandler;
private ReactiveOAuth2AuthorizationFailureHandler authorizationFailureHandler;
diff --git a/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/web/OAuth2AuthorizationCodeGrantFilter.java b/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/web/OAuth2AuthorizationCodeGrantFilter.java
index 4f8aaefbafa..8d2f157c457 100644
--- a/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/web/OAuth2AuthorizationCodeGrantFilter.java
+++ b/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/web/OAuth2AuthorizationCodeGrantFilter.java
@@ -83,6 +83,7 @@
*
*
* @author Joe Grandja
+ * @author Parikshit Dutta
* @since 5.1
* @see OAuth2AuthorizationCodeAuthenticationToken
* @see OAuth2AuthorizationCodeAuthenticationProvider
@@ -104,7 +105,7 @@ public class OAuth2AuthorizationCodeGrantFilter extends OncePerRequestFilter {
new HttpSessionOAuth2AuthorizationRequestRepository();
private final AuthenticationDetailsSource authenticationDetailsSource = new WebAuthenticationDetailsSource();
private final RedirectStrategy redirectStrategy = new DefaultRedirectStrategy();
- private final RequestCache requestCache = new HttpSessionRequestCache();
+ private RequestCache requestCache = new HttpSessionRequestCache();
/**
* Constructs an {@code OAuth2AuthorizationCodeGrantFilter} using the provided parameters.
@@ -134,6 +135,18 @@ public final void setAuthorizationRequestRepository(AuthorizationRequestReposito
this.authorizationRequestRepository = authorizationRequestRepository;
}
+ /**
+ * Sets the {@link RequestCache} used for loading a previously saved request (if available)
+ * and replaying it after completing the processing of the OAuth 2.0 Authorization Response.
+ *
+ * @since 5.4
+ * @param requestCache the cache used for loading a previously saved request (if available)
+ */
+ public final void setRequestCache(RequestCache requestCache) {
+ Assert.notNull(requestCache, "requestCache cannot be null");
+ this.requestCache = requestCache;
+ }
+
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
throws ServletException, IOException {
diff --git a/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/web/method/annotation/OAuth2AuthorizedClientArgumentResolver.java b/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/web/method/annotation/OAuth2AuthorizedClientArgumentResolver.java
index f0385fb885a..8241f1bce19 100644
--- a/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/web/method/annotation/OAuth2AuthorizedClientArgumentResolver.java
+++ b/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/web/method/annotation/OAuth2AuthorizedClientArgumentResolver.java
@@ -93,25 +93,9 @@ public OAuth2AuthorizedClientArgumentResolver(ClientRegistrationRepository clien
OAuth2AuthorizedClientRepository authorizedClientRepository) {
Assert.notNull(clientRegistrationRepository, "clientRegistrationRepository cannot be null");
Assert.notNull(authorizedClientRepository, "authorizedClientRepository cannot be null");
- this.authorizedClientManager = createDefaultAuthorizedClientManager(clientRegistrationRepository, authorizedClientRepository);
- this.defaultAuthorizedClientManager = true;
- }
-
- private static OAuth2AuthorizedClientManager createDefaultAuthorizedClientManager(
- ClientRegistrationRepository clientRegistrationRepository, OAuth2AuthorizedClientRepository authorizedClientRepository) {
-
- OAuth2AuthorizedClientProvider authorizedClientProvider =
- OAuth2AuthorizedClientProviderBuilder.builder()
- .authorizationCode()
- .refreshToken()
- .clientCredentials()
- .password()
- .build();
- DefaultOAuth2AuthorizedClientManager authorizedClientManager = new DefaultOAuth2AuthorizedClientManager(
+ this.authorizedClientManager = new DefaultOAuth2AuthorizedClientManager(
clientRegistrationRepository, authorizedClientRepository);
- authorizedClientManager.setAuthorizedClientProvider(authorizedClientProvider);
-
- return authorizedClientManager;
+ this.defaultAuthorizedClientManager = true;
}
@Override
diff --git a/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/web/reactive/function/client/ServerOAuth2AuthorizedClientExchangeFilterFunction.java b/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/web/reactive/function/client/ServerOAuth2AuthorizedClientExchangeFilterFunction.java
index 99dff8c2710..23232bd30a5 100644
--- a/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/web/reactive/function/client/ServerOAuth2AuthorizedClientExchangeFilterFunction.java
+++ b/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/web/reactive/function/client/ServerOAuth2AuthorizedClientExchangeFilterFunction.java
@@ -208,14 +208,6 @@ private static ReactiveOAuth2AuthorizedClientManager createDefaultAuthorizedClie
ServerOAuth2AuthorizedClientRepository authorizedClientRepository,
ReactiveOAuth2AuthorizationFailureHandler authorizationFailureHandler) {
- ReactiveOAuth2AuthorizedClientProvider authorizedClientProvider =
- ReactiveOAuth2AuthorizedClientProviderBuilder.builder()
- .authorizationCode()
- .refreshToken()
- .clientCredentials()
- .password()
- .build();
-
// gh-7544
if (authorizedClientRepository instanceof UnAuthenticatedServerOAuth2AuthorizedClientRepository) {
UnAuthenticatedReactiveOAuth2AuthorizedClientManager unauthenticatedAuthorizedClientManager =
@@ -223,13 +215,19 @@ private static ReactiveOAuth2AuthorizedClientManager createDefaultAuthorizedClie
clientRegistrationRepository,
(UnAuthenticatedServerOAuth2AuthorizedClientRepository) authorizedClientRepository,
authorizationFailureHandler);
- unauthenticatedAuthorizedClientManager.setAuthorizedClientProvider(authorizedClientProvider);
+ unauthenticatedAuthorizedClientManager.setAuthorizedClientProvider(
+ ReactiveOAuth2AuthorizedClientProviderBuilder.builder()
+ .authorizationCode()
+ .refreshToken()
+ .clientCredentials()
+ .password()
+ .build());
return unauthenticatedAuthorizedClientManager;
}
- DefaultReactiveOAuth2AuthorizedClientManager authorizedClientManager = new DefaultReactiveOAuth2AuthorizedClientManager(
- clientRegistrationRepository, authorizedClientRepository);
- authorizedClientManager.setAuthorizedClientProvider(authorizedClientProvider);
+ DefaultReactiveOAuth2AuthorizedClientManager authorizedClientManager =
+ new DefaultReactiveOAuth2AuthorizedClientManager(
+ clientRegistrationRepository, authorizedClientRepository);
authorizedClientManager.setAuthorizationFailureHandler(authorizationFailureHandler);
return authorizedClientManager;
diff --git a/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/web/reactive/function/client/ServletOAuth2AuthorizedClientExchangeFilterFunction.java b/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/web/reactive/function/client/ServletOAuth2AuthorizedClientExchangeFilterFunction.java
index 610adf2c2b3..8694121c8e2 100644
--- a/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/web/reactive/function/client/ServletOAuth2AuthorizedClientExchangeFilterFunction.java
+++ b/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/web/reactive/function/client/ServletOAuth2AuthorizedClientExchangeFilterFunction.java
@@ -216,32 +216,15 @@ public ServletOAuth2AuthorizedClientExchangeFilterFunction(
authorizedClientRepository.removeAuthorizedClient(clientRegistrationId, principal,
(HttpServletRequest) attributes.get(HttpServletRequest.class.getName()),
(HttpServletResponse) attributes.get(HttpServletResponse.class.getName())));
- this.authorizedClientManager = createDefaultAuthorizedClientManager(
- clientRegistrationRepository, authorizedClientRepository, authorizationFailureHandler);
+ DefaultOAuth2AuthorizedClientManager defaultAuthorizedClientManager =
+ new DefaultOAuth2AuthorizedClientManager(
+ clientRegistrationRepository, authorizedClientRepository);
+ defaultAuthorizedClientManager.setAuthorizationFailureHandler(authorizationFailureHandler);
+ this.authorizedClientManager = defaultAuthorizedClientManager;
this.defaultAuthorizedClientManager = true;
this.clientResponseHandler = new AuthorizationFailureForwarder(authorizationFailureHandler);
}
- private static OAuth2AuthorizedClientManager createDefaultAuthorizedClientManager(
- ClientRegistrationRepository clientRegistrationRepository,
- OAuth2AuthorizedClientRepository authorizedClientRepository,
- OAuth2AuthorizationFailureHandler authorizationFailureHandler) {
-
- OAuth2AuthorizedClientProvider authorizedClientProvider =
- OAuth2AuthorizedClientProviderBuilder.builder()
- .authorizationCode()
- .refreshToken()
- .clientCredentials()
- .password()
- .build();
- DefaultOAuth2AuthorizedClientManager authorizedClientManager = new DefaultOAuth2AuthorizedClientManager(
- clientRegistrationRepository, authorizedClientRepository);
- authorizedClientManager.setAuthorizedClientProvider(authorizedClientProvider);
- authorizedClientManager.setAuthorizationFailureHandler(authorizationFailureHandler);
-
- return authorizedClientManager;
- }
-
/**
* Sets the {@link OAuth2AccessTokenResponseClient} used for getting an {@link OAuth2AuthorizedClient} for the client_credentials grant.
*
diff --git a/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/web/reactive/result/method/annotation/OAuth2AuthorizedClientArgumentResolver.java b/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/web/reactive/result/method/annotation/OAuth2AuthorizedClientArgumentResolver.java
index 5f784a51c1b..798fafc30cd 100644
--- a/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/web/reactive/result/method/annotation/OAuth2AuthorizedClientArgumentResolver.java
+++ b/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/web/reactive/result/method/annotation/OAuth2AuthorizedClientArgumentResolver.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2019 the original author or authors.
+ * Copyright 2002-2020 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.
@@ -25,8 +25,6 @@
import org.springframework.security.core.context.SecurityContext;
import org.springframework.security.oauth2.client.OAuth2AuthorizeRequest;
import org.springframework.security.oauth2.client.OAuth2AuthorizedClient;
-import org.springframework.security.oauth2.client.ReactiveOAuth2AuthorizedClientProvider;
-import org.springframework.security.oauth2.client.ReactiveOAuth2AuthorizedClientProviderBuilder;
import org.springframework.security.oauth2.client.ReactiveOAuth2AuthorizedClientManager;
import org.springframework.security.oauth2.client.annotation.RegisteredOAuth2AuthorizedClient;
import org.springframework.security.oauth2.client.authentication.OAuth2AuthenticationToken;
@@ -87,24 +85,8 @@ public OAuth2AuthorizedClientArgumentResolver(ReactiveClientRegistrationReposito
ServerOAuth2AuthorizedClientRepository authorizedClientRepository) {
Assert.notNull(clientRegistrationRepository, "clientRegistrationRepository cannot be null");
Assert.notNull(authorizedClientRepository, "authorizedClientRepository cannot be null");
- this.authorizedClientManager = createDefaultAuthorizedClientManager(clientRegistrationRepository, authorizedClientRepository);
- }
-
- private static ReactiveOAuth2AuthorizedClientManager createDefaultAuthorizedClientManager(
- ReactiveClientRegistrationRepository clientRegistrationRepository, ServerOAuth2AuthorizedClientRepository authorizedClientRepository) {
-
- ReactiveOAuth2AuthorizedClientProvider authorizedClientProvider =
- ReactiveOAuth2AuthorizedClientProviderBuilder.builder()
- .authorizationCode()
- .refreshToken()
- .clientCredentials()
- .password()
- .build();
- DefaultReactiveOAuth2AuthorizedClientManager authorizedClientManager = new DefaultReactiveOAuth2AuthorizedClientManager(
+ this.authorizedClientManager = new DefaultReactiveOAuth2AuthorizedClientManager(
clientRegistrationRepository, authorizedClientRepository);
- authorizedClientManager.setAuthorizedClientProvider(authorizedClientProvider);
-
- return authorizedClientManager;
}
@Override
diff --git a/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/web/server/OAuth2AuthorizationCodeGrantWebFilter.java b/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/web/server/OAuth2AuthorizationCodeGrantWebFilter.java
index 7ef55667cbb..a8d10bff0e1 100644
--- a/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/web/server/OAuth2AuthorizationCodeGrantWebFilter.java
+++ b/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/web/server/OAuth2AuthorizationCodeGrantWebFilter.java
@@ -27,6 +27,8 @@
import org.springframework.security.oauth2.client.authentication.OAuth2AuthorizationCodeAuthenticationToken;
import org.springframework.security.oauth2.client.registration.ReactiveClientRegistrationRepository;
import org.springframework.security.oauth2.client.web.AuthorizationRequestRepository;
+import org.springframework.security.oauth2.core.OAuth2AuthenticationException;
+import org.springframework.security.oauth2.core.OAuth2AuthorizationException;
import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationRequest;
import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationResponse;
import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames;
@@ -35,6 +37,8 @@
import org.springframework.security.web.server.authentication.ServerAuthenticationConverter;
import org.springframework.security.web.server.authentication.ServerAuthenticationFailureHandler;
import org.springframework.security.web.server.authentication.ServerAuthenticationSuccessHandler;
+import org.springframework.security.web.server.savedrequest.ServerRequestCache;
+import org.springframework.security.web.server.savedrequest.WebSessionServerRequestCache;
import org.springframework.security.web.server.util.matcher.ServerWebExchangeMatcher;
import org.springframework.util.Assert;
import org.springframework.web.server.ServerWebExchange;
@@ -80,6 +84,7 @@
*
* @author Rob Winch
* @author Joe Grandja
+ * @author Parikshit Dutta
* @since 5.1
* @see OAuth2AuthorizationCodeAuthenticationToken
* @see org.springframework.security.oauth2.client.authentication.OAuth2AuthorizationCodeReactiveAuthenticationManager
@@ -111,6 +116,8 @@ public class OAuth2AuthorizationCodeGrantWebFilter implements WebFilter {
private ServerWebExchangeMatcher requiresAuthenticationMatcher;
+ private ServerRequestCache requestCache = new WebSessionServerRequestCache();
+
private AnonymousAuthenticationToken anonymousToken = new AnonymousAuthenticationToken("key", "anonymous",
AuthorityUtils.createAuthorityList("ROLE_ANONYMOUS"));
@@ -129,7 +136,10 @@ public OAuth2AuthorizationCodeGrantWebFilter(
authenticationConverter.setAuthorizationRequestRepository(this.authorizationRequestRepository);
this.authenticationConverter = authenticationConverter;
this.defaultAuthenticationConverter = true;
- this.authenticationSuccessHandler = new RedirectServerAuthenticationSuccessHandler();
+ RedirectServerAuthenticationSuccessHandler authenticationSuccessHandler =
+ new RedirectServerAuthenticationSuccessHandler();
+ authenticationSuccessHandler.setRequestCache(this.requestCache);
+ this.authenticationSuccessHandler = authenticationSuccessHandler;
this.authenticationFailureHandler = (webFilterExchange, exception) -> Mono.error(exception);
}
@@ -144,7 +154,10 @@ public OAuth2AuthorizationCodeGrantWebFilter(
this.authorizedClientRepository = authorizedClientRepository;
this.requiresAuthenticationMatcher = this::matchesAuthorizationResponse;
this.authenticationConverter = authenticationConverter;
- this.authenticationSuccessHandler = new RedirectServerAuthenticationSuccessHandler();
+ RedirectServerAuthenticationSuccessHandler authenticationSuccessHandler =
+ new RedirectServerAuthenticationSuccessHandler();
+ authenticationSuccessHandler.setRequestCache(this.requestCache);
+ this.authenticationSuccessHandler = authenticationSuccessHandler;
this.authenticationFailureHandler = (webFilterExchange, exception) -> Mono.error(exception);
}
@@ -169,19 +182,42 @@ private void updateDefaultAuthenticationConverter() {
}
}
+ /**
+ * Sets the {@link ServerRequestCache} used for loading a previously saved request (if available)
+ * and replaying it after completing the processing of the OAuth 2.0 Authorization Response.
+ *
+ * @since 5.4
+ * @param requestCache the cache used for loading a previously saved request (if available)
+ */
+ public final void setRequestCache(ServerRequestCache requestCache) {
+ Assert.notNull(requestCache, "requestCache cannot be null");
+ this.requestCache = requestCache;
+ updateDefaultAuthenticationSuccessHandler();
+ }
+
+ private void updateDefaultAuthenticationSuccessHandler() {
+ ((RedirectServerAuthenticationSuccessHandler) this.authenticationSuccessHandler).setRequestCache(this.requestCache);
+ }
+
@Override
public Mono filter(ServerWebExchange exchange, WebFilterChain chain) {
return this.requiresAuthenticationMatcher.matches(exchange)
.filter(ServerWebExchangeMatcher.MatchResult::isMatch)
- .flatMap(matchResult -> this.authenticationConverter.convert(exchange))
+ .flatMap(matchResult ->
+ this.authenticationConverter.convert(exchange)
+ .onErrorMap(OAuth2AuthorizationException.class, e -> new OAuth2AuthenticationException(
+ e.getError(), e.getError().toString())))
.switchIfEmpty(chain.filter(exchange).then(Mono.empty()))
- .flatMap(token -> authenticate(exchange, chain, token));
+ .flatMap(token -> authenticate(exchange, chain, token))
+ .onErrorResume(AuthenticationException.class, e -> this.authenticationFailureHandler
+ .onAuthenticationFailure(new WebFilterExchange(exchange, chain), e));
}
- private Mono authenticate(ServerWebExchange exchange,
- WebFilterChain chain, Authentication token) {
+ private Mono authenticate(ServerWebExchange exchange, WebFilterChain chain, Authentication token) {
WebFilterExchange webFilterExchange = new WebFilterExchange(exchange, chain);
return this.authenticationManager.authenticate(token)
+ .onErrorMap(OAuth2AuthorizationException.class, e -> new OAuth2AuthenticationException(
+ e.getError(), e.getError().toString()))
.switchIfEmpty(Mono.defer(() -> Mono.error(new IllegalStateException("No provider found for " + token.getClass()))))
.flatMap(authentication -> onAuthenticationSuccess(authentication, webFilterExchange))
.onErrorResume(AuthenticationException.class, e -> this.authenticationFailureHandler
diff --git a/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/web/server/ServerOAuth2AuthorizationCodeAuthenticationTokenConverter.java b/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/web/server/ServerOAuth2AuthorizationCodeAuthenticationTokenConverter.java
index 2e9bd68c173..6ff9d34fd1e 100644
--- a/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/web/server/ServerOAuth2AuthorizationCodeAuthenticationTokenConverter.java
+++ b/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/web/server/ServerOAuth2AuthorizationCodeAuthenticationTokenConverter.java
@@ -18,7 +18,6 @@
import org.springframework.security.core.Authentication;
import org.springframework.security.oauth2.client.authentication.OAuth2AuthorizationCodeAuthenticationToken;
-import org.springframework.security.oauth2.client.authentication.OAuth2LoginAuthenticationToken;
import org.springframework.security.oauth2.client.registration.ReactiveClientRegistrationRepository;
import org.springframework.security.oauth2.core.OAuth2AuthorizationException;
import org.springframework.security.oauth2.core.OAuth2Error;
@@ -33,7 +32,7 @@
import reactor.core.publisher.Mono;
/**
- * Converts from a {@link ServerWebExchange} to an {@link OAuth2LoginAuthenticationToken} that can be authenticated. The
+ * Converts from a {@link ServerWebExchange} to an {@link OAuth2AuthorizationCodeAuthenticationToken} that can be authenticated. The
* converter does not validate any errors it only performs a conversion.
* @author Rob Winch
* @since 5.1
diff --git a/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/JdbcOAuth2AuthorizedClientServiceTests.java b/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/JdbcOAuth2AuthorizedClientServiceTests.java
index 40b2957e2a8..78fe9c30fb4 100644
--- a/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/JdbcOAuth2AuthorizedClientServiceTests.java
+++ b/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/JdbcOAuth2AuthorizedClientServiceTests.java
@@ -19,7 +19,6 @@
import org.junit.Before;
import org.junit.Test;
import org.springframework.dao.DataRetrievalFailureException;
-import org.springframework.dao.DuplicateKeyException;
import org.springframework.jdbc.core.ArgumentPreparedStatementSetter;
import org.springframework.jdbc.core.JdbcOperations;
import org.springframework.jdbc.core.JdbcTemplate;
@@ -47,12 +46,14 @@
import java.sql.Timestamp;
import java.sql.Types;
import java.time.Instant;
+import java.time.temporal.ChronoUnit;
import java.util.Collections;
import java.util.List;
import java.util.Set;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
+import static org.assertj.core.api.Assertions.within;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.Mockito.mock;
@@ -64,6 +65,7 @@
* Tests for {@link JdbcOAuth2AuthorizedClientService}.
*
* @author Joe Grandja
+ * @author Stav Shamir
*/
public class JdbcOAuth2AuthorizedClientServiceTests {
private static final String OAUTH2_CLIENT_SCHEMA_SQL_RESOURCE = "org/springframework/security/oauth2/client/oauth2-client-schema.sql";
@@ -154,11 +156,11 @@ public void loadAuthorizedClientWhenExistsThenReturnAuthorizedClient() {
assertThat(authorizedClient.getPrincipalName()).isEqualTo(expected.getPrincipalName());
assertThat(authorizedClient.getAccessToken().getTokenType()).isEqualTo(expected.getAccessToken().getTokenType());
assertThat(authorizedClient.getAccessToken().getTokenValue()).isEqualTo(expected.getAccessToken().getTokenValue());
- assertThat(authorizedClient.getAccessToken().getIssuedAt()).isEqualTo(expected.getAccessToken().getIssuedAt());
- assertThat(authorizedClient.getAccessToken().getExpiresAt()).isEqualTo(expected.getAccessToken().getExpiresAt());
+ assertThat(authorizedClient.getAccessToken().getIssuedAt()).isCloseTo(expected.getAccessToken().getIssuedAt(), within(1, ChronoUnit.MILLIS));
+ assertThat(authorizedClient.getAccessToken().getExpiresAt()).isCloseTo(expected.getAccessToken().getExpiresAt(), within(1, ChronoUnit.MILLIS));
assertThat(authorizedClient.getAccessToken().getScopes()).isEqualTo(expected.getAccessToken().getScopes());
assertThat(authorizedClient.getRefreshToken().getTokenValue()).isEqualTo(expected.getRefreshToken().getTokenValue());
- assertThat(authorizedClient.getRefreshToken().getIssuedAt()).isEqualTo(expected.getRefreshToken().getIssuedAt());
+ assertThat(authorizedClient.getRefreshToken().getIssuedAt()).isCloseTo(expected.getRefreshToken().getIssuedAt(), within(1, ChronoUnit.MILLIS));
}
@Test
@@ -209,11 +211,11 @@ public void saveAuthorizedClientWhenSaveThenLoadReturnsSaved() {
assertThat(authorizedClient.getPrincipalName()).isEqualTo(expected.getPrincipalName());
assertThat(authorizedClient.getAccessToken().getTokenType()).isEqualTo(expected.getAccessToken().getTokenType());
assertThat(authorizedClient.getAccessToken().getTokenValue()).isEqualTo(expected.getAccessToken().getTokenValue());
- assertThat(authorizedClient.getAccessToken().getIssuedAt()).isEqualTo(expected.getAccessToken().getIssuedAt());
- assertThat(authorizedClient.getAccessToken().getExpiresAt()).isEqualTo(expected.getAccessToken().getExpiresAt());
+ assertThat(authorizedClient.getAccessToken().getIssuedAt()).isCloseTo(expected.getAccessToken().getIssuedAt(), within(1, ChronoUnit.MILLIS));
+ assertThat(authorizedClient.getAccessToken().getExpiresAt()).isCloseTo(expected.getAccessToken().getExpiresAt(), within(1, ChronoUnit.MILLIS));
assertThat(authorizedClient.getAccessToken().getScopes()).isEqualTo(expected.getAccessToken().getScopes());
assertThat(authorizedClient.getRefreshToken().getTokenValue()).isEqualTo(expected.getRefreshToken().getTokenValue());
- assertThat(authorizedClient.getRefreshToken().getIssuedAt()).isEqualTo(expected.getRefreshToken().getIssuedAt());
+ assertThat(authorizedClient.getRefreshToken().getIssuedAt()).isCloseTo(expected.getRefreshToken().getIssuedAt(), within(1, ChronoUnit.MILLIS));
// Test save/load of NOT NULL attributes only
principal = createPrincipal();
@@ -229,21 +231,37 @@ public void saveAuthorizedClientWhenSaveThenLoadReturnsSaved() {
assertThat(authorizedClient.getPrincipalName()).isEqualTo(expected.getPrincipalName());
assertThat(authorizedClient.getAccessToken().getTokenType()).isEqualTo(expected.getAccessToken().getTokenType());
assertThat(authorizedClient.getAccessToken().getTokenValue()).isEqualTo(expected.getAccessToken().getTokenValue());
- assertThat(authorizedClient.getAccessToken().getIssuedAt()).isEqualTo(expected.getAccessToken().getIssuedAt());
- assertThat(authorizedClient.getAccessToken().getExpiresAt()).isEqualTo(expected.getAccessToken().getExpiresAt());
+ assertThat(authorizedClient.getAccessToken().getIssuedAt()).isCloseTo(expected.getAccessToken().getIssuedAt(), within(1, ChronoUnit.MILLIS));
+ assertThat(authorizedClient.getAccessToken().getExpiresAt()).isCloseTo(expected.getAccessToken().getExpiresAt(), within(1, ChronoUnit.MILLIS));
assertThat(authorizedClient.getAccessToken().getScopes()).isEmpty();
assertThat(authorizedClient.getRefreshToken()).isNull();
}
@Test
- public void saveAuthorizedClientWhenSaveDuplicateThenThrowDuplicateKeyException() {
+ public void saveAuthorizedClientWhenSaveClientWithExistingPrimaryKeyThenUpdate() {
+ // Given a saved authorized client
Authentication principal = createPrincipal();
OAuth2AuthorizedClient authorizedClient = createAuthorizedClient(principal, this.clientRegistration);
-
this.authorizedClientService.saveAuthorizedClient(authorizedClient, principal);
- assertThatThrownBy(() -> this.authorizedClientService.saveAuthorizedClient(authorizedClient, principal))
- .isInstanceOf(DuplicateKeyException.class);
+ // When a client with the same principal and registration id is saved
+ OAuth2AuthorizedClient updatedClient = createAuthorizedClient(principal, this.clientRegistration);
+ this.authorizedClientService.saveAuthorizedClient(updatedClient, principal);
+
+ // Then the saved client is updated
+ OAuth2AuthorizedClient savedClient = this.authorizedClientService.loadAuthorizedClient(
+ this.clientRegistration.getRegistrationId(), principal.getName());
+
+ assertThat(savedClient).isNotNull();
+ assertThat(savedClient.getClientRegistration()).isEqualTo(updatedClient.getClientRegistration());
+ assertThat(savedClient.getPrincipalName()).isEqualTo(updatedClient.getPrincipalName());
+ assertThat(savedClient.getAccessToken().getTokenType()).isEqualTo(updatedClient.getAccessToken().getTokenType());
+ assertThat(savedClient.getAccessToken().getTokenValue()).isEqualTo(updatedClient.getAccessToken().getTokenValue());
+ assertThat(savedClient.getAccessToken().getIssuedAt()).isCloseTo(updatedClient.getAccessToken().getIssuedAt(), within(1, ChronoUnit.MILLIS));
+ assertThat(savedClient.getAccessToken().getExpiresAt()).isCloseTo(updatedClient.getAccessToken().getExpiresAt(), within(1, ChronoUnit.MILLIS));
+ assertThat(savedClient.getAccessToken().getScopes()).isEqualTo(updatedClient.getAccessToken().getScopes());
+ assertThat(savedClient.getRefreshToken().getTokenValue()).isEqualTo(updatedClient.getRefreshToken().getTokenValue());
+ assertThat(savedClient.getRefreshToken().getIssuedAt()).isCloseTo(updatedClient.getRefreshToken().getIssuedAt(), within(1, ChronoUnit.MILLIS));
}
@Test
diff --git a/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/authentication/OAuth2AuthorizationCodeAuthenticationProviderTests.java b/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/authentication/OAuth2AuthorizationCodeAuthenticationProviderTests.java
index 2ba5e369277..41ebe4a1e6e 100644
--- a/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/authentication/OAuth2AuthorizationCodeAuthenticationProviderTests.java
+++ b/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/authentication/OAuth2AuthorizationCodeAuthenticationProviderTests.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2019 the original author or authors.
+ * Copyright 2002-2020 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.
@@ -16,6 +16,8 @@
package org.springframework.security.oauth2.client.authentication;
import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
import org.junit.Before;
import org.junit.Test;
@@ -119,4 +121,26 @@ public void authenticateWhenAuthorizationSuccessResponseThenExchangedForAccessTo
assertThat(authenticationResult.getAccessToken()).isEqualTo(accessTokenResponse.getAccessToken());
assertThat(authenticationResult.getRefreshToken()).isEqualTo(accessTokenResponse.getRefreshToken());
}
+
+ // gh-5368
+ @Test
+ public void authenticateWhenAuthorizationSuccessResponseThenAdditionalParametersIncluded() {
+ Map additionalParameters = new HashMap<>();
+ additionalParameters.put("param1", "value1");
+ additionalParameters.put("param2", "value2");
+
+ OAuth2AccessTokenResponse accessTokenResponse = accessTokenResponse().additionalParameters(additionalParameters)
+ .build();
+ when(this.accessTokenResponseClient.getTokenResponse(any())).thenReturn(accessTokenResponse);
+
+ OAuth2AuthorizationExchange authorizationExchange = new OAuth2AuthorizationExchange(this.authorizationRequest,
+ success().build());
+
+ OAuth2AuthorizationCodeAuthenticationToken authentication = (OAuth2AuthorizationCodeAuthenticationToken) this.authenticationProvider
+ .authenticate(
+ new OAuth2AuthorizationCodeAuthenticationToken(this.clientRegistration, authorizationExchange));
+
+ assertThat(authentication.getAdditionalParameters())
+ .containsAllEntriesOf(accessTokenResponse.getAdditionalParameters());
+ }
}
diff --git a/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/authentication/OAuth2LoginReactiveAuthenticationManagerTests.java b/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/authentication/OAuth2LoginReactiveAuthenticationManagerTests.java
index ee636c3f794..197bf53d867 100644
--- a/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/authentication/OAuth2LoginReactiveAuthenticationManagerTests.java
+++ b/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/authentication/OAuth2LoginReactiveAuthenticationManagerTests.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2018 the original author or authors.
+ * Copyright 2002-2020 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.
@@ -20,10 +20,13 @@
import static org.assertj.core.api.Assertions.assertThatCode;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyCollection;
+import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
import java.util.Collections;
import java.util.HashMap;
+import java.util.List;
import java.util.Map;
import org.junit.Before;
@@ -33,8 +36,11 @@
import org.mockito.ArgumentCaptor;
import org.mockito.Mock;
import org.mockito.junit.MockitoJUnitRunner;
+import org.mockito.stubbing.Answer;
import org.springframework.security.authentication.TestingAuthenticationToken;
+import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.AuthorityUtils;
+import org.springframework.security.core.authority.mapping.GrantedAuthoritiesMapper;
import org.springframework.security.oauth2.client.ReactiveOAuth2AuthorizedClientService;
import org.springframework.security.oauth2.client.endpoint.OAuth2AuthorizationCodeGrantRequest;
import org.springframework.security.oauth2.client.endpoint.ReactiveOAuth2AccessTokenResponseClient;
@@ -96,6 +102,12 @@ public void constructorWhenNullUserServiceThenIllegalArgumentException() {
.isInstanceOf(IllegalArgumentException.class);
}
+ @Test
+ public void setAuthoritiesMapperWhenAuthoritiesMapperIsNullThenThrowIllegalArgumentException() {
+ assertThatThrownBy(() -> this.manager.setAuthoritiesMapper(null))
+ .isInstanceOf(IllegalArgumentException.class);
+ }
+
@Test
public void authenticateWhenNoSubscriptionThenDoesNothing() {
// we didn't do anything because it should cause a ClassCastException (as verified below)
@@ -178,6 +190,24 @@ public void authenticateWhenTokenSuccessResponseThenAdditionalParametersAddedToU
.containsAllEntriesOf(accessTokenResponse.getAdditionalParameters());
}
+ @Test
+ public void authenticateWhenAuthoritiesMapperSetThenReturnMappedAuthorities() {
+ OAuth2AccessTokenResponse accessTokenResponse = OAuth2AccessTokenResponse.withToken("foo")
+ .tokenType(OAuth2AccessToken.TokenType.BEARER)
+ .build();
+ when(this.accessTokenResponseClient.getTokenResponse(any())).thenReturn(Mono.just(accessTokenResponse));
+ DefaultOAuth2User user = new DefaultOAuth2User(AuthorityUtils.createAuthorityList("ROLE_USER"), Collections.singletonMap("user", "rob"), "user");
+ when(this.userService.loadUser(any())).thenReturn(Mono.just(user));
+ List mappedAuthorities = AuthorityUtils.createAuthorityList("ROLE_OAUTH_USER");
+ GrantedAuthoritiesMapper authoritiesMapper = mock(GrantedAuthoritiesMapper.class);
+ when(authoritiesMapper.mapAuthorities(anyCollection())).thenAnswer((Answer>) invocation -> mappedAuthorities);
+ manager.setAuthoritiesMapper(authoritiesMapper);
+
+ OAuth2LoginAuthenticationToken result = (OAuth2LoginAuthenticationToken) this.manager.authenticate(loginToken()).block();
+
+ assertThat(result.getAuthorities()).isEqualTo(mappedAuthorities);
+ }
+
private OAuth2AuthorizationCodeAuthenticationToken loginToken() {
ClientRegistration clientRegistration = this.registration.build();
OAuth2AuthorizationRequest authorizationRequest = OAuth2AuthorizationRequest
diff --git a/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/jackson2/OAuth2AuthorizationRequestMixinTests.java b/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/jackson2/OAuth2AuthorizationRequestMixinTests.java
index 21ca65fb874..2630efeabc3 100644
--- a/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/jackson2/OAuth2AuthorizationRequestMixinTests.java
+++ b/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/jackson2/OAuth2AuthorizationRequestMixinTests.java
@@ -27,7 +27,6 @@
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;
-import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.stream.Collectors;
@@ -71,8 +70,8 @@ public void serializeWhenRequiredAttributesOnlyThenSerializes() throws Exception
this.authorizationRequestBuilder
.scopes(null)
.state(null)
- .additionalParameters(Collections.emptyMap())
- .attributes(Collections.emptyMap())
+ .additionalParameters(Map::clear)
+ .attributes(Map::clear)
.build();
String expectedJson = asJson(authorizationRequest);
String json = this.mapper.writeValueAsString(authorizationRequest);
@@ -119,8 +118,8 @@ public void deserializeWhenRequiredAttributesOnlyThenDeserializes() throws Excep
this.authorizationRequestBuilder
.scopes(null)
.state(null)
- .additionalParameters(Collections.emptyMap())
- .attributes(Collections.emptyMap())
+ .additionalParameters(Map::clear)
+ .attributes(Map::clear)
.build();
String json = asJson(expectedAuthorizationRequest);
OAuth2AuthorizationRequest authorizationRequest = this.mapper.readValue(json, OAuth2AuthorizationRequest.class);
diff --git a/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/jackson2/OAuth2AuthorizedClientMixinTests.java b/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/jackson2/OAuth2AuthorizedClientMixinTests.java
index 93dfd2dd083..d17675faad7 100644
--- a/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/jackson2/OAuth2AuthorizedClientMixinTests.java
+++ b/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/jackson2/OAuth2AuthorizedClientMixinTests.java
@@ -86,6 +86,7 @@ public void serializeWhenRequiredAttributesOnlyThenSerializes() throws Exception
.userInfoUri(null)
.userNameAttributeName(null)
.jwkSetUri(null)
+ .issuerUri(null)
.build();
OAuth2AuthorizedClient authorizedClient = new OAuth2AuthorizedClient(
clientRegistration, this.principalName, TestOAuth2AccessTokens.noScopes());
@@ -139,6 +140,8 @@ public void deserializeWhenMixinRegisteredThenDeserializes() throws Exception {
.isEqualTo(expectedClientRegistration.getProviderDetails().getUserInfoEndpoint().getUserNameAttributeName());
assertThat(clientRegistration.getProviderDetails().getJwkSetUri())
.isEqualTo(expectedClientRegistration.getProviderDetails().getJwkSetUri());
+ assertThat(clientRegistration.getProviderDetails().getIssuerUri())
+ .isEqualTo(expectedClientRegistration.getProviderDetails().getIssuerUri());
assertThat(clientRegistration.getProviderDetails().getConfigurationMetadata())
.containsExactlyEntriesOf(clientRegistration.getProviderDetails().getConfigurationMetadata());
assertThat(clientRegistration.getClientName())
@@ -174,6 +177,7 @@ public void deserializeWhenRequiredAttributesOnlyThenDeserializes() throws Excep
.userInfoUri(null)
.userNameAttributeName(null)
.jwkSetUri(null)
+ .issuerUri(null)
.build();
OAuth2AccessToken expectedAccessToken = TestOAuth2AccessTokens.noScopes();
OAuth2AuthorizedClient expectedAuthorizedClient = new OAuth2AuthorizedClient(
@@ -203,6 +207,7 @@ public void deserializeWhenRequiredAttributesOnlyThenDeserializes() throws Excep
.isEqualTo(expectedClientRegistration.getProviderDetails().getUserInfoEndpoint().getAuthenticationMethod());
assertThat(clientRegistration.getProviderDetails().getUserInfoEndpoint().getUserNameAttributeName()).isNull();
assertThat(clientRegistration.getProviderDetails().getJwkSetUri()).isNull();
+ assertThat(clientRegistration.getProviderDetails().getIssuerUri()).isNull();
assertThat(clientRegistration.getProviderDetails().getConfigurationMetadata()).isEmpty();
assertThat(clientRegistration.getClientName())
.isEqualTo(clientRegistration.getRegistrationId());
@@ -276,6 +281,7 @@ private static String asJson(ClientRegistration clientRegistration) {
" \"userNameAttributeName\": " + (userInfoEndpoint.getUserNameAttributeName() != null ? "\"" + userInfoEndpoint.getUserNameAttributeName() + "\"" : null) + "\n" +
" },\n" +
" \"jwkSetUri\": " + (providerDetails.getJwkSetUri() != null ? "\"" + providerDetails.getJwkSetUri() + "\"" : null) + ",\n" +
+ " \"issuerUri\": " + (providerDetails.getIssuerUri() != null ? "\"" + providerDetails.getIssuerUri() + "\"" : null) + ",\n" +
" \"configurationMetadata\": {\n" +
" " + configurationMetadata + "\n" +
" }\n" +
diff --git a/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/oidc/authentication/OidcAuthorizationCodeReactiveAuthenticationManagerTests.java b/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/oidc/authentication/OidcAuthorizationCodeReactiveAuthenticationManagerTests.java
index 5e19ab85695..fff93cdc286 100644
--- a/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/oidc/authentication/OidcAuthorizationCodeReactiveAuthenticationManagerTests.java
+++ b/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/oidc/authentication/OidcAuthorizationCodeReactiveAuthenticationManagerTests.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2019 the original author or authors.
+ * Copyright 2002-2020 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.
@@ -21,6 +21,7 @@
import java.util.Base64;
import java.util.Collections;
import java.util.HashMap;
+import java.util.List;
import java.util.Map;
import org.junit.Before;
@@ -29,6 +30,10 @@
import org.mockito.ArgumentCaptor;
import org.mockito.Mock;
import org.mockito.junit.MockitoJUnitRunner;
+import org.mockito.stubbing.Answer;
+import org.springframework.security.core.Authentication;
+import org.springframework.security.core.GrantedAuthority;
+import org.springframework.security.core.authority.mapping.GrantedAuthoritiesMapper;
import reactor.core.publisher.Mono;
import org.springframework.security.authentication.TestingAuthenticationToken;
@@ -63,6 +68,8 @@
import static org.assertj.core.api.Assertions.assertThatCode;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyCollection;
+import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
import static org.springframework.security.oauth2.client.oidc.authentication.OidcAuthorizationCodeReactiveAuthenticationManager.createHash;
import static org.springframework.security.oauth2.jwt.TestJwts.jwt;
@@ -123,6 +130,12 @@ public void setJwtDecoderFactoryWhenNullThenIllegalArgumentException() {
.isInstanceOf(IllegalArgumentException.class);
}
+ @Test
+ public void setAuthoritiesMapperWhenAuthoritiesMapperIsNullThenThrowIllegalArgumentException() {
+ assertThatThrownBy(() -> this.manager.setAuthoritiesMapper(null))
+ .isInstanceOf(IllegalArgumentException.class);
+ }
+
@Test
public void authenticateWhenNoSubscriptionThenDoesNothing() {
// we didn't do anything because it should cause a ClassCastException (as verified below)
@@ -316,6 +329,42 @@ public void authenticateWhenTokenSuccessResponseThenAdditionalParametersAddedToU
.containsAllEntriesOf(accessTokenResponse.getAdditionalParameters());
}
+ @Test
+ public void authenticateWhenAuthoritiesMapperSetThenReturnMappedAuthorities() {
+ ClientRegistration clientRegistration = this.registration.build();
+ OAuth2AccessTokenResponse accessTokenResponse = OAuth2AccessTokenResponse.withToken("foo")
+ .tokenType(OAuth2AccessToken.TokenType.BEARER)
+ .additionalParameters(Collections.singletonMap(OidcParameterNames.ID_TOKEN, this.idToken.getTokenValue()))
+ .build();
+
+ OAuth2AuthorizationCodeAuthenticationToken authorizationCodeAuthentication = loginToken();
+
+ Map claims = new HashMap<>();
+ claims.put(IdTokenClaimNames.ISS, "https://issuer.example.com");
+ claims.put(IdTokenClaimNames.SUB, "rob");
+ claims.put(IdTokenClaimNames.AUD, Collections.singletonList(clientRegistration.getClientId()));
+ claims.put(IdTokenClaimNames.NONCE, this.nonceHash);
+ Jwt idToken = jwt().claims(c -> c.putAll(claims)).build();
+
+
+ when(this.accessTokenResponseClient.getTokenResponse(any())).thenReturn(Mono.just(accessTokenResponse));
+ DefaultOidcUser user = new DefaultOidcUser(AuthorityUtils.createAuthorityList("ROLE_USER"), this.idToken);
+ ArgumentCaptor userRequestArgCaptor = ArgumentCaptor.forClass(OidcUserRequest.class);
+ when(this.userService.loadUser(userRequestArgCaptor.capture())).thenReturn(Mono.just(user));
+
+ List mappedAuthorities = AuthorityUtils.createAuthorityList("ROLE_OIDC_USER");
+ GrantedAuthoritiesMapper authoritiesMapper = mock(GrantedAuthoritiesMapper.class);
+ when(authoritiesMapper.mapAuthorities(anyCollection())).thenAnswer(
+ (Answer>) invocation -> mappedAuthorities);
+ when(this.jwtDecoder.decode(any())).thenReturn(Mono.just(idToken));
+ this.manager.setJwtDecoderFactory(c -> this.jwtDecoder);
+ this.manager.setAuthoritiesMapper(authoritiesMapper);
+
+ Authentication result = this.manager.authenticate(authorizationCodeAuthentication).block();
+
+ assertThat(result.getAuthorities()).isEqualTo(mappedAuthorities);
+ }
+
private OAuth2AuthorizationCodeAuthenticationToken loginToken() {
ClientRegistration clientRegistration = this.registration.build();
Map attributes = new HashMap<>();
diff --git a/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/oidc/authentication/OidcIdTokenValidatorTests.java b/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/oidc/authentication/OidcIdTokenValidatorTests.java
index f357cc38c85..098fe8d1f29 100644
--- a/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/oidc/authentication/OidcIdTokenValidatorTests.java
+++ b/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/oidc/authentication/OidcIdTokenValidatorTests.java
@@ -51,7 +51,7 @@ public class OidcIdTokenValidatorTests {
@Before
public void setup() {
this.headers.put("alg", JwsAlgorithms.RS256);
- this.claims.put(IdTokenClaimNames.ISS, "https://issuer.example.com");
+ this.claims.put(IdTokenClaimNames.ISS, "https://example.com");
this.claims.put(IdTokenClaimNames.SUB, "rob");
this.claims.put(IdTokenClaimNames.AUD, Collections.singletonList("client-id"));
}
@@ -92,6 +92,31 @@ public void validateWhenIssuerNullThenHasErrors() {
.allMatch(msg -> msg.contains(IdTokenClaimNames.ISS));
}
+ @Test
+ public void validateWhenMetadataIssuerMismatchThenHasErrors() {
+ /*
+ * When the issuer is set in the provider metadata, and it does not match the issuer in the ID Token,
+ * the validation must fail
+ */
+ this.registration = this.registration.issuerUri("https://somethingelse.com");
+
+ assertThat(this.validateIdToken())
+ .hasSize(1)
+ .extracting(OAuth2Error::getDescription)
+ .allMatch(msg -> msg.contains(IdTokenClaimNames.ISS));
+ }
+
+ @Test
+ public void validateWhenMetadataIssuerMatchThenNoErrors() {
+ /*
+ * When the issuer is set in the provider metadata, and it does match the issuer in the ID Token,
+ * the validation must succeed
+ */
+ this.registration = this.registration.issuerUri("https://example.com");
+
+ assertThat(this.validateIdToken()).isEmpty();
+ }
+
@Test
public void validateWhenSubNullThenHasErrors() {
this.claims.remove(IdTokenClaimNames.SUB);
diff --git a/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/registration/ClientRegistrationTests.java b/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/registration/ClientRegistrationTests.java
index 2a2adb07fef..1547706f2a8 100644
--- a/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/registration/ClientRegistrationTests.java
+++ b/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/registration/ClientRegistrationTests.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2019 the original author or authors.
+ * Copyright 2002-2020 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.
@@ -48,6 +48,7 @@ public class ClientRegistrationTests {
private static final String AUTHORIZATION_URI = "https://provider.com/oauth2/authorization";
private static final String TOKEN_URI = "https://provider.com/oauth2/token";
private static final String JWK_SET_URI = "https://provider.com/oauth2/keys";
+ private static final String ISSUER_URI = "https://provider.com";
private static final String CLIENT_NAME = "Client 1";
private static final Map PROVIDER_CONFIGURATION_METADATA =
Collections.unmodifiableMap(createProviderConfigurationMetadata());
@@ -89,6 +90,7 @@ public void buildWhenAuthorizationCodeGrantAllAttributesProvidedThenAllAttribute
.tokenUri(TOKEN_URI)
.userInfoAuthenticationMethod(AuthenticationMethod.FORM)
.jwkSetUri(JWK_SET_URI)
+ .issuerUri(ISSUER_URI)
.providerConfigurationMetadata(PROVIDER_CONFIGURATION_METADATA)
.clientName(CLIENT_NAME)
.build();
@@ -104,6 +106,7 @@ public void buildWhenAuthorizationCodeGrantAllAttributesProvidedThenAllAttribute
assertThat(registration.getProviderDetails().getTokenUri()).isEqualTo(TOKEN_URI);
assertThat(registration.getProviderDetails().getUserInfoEndpoint().getAuthenticationMethod()).isEqualTo(AuthenticationMethod.FORM);
assertThat(registration.getProviderDetails().getJwkSetUri()).isEqualTo(JWK_SET_URI);
+ assertThat(registration.getProviderDetails().getIssuerUri()).isEqualTo(ISSUER_URI);
assertThat(registration.getProviderDetails().getConfigurationMetadata()).isEqualTo(PROVIDER_CONFIGURATION_METADATA);
assertThat(registration.getClientName()).isEqualTo(CLIENT_NAME);
}
@@ -743,6 +746,7 @@ public void buildWhenClientRegistrationProvidedThenEachPropertyMatches() {
.isEqualTo(updatedUserInfoEndpoint.getUserNameAttributeName());
assertThat(providerDetails.getJwkSetUri()).isEqualTo(updatedProviderDetails.getJwkSetUri());
+ assertThat(providerDetails.getIssuerUri()).isEqualTo(updatedProviderDetails.getIssuerUri());
assertThat(providerDetails.getConfigurationMetadata())
.isEqualTo(updatedProviderDetails.getConfigurationMetadata());
diff --git a/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/registration/ClientRegistrationsTest.java b/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/registration/ClientRegistrationsTest.java
index 3897870e702..03677717b18 100644
--- a/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/registration/ClientRegistrationsTest.java
+++ b/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/registration/ClientRegistrationsTest.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2019 the original author or authors.
+ * Copyright 2002-2020 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.
@@ -162,6 +162,7 @@ private void assertIssuerMetadata(ClientRegistration registration,
assertThat(provider.getAuthorizationUri()).isEqualTo("https://example.com/o/oauth2/v2/auth");
assertThat(provider.getTokenUri()).isEqualTo("https://example.com/oauth2/v4/token");
assertThat(provider.getJwkSetUri()).isEqualTo("https://example.com/oauth2/v3/certs");
+ assertThat(provider.getIssuerUri()).isEqualTo(this.issuer);
assertThat(provider.getConfigurationMetadata()).containsKeys("authorization_endpoint", "claims_supported",
"code_challenge_methods_supported", "id_token_signing_alg_values_supported", "issuer", "jwks_uri",
"response_types_supported", "revocation_endpoint", "scopes_supported", "subject_types_supported",
@@ -195,6 +196,14 @@ public void issuerWhenOAuth2ResponseMissingJwksUriThenThenSuccess() throws Excep
assertThat(provider.getJwkSetUri()).isNull();
}
+ // gh-8187
+ @Test
+ public void issuerWhenResponseMissingUserInfoUriThenSuccess() throws Exception {
+ this.response.remove("userinfo_endpoint");
+ ClientRegistration registration = registration("").build();
+ assertThat(registration.getProviderDetails().getUserInfoEndpoint().getUri()).isNull();
+ }
+
@Test
public void issuerWhenContainsTrailingSlashThenSuccess() throws Exception {
assertThat(registration("")).isNotNull();
diff --git a/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/registration/TestClientRegistrations.java b/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/registration/TestClientRegistrations.java
index 7cf750e9df1..fe0391af818 100644
--- a/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/registration/TestClientRegistrations.java
+++ b/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/registration/TestClientRegistrations.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2018 the original author or authors.
+ * Copyright 2002-2020 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.
@@ -33,6 +33,7 @@ public static ClientRegistration.Builder clientRegistration() {
.authorizationUri("https://example.com/login/oauth/authorize")
.tokenUri("https://example.com/login/oauth/access_token")
.jwkSetUri("https://example.com/oauth2/jwk")
+ .issuerUri("https://example.com")
.userInfoUri("https://api.example.com/user")
.userNameAttributeName("id")
.clientName("Client Name")
diff --git a/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/web/DefaultOAuth2AuthorizationRequestResolverTests.java b/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/web/DefaultOAuth2AuthorizationRequestResolverTests.java
index 45cf3897ef8..36a11b94d90 100644
--- a/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/web/DefaultOAuth2AuthorizationRequestResolverTests.java
+++ b/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/web/DefaultOAuth2AuthorizationRequestResolverTests.java
@@ -15,8 +15,12 @@
*/
package org.springframework.security.oauth2.client.web;
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+import javax.servlet.http.HttpServletRequest;
import org.junit.Before;
import org.junit.Test;
+import org.mockito.Mockito;
import org.springframework.mock.web.MockHttpServletRequest;
import org.springframework.security.oauth2.client.registration.ClientRegistration;
import org.springframework.security.oauth2.client.registration.ClientRegistrationRepository;
@@ -99,6 +103,25 @@ public void resolveWhenNotAuthorizationRequestThenDoesNotResolve() {
assertThat(authorizationRequest).isNull();
}
+ // gh-8650
+ @Test
+ public void resolveWhenNotAuthorizationRequestThenRequestBodyNotConsumed() throws IOException {
+ String requestUri = "/path";
+ MockHttpServletRequest request = new MockHttpServletRequest("POST", requestUri);
+ request.setContent("foo".getBytes(StandardCharsets.UTF_8));
+ request.setCharacterEncoding(StandardCharsets.UTF_8.name());
+ HttpServletRequest spyRequest = Mockito.spy(request);
+
+ this.resolver.resolve(spyRequest);
+
+ Mockito.verify(spyRequest, Mockito.never()).getReader();
+ Mockito.verify(spyRequest, Mockito.never()).getInputStream();
+ Mockito.verify(spyRequest, Mockito.never()).getParameter(Mockito.anyString());
+ Mockito.verify(spyRequest, Mockito.never()).getParameterMap();
+ Mockito.verify(spyRequest, Mockito.never()).getParameterNames();
+ Mockito.verify(spyRequest, Mockito.never()).getParameterValues(Mockito.anyString());
+ }
+
@Test
public void resolveWhenAuthorizationRequestWithInvalidClientThenThrowIllegalArgumentException() {
ClientRegistration clientRegistration = this.registration1;
@@ -437,6 +460,7 @@ public void resolveWhenAuthorizationRequestCustomizerRemovesNonceThenQueryExclud
OAuth2AuthorizationRequest authorizationRequest = this.resolver.resolve(request);
assertThat(authorizationRequest.getAdditionalParameters()).doesNotContainKey(OidcParameterNames.NONCE);
assertThat(authorizationRequest.getAttributes()).doesNotContainKey(OidcParameterNames.NONCE);
+ assertThat(authorizationRequest.getAttributes()).containsKey(OAuth2ParameterNames.REGISTRATION_ID);
assertThat(authorizationRequest.getAuthorizationRequestUri())
.matches("https://example.com/login/oauth/authorize\\?" +
"response_type=code&client_id=client-id&" +
diff --git a/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/web/OAuth2AuthorizationCodeGrantFilterTests.java b/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/web/OAuth2AuthorizationCodeGrantFilterTests.java
index 39b3011f03b..d1dba0d8997 100644
--- a/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/web/OAuth2AuthorizationCodeGrantFilterTests.java
+++ b/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/web/OAuth2AuthorizationCodeGrantFilterTests.java
@@ -72,6 +72,7 @@
* Tests for {@link OAuth2AuthorizationCodeGrantFilter}.
*
* @author Joe Grandja
+ * @author Parikshit Dutta
*/
public class OAuth2AuthorizationCodeGrantFilterTests {
private ClientRegistration registration1;
@@ -130,6 +131,12 @@ public void setAuthorizationRequestRepositoryWhenAuthorizationRequestRepositoryI
.isInstanceOf(IllegalArgumentException.class);
}
+ @Test
+ public void setRequestCacheWhenRequestCacheIsNullThenThrowIllegalArgumentException() {
+ assertThatThrownBy(() -> this.filter.setRequestCache(null))
+ .isInstanceOf(IllegalArgumentException.class);
+ }
+
@Test
public void doFilterWhenNotAuthorizationResponseThenNotProcessed() throws Exception {
String requestUri = "/path";
@@ -326,6 +333,28 @@ public void doFilterWhenAuthorizationSucceedsAndHasSavedRequestThenRedirectToSav
assertThat(response.getRedirectedUrl()).isEqualTo("http://localhost/saved-request");
}
+ @Test
+ public void doFilterWhenAuthorizationSucceedsAndRequestCacheConfiguredThenRequestCacheUsed() throws Exception {
+ MockHttpServletRequest authorizationRequest = createAuthorizationRequest("/callback/client-1");
+ MockHttpServletRequest authorizationResponse = createAuthorizationResponse(authorizationRequest);
+ MockHttpServletResponse response = new MockHttpServletResponse();
+
+ FilterChain filterChain = mock(FilterChain.class);
+ this.setUpAuthorizationRequest(authorizationRequest, response, this.registration1);
+ this.setUpAuthenticationResult(this.registration1);
+
+ RequestCache requestCache = spy(HttpSessionRequestCache.class);
+ this.filter.setRequestCache(requestCache);
+
+ authorizationRequest.setRequestURI("/saved-request");
+ requestCache.saveRequest(authorizationRequest, response);
+
+ this.filter.doFilter(authorizationResponse, response, filterChain);
+
+ verify(requestCache).getRequest(any(HttpServletRequest.class), any(HttpServletResponse.class));
+ assertThat(response.getRedirectedUrl()).isEqualTo("http://localhost/saved-request");
+ }
+
@Test
public void doFilterWhenAuthorizationSucceedsAndAnonymousAccessThenAuthorizedClientSavedToHttpSession() throws Exception {
AnonymousAuthenticationToken anonymousPrincipal =
diff --git a/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/web/server/DefaultServerOAuth2AuthorizationRequestResolverTests.java b/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/web/server/DefaultServerOAuth2AuthorizationRequestResolverTests.java
index 77479bcf8e8..958799b0146 100644
--- a/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/web/server/DefaultServerOAuth2AuthorizationRequestResolverTests.java
+++ b/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/web/server/DefaultServerOAuth2AuthorizationRequestResolverTests.java
@@ -29,6 +29,7 @@
import org.springframework.security.oauth2.client.registration.TestClientRegistrations;
import org.springframework.security.oauth2.core.ClientAuthenticationMethod;
import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationRequest;
+import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames;
import org.springframework.security.oauth2.core.endpoint.PkceParameterNames;
import org.springframework.security.oauth2.core.oidc.OidcScopes;
import org.springframework.security.oauth2.core.oidc.endpoint.OidcParameterNames;
@@ -162,6 +163,7 @@ public void resolveWhenAuthorizationRequestCustomizerRemovesNonceThenQueryExclud
assertThat(authorizationRequest.getAdditionalParameters()).doesNotContainKey(OidcParameterNames.NONCE);
assertThat(authorizationRequest.getAttributes()).doesNotContainKey(OidcParameterNames.NONCE);
+ assertThat(authorizationRequest.getAttributes()).containsKey(OAuth2ParameterNames.REGISTRATION_ID);
assertThat(authorizationRequest.getAuthorizationRequestUri())
.matches("https://example.com/login/oauth/authorize\\?" +
"response_type=code&client_id=client-id&" +
diff --git a/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/web/server/OAuth2AuthorizationCodeGrantWebFilterTests.java b/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/web/server/OAuth2AuthorizationCodeGrantWebFilterTests.java
index 3c2a4bcc2d6..1efd84389ee 100644
--- a/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/web/server/OAuth2AuthorizationCodeGrantWebFilterTests.java
+++ b/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/web/server/OAuth2AuthorizationCodeGrantWebFilterTests.java
@@ -29,19 +29,28 @@
import org.springframework.security.oauth2.client.registration.ClientRegistration;
import org.springframework.security.oauth2.client.registration.ReactiveClientRegistrationRepository;
import org.springframework.security.oauth2.client.registration.TestClientRegistrations;
+import org.springframework.security.oauth2.core.OAuth2AuthenticationException;
+import org.springframework.security.oauth2.core.OAuth2AuthorizationException;
+import org.springframework.security.oauth2.core.OAuth2Error;
import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationRequest;
import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames;
+import org.springframework.security.web.server.savedrequest.ServerRequestCache;
import org.springframework.util.CollectionUtils;
+import org.springframework.web.server.ServerWebExchange;
import org.springframework.web.server.handler.DefaultWebFilterChain;
import reactor.core.publisher.Mono;
+import java.net.URI;
import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.Map;
+import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatCode;
+import static org.assertj.core.api.Assertions.assertThatThrownBy;
import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyNoInteractions;
@@ -50,6 +59,7 @@
/**
* @author Rob Winch
+ * @author Parikshit Dutta
* @since 5.1
*/
@RunWith(MockitoJUnitRunner.class)
@@ -99,6 +109,12 @@ public void constructorWhenAuthorizedClientRepositoryNullThenIllegalArgumentExce
.isInstanceOf(IllegalArgumentException.class);
}
+ @Test
+ public void setRequestCacheWhenRequestCacheIsNullThenThrowIllegalArgumentException() {
+ assertThatCode(() -> this.filter.setRequestCache(null))
+ .isInstanceOf(IllegalArgumentException.class);
+ }
+
@Test
public void filterWhenNotMatchThenAuthenticationManagerNotCalled() {
MockServerWebExchange exchange = MockServerWebExchange
@@ -233,6 +249,99 @@ public void filterWhenAuthorizationRequestRedirectUriParametersNotMatchThenNotPr
verifyNoInteractions(this.authenticationManager);
}
+ @Test
+ public void filterWhenAuthorizationSucceedsAndRequestCacheConfiguredThenRequestCacheUsed() {
+ ClientRegistration clientRegistration = TestClientRegistrations.clientRegistration().build();
+ when(this.clientRegistrationRepository.findByRegistrationId(any()))
+ .thenReturn(Mono.just(clientRegistration));
+ when(this.authorizedClientRepository.saveAuthorizedClient(any(), any(), any()))
+ .thenReturn(Mono.empty());
+ when(this.authenticationManager.authenticate(any()))
+ .thenReturn(Mono.just(TestOAuth2AuthorizationCodeAuthenticationTokens.authenticated()));
+
+ MockServerHttpRequest authorizationRequest = createAuthorizationRequest("/authorization/callback");
+ OAuth2AuthorizationRequest oauth2AuthorizationRequest =
+ createOAuth2AuthorizationRequest(authorizationRequest, clientRegistration);
+ when(this.authorizationRequestRepository.loadAuthorizationRequest(any()))
+ .thenReturn(Mono.just(oauth2AuthorizationRequest));
+ when(this.authorizationRequestRepository.removeAuthorizationRequest(any()))
+ .thenReturn(Mono.just(oauth2AuthorizationRequest));
+
+ MockServerHttpRequest authorizationResponse = createAuthorizationResponse(authorizationRequest);
+ MockServerWebExchange exchange = MockServerWebExchange.from(authorizationResponse);
+ DefaultWebFilterChain chain = new DefaultWebFilterChain(
+ e -> e.getResponse().setComplete(), Collections.emptyList());
+
+ ServerRequestCache requestCache = mock(ServerRequestCache.class);
+ when(requestCache.getRedirectUri(any(ServerWebExchange.class))).thenReturn(Mono.just(URI.create("/saved-request")));
+
+ this.filter.setRequestCache(requestCache);
+
+ this.filter.filter(exchange, chain).block();
+
+ verify(requestCache).getRedirectUri(exchange);
+ assertThat(exchange.getResponse().getHeaders().getLocation().toString()).isEqualTo("/saved-request");
+ }
+
+ // gh-8609
+ @Test
+ public void filterWhenAuthenticationConverterThrowsOAuth2AuthorizationExceptionThenMappedToOAuth2AuthenticationException() {
+ ClientRegistration clientRegistration = TestClientRegistrations.clientRegistration().build();
+ when(this.clientRegistrationRepository.findByRegistrationId(any())).thenReturn(Mono.empty());
+
+ MockServerHttpRequest authorizationRequest =
+ createAuthorizationRequest("/authorization/callback");
+ OAuth2AuthorizationRequest oauth2AuthorizationRequest =
+ createOAuth2AuthorizationRequest(authorizationRequest, clientRegistration);
+ when(this.authorizationRequestRepository.loadAuthorizationRequest(any()))
+ .thenReturn(Mono.just(oauth2AuthorizationRequest));
+ when(this.authorizationRequestRepository.removeAuthorizationRequest(any()))
+ .thenReturn(Mono.just(oauth2AuthorizationRequest));
+
+ MockServerHttpRequest authorizationResponse = createAuthorizationResponse(authorizationRequest);
+ MockServerWebExchange exchange = MockServerWebExchange.from(authorizationResponse);
+ DefaultWebFilterChain chain = new DefaultWebFilterChain(
+ e -> e.getResponse().setComplete(), Collections.emptyList());
+
+ assertThatThrownBy(() -> this.filter.filter(exchange, chain).block())
+ .isInstanceOf(OAuth2AuthenticationException.class)
+ .extracting(ex -> ((OAuth2AuthenticationException) ex).getError())
+ .extracting("errorCode")
+ .isEqualTo("client_registration_not_found");
+ verifyNoInteractions(this.authenticationManager);
+ }
+
+ // gh-8609
+ @Test
+ public void filterWhenAuthenticationManagerThrowsOAuth2AuthorizationExceptionThenMappedToOAuth2AuthenticationException() {
+ ClientRegistration clientRegistration = TestClientRegistrations.clientRegistration().build();
+ when(this.clientRegistrationRepository.findByRegistrationId(any()))
+ .thenReturn(Mono.just(clientRegistration));
+
+ MockServerHttpRequest authorizationRequest =
+ createAuthorizationRequest("/authorization/callback");
+ OAuth2AuthorizationRequest oauth2AuthorizationRequest =
+ createOAuth2AuthorizationRequest(authorizationRequest, clientRegistration);
+ when(this.authorizationRequestRepository.loadAuthorizationRequest(any()))
+ .thenReturn(Mono.just(oauth2AuthorizationRequest));
+ when(this.authorizationRequestRepository.removeAuthorizationRequest(any()))
+ .thenReturn(Mono.just(oauth2AuthorizationRequest));
+
+ when(this.authenticationManager.authenticate(any()))
+ .thenReturn(Mono.error(new OAuth2AuthorizationException(new OAuth2Error("authorization_error"))));
+
+ MockServerHttpRequest authorizationResponse = createAuthorizationResponse(authorizationRequest);
+ MockServerWebExchange exchange = MockServerWebExchange.from(authorizationResponse);
+ DefaultWebFilterChain chain = new DefaultWebFilterChain(
+ e -> e.getResponse().setComplete(), Collections.emptyList());
+
+ assertThatThrownBy(() -> this.filter.filter(exchange, chain).block())
+ .isInstanceOf(OAuth2AuthenticationException.class)
+ .extracting(ex -> ((OAuth2AuthenticationException) ex).getError())
+ .extracting("errorCode")
+ .isEqualTo("authorization_error");
+ }
+
private static OAuth2AuthorizationRequest createOAuth2AuthorizationRequest(
MockServerHttpRequest authorizationRequest, ClientRegistration registration) {
Map attributes = new HashMap<>();
diff --git a/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/web/server/ServerOAuth2AuthorizationCodeAuthenticationTokenConverterTest.java b/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/web/server/ServerOAuth2AuthorizationCodeAuthenticationTokenConverterTest.java
index 83aa315cd5a..95b19f7014e 100644
--- a/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/web/server/ServerOAuth2AuthorizationCodeAuthenticationTokenConverterTest.java
+++ b/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/web/server/ServerOAuth2AuthorizationCodeAuthenticationTokenConverterTest.java
@@ -35,6 +35,7 @@
import reactor.core.publisher.Mono;
import java.util.Collections;
+import java.util.Map;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
@@ -96,7 +97,7 @@ public void applyWhenAuthorizationRequestEmptyThenOAuth2AuthorizationException()
@Test
public void applyWhenAttributesMissingThenOAuth2AuthorizationException() {
- this.authorizationRequest.attributes(Collections.emptyMap());
+ this.authorizationRequest.attributes(Map::clear);
when(this.authorizationRequestRepository.removeAuthorizationRequest(any())).thenReturn(Mono.just(this.authorizationRequest.build()));
assertThatThrownBy(() -> applyConverter())
diff --git a/oauth2/oauth2-core/src/main/java/org/springframework/security/oauth2/core/endpoint/OAuth2AccessTokenResponse.java b/oauth2/oauth2-core/src/main/java/org/springframework/security/oauth2/core/endpoint/OAuth2AccessTokenResponse.java
index 55488926cb1..5bd0df56967 100644
--- a/oauth2/oauth2-core/src/main/java/org/springframework/security/oauth2/core/endpoint/OAuth2AccessTokenResponse.java
+++ b/oauth2/oauth2-core/src/main/java/org/springframework/security/oauth2/core/endpoint/OAuth2AccessTokenResponse.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2018 the original author or authors.
+ * Copyright 2002-2020 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.
@@ -82,8 +82,9 @@ public static Builder withToken(String tokenValue) {
}
/**
- * Returns a new {@link Builder}, initialized with the provided response
- * @param response the response to intialize the builder with
+ * Returns a new {@link Builder}, initialized with the provided response.
+ *
+ * @param response the response to initialize the builder with
* @return the {@link Builder}
*/
public static Builder withResponse(OAuth2AccessTokenResponse response) {
@@ -96,20 +97,19 @@ public static Builder withResponse(OAuth2AccessTokenResponse response) {
public static class Builder {
private String tokenValue;
private OAuth2AccessToken.TokenType tokenType;
+ private Instant issuedAt;
+ private Instant expiresAt;
private long expiresIn;
private Set scopes;
private String refreshToken;
private Map additionalParameters;
- private Instant issuedAt;
- private Instant expiresAt;
-
private Builder(OAuth2AccessTokenResponse response) {
OAuth2AccessToken accessToken = response.getAccessToken();
this.tokenValue = accessToken.getTokenValue();
this.tokenType = accessToken.getTokenType();
- this.expiresAt = accessToken.getExpiresAt();
this.issuedAt = accessToken.getIssuedAt();
+ this.expiresAt = accessToken.getExpiresAt();
this.scopes = accessToken.getScopes();
this.refreshToken = response.getRefreshToken() == null ?
null : response.getRefreshToken().getTokenValue();
@@ -139,6 +139,7 @@ public Builder tokenType(OAuth2AccessToken.TokenType tokenType) {
*/
public Builder expiresIn(long expiresIn) {
this.expiresIn = expiresIn;
+ this.expiresAt = null;
return this;
}
@@ -182,7 +183,6 @@ public Builder additionalParameters(Map additionalParameters) {
*/
public OAuth2AccessTokenResponse build() {
Instant issuedAt = getIssuedAt();
-
Instant expiresAt = getExpiresAt();
OAuth2AccessTokenResponse accessTokenResponse = new OAuth2AccessTokenResponse();
diff --git a/oauth2/oauth2-core/src/main/java/org/springframework/security/oauth2/core/endpoint/OAuth2AuthorizationRequest.java b/oauth2/oauth2-core/src/main/java/org/springframework/security/oauth2/core/endpoint/OAuth2AuthorizationRequest.java
index 28937c61c45..9323ce2a537 100644
--- a/oauth2/oauth2-core/src/main/java/org/springframework/security/oauth2/core/endpoint/OAuth2AuthorizationRequest.java
+++ b/oauth2/oauth2-core/src/main/java/org/springframework/security/oauth2/core/endpoint/OAuth2AuthorizationRequest.java
@@ -229,9 +229,9 @@ public static class Builder {
private String redirectUri;
private Set scopes;
private String state;
- private Consumer> additionalParametersConsumer = params -> {};
+ private Map additionalParameters = new LinkedHashMap<>();
private Consumer> parametersConsumer = params -> {};
- private Consumer> attributesConsumer = attrs -> {};
+ private Map attributes = new LinkedHashMap<>();
private String authorizationRequestUri;
private Function authorizationRequestUriFunction = builder -> builder.build();
private final DefaultUriBuilderFactory uriBuilderFactory;
@@ -325,8 +325,8 @@ public Builder state(String state) {
* @return the {@link Builder}
*/
public Builder additionalParameters(Map additionalParameters) {
- if (additionalParameters != null) {
- return additionalParameters(params -> params.putAll(additionalParameters));
+ if (!CollectionUtils.isEmpty(additionalParameters)) {
+ this.additionalParameters.putAll(additionalParameters);
}
return this;
}
@@ -340,7 +340,7 @@ public Builder additionalParameters(Map additionalParameters) {
*/
public Builder additionalParameters(Consumer> additionalParametersConsumer) {
if (additionalParametersConsumer != null) {
- this.additionalParametersConsumer = additionalParametersConsumer;
+ additionalParametersConsumer.accept(this.additionalParameters);
}
return this;
}
@@ -367,8 +367,8 @@ public Builder parameters(Consumer> parametersConsumer) {
* @return the {@link Builder}
*/
public Builder attributes(Map attributes) {
- if (attributes != null) {
- return attributes(attrs -> attrs.putAll(attributes));
+ if (!CollectionUtils.isEmpty(attributes)) {
+ this.attributes.putAll(attributes);
}
return this;
}
@@ -382,7 +382,7 @@ public Builder attributes(Map attributes) {
*/
public Builder attributes(Consumer> attributesConsumer) {
if (attributesConsumer != null) {
- this.attributesConsumer = attributesConsumer;
+ attributesConsumer.accept(this.attributes);
}
return this;
}
@@ -439,12 +439,8 @@ public OAuth2AuthorizationRequest build() {
authorizationRequest.scopes = Collections.unmodifiableSet(
CollectionUtils.isEmpty(this.scopes) ?
Collections.emptySet() : new LinkedHashSet<>(this.scopes));
- Map additionalParameters = new LinkedHashMap<>();
- this.additionalParametersConsumer.accept(additionalParameters);
- authorizationRequest.additionalParameters = Collections.unmodifiableMap(additionalParameters);
- Map attributes = new LinkedHashMap<>();
- this.attributesConsumer.accept(attributes);
- authorizationRequest.attributes = Collections.unmodifiableMap(attributes);
+ authorizationRequest.additionalParameters = Collections.unmodifiableMap(this.additionalParameters);
+ authorizationRequest.attributes = Collections.unmodifiableMap(this.attributes);
authorizationRequest.authorizationRequestUri =
StringUtils.hasText(this.authorizationRequestUri) ?
this.authorizationRequestUri : this.buildAuthorizationRequestUri();
@@ -457,7 +453,7 @@ private String buildAuthorizationRequestUri() {
this.parametersConsumer.accept(parameters);
MultiValueMap queryParams = new LinkedMultiValueMap<>();
parameters.forEach((k, v) -> queryParams.set(
- encodeQueryParam(k), encodeQueryParam(v.toString()))); // Encoded
+ encodeQueryParam(k), encodeQueryParam(String.valueOf(v)))); // Encoded
UriBuilder uriBuilder = this.uriBuilderFactory.uriString(this.authorizationUri)
.queryParams(queryParams);
return this.authorizationRequestUriFunction.apply(uriBuilder).toString();
@@ -477,9 +473,7 @@ private Map getParameters() {
if (this.redirectUri != null) {
parameters.put(OAuth2ParameterNames.REDIRECT_URI, this.redirectUri);
}
- Map additionalParameters = new LinkedHashMap<>();
- this.additionalParametersConsumer.accept(additionalParameters);
- additionalParameters.forEach((k, v) -> parameters.put(k, v.toString()));
+ parameters.putAll(this.additionalParameters);
return parameters;
}
diff --git a/oauth2/oauth2-core/src/main/java/org/springframework/security/oauth2/core/http/converter/OAuth2ErrorHttpMessageConverter.java b/oauth2/oauth2-core/src/main/java/org/springframework/security/oauth2/core/http/converter/OAuth2ErrorHttpMessageConverter.java
index 1e60804d80b..a9901c97d91 100644
--- a/oauth2/oauth2-core/src/main/java/org/springframework/security/oauth2/core/http/converter/OAuth2ErrorHttpMessageConverter.java
+++ b/oauth2/oauth2-core/src/main/java/org/springframework/security/oauth2/core/http/converter/OAuth2ErrorHttpMessageConverter.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2018 the original author or authors.
+ * Copyright 2002-2020 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.
@@ -34,6 +34,7 @@
import java.nio.charset.StandardCharsets;
import java.util.HashMap;
import java.util.Map;
+import java.util.stream.Collectors;
/**
* A {@link HttpMessageConverter} for an {@link OAuth2Error OAuth 2.0 Error}.
@@ -46,8 +47,8 @@
public class OAuth2ErrorHttpMessageConverter extends AbstractHttpMessageConverter {
private static final Charset DEFAULT_CHARSET = StandardCharsets.UTF_8;
- private static final ParameterizedTypeReference> PARAMETERIZED_RESPONSE_TYPE =
- new ParameterizedTypeReference>() {};
+ private static final ParameterizedTypeReference> PARAMETERIZED_RESPONSE_TYPE =
+ new ParameterizedTypeReference>() {};
private GenericHttpMessageConverter jsonMessageConverter = HttpMessageConverters.getJsonMessageConverter();
@@ -69,10 +70,16 @@ protected OAuth2Error readInternal(Class extends OAuth2Error> clazz, HttpInput
throws HttpMessageNotReadableException {
try {
+ // gh-8157
+ // Parse parameter values as Object in order to handle potential JSON Object and then convert values to String
@SuppressWarnings("unchecked")
- Map errorParameters = (Map) this.jsonMessageConverter.read(
+ Map errorParameters = (Map) this.jsonMessageConverter.read(
PARAMETERIZED_RESPONSE_TYPE.getType(), null, inputMessage);
- return this.errorConverter.convert(errorParameters);
+ return this.errorConverter.convert(
+ errorParameters.entrySet().stream()
+ .collect(Collectors.toMap(
+ Map.Entry::getKey,
+ entry -> String.valueOf(entry.getValue()))));
} catch (Exception ex) {
throw new HttpMessageNotReadableException("An error occurred reading the OAuth 2.0 Error: " +
ex.getMessage(), ex, inputMessage);
diff --git a/oauth2/oauth2-core/src/test/java/org/springframework/security/oauth2/core/endpoint/OAuth2AccessTokenResponseTests.java b/oauth2/oauth2-core/src/test/java/org/springframework/security/oauth2/core/endpoint/OAuth2AccessTokenResponseTests.java
index fa83124ac6e..8f2d3aa1cbc 100644
--- a/oauth2/oauth2-core/src/test/java/org/springframework/security/oauth2/core/endpoint/OAuth2AccessTokenResponseTests.java
+++ b/oauth2/oauth2-core/src/test/java/org/springframework/security/oauth2/core/endpoint/OAuth2AccessTokenResponseTests.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2018 the original author or authors.
+ * Copyright 2002-2020 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.
@@ -153,4 +153,20 @@ public void buildWhenResponseAndRefreshNullThenRefreshNull() {
assertThat(withResponse.getRefreshToken()).isNull();
}
+
+ @Test
+ public void buildWhenResponseAndExpiresInThenExpiresAtEqualToIssuedAtPlusExpiresIn() {
+ OAuth2AccessTokenResponse tokenResponse = OAuth2AccessTokenResponse
+ .withToken(TOKEN_VALUE)
+ .tokenType(OAuth2AccessToken.TokenType.BEARER)
+ .build();
+
+ long expiresIn = 30;
+ OAuth2AccessTokenResponse withResponse = OAuth2AccessTokenResponse.withResponse(tokenResponse)
+ .expiresIn(expiresIn)
+ .build();
+
+ assertThat(withResponse.getAccessToken().getExpiresAt()).isEqualTo(
+ withResponse.getAccessToken().getIssuedAt().plusSeconds(expiresIn));
+ }
}
diff --git a/oauth2/oauth2-core/src/test/java/org/springframework/security/oauth2/core/endpoint/OAuth2AuthorizationRequestTests.java b/oauth2/oauth2-core/src/test/java/org/springframework/security/oauth2/core/endpoint/OAuth2AuthorizationRequestTests.java
index 7952db6a54b..8f0745d4f29 100644
--- a/oauth2/oauth2-core/src/test/java/org/springframework/security/oauth2/core/endpoint/OAuth2AuthorizationRequestTests.java
+++ b/oauth2/oauth2-core/src/test/java/org/springframework/security/oauth2/core/endpoint/OAuth2AuthorizationRequestTests.java
@@ -121,7 +121,7 @@ public void buildWhenStateIsNullThenDoesNotThrowAnyException() {
}
@Test
- public void buildWhenAdditionalParametersIsNullThenDoesNotThrowAnyException() {
+ public void buildWhenAdditionalParametersEmptyThenDoesNotThrowAnyException() {
assertThatCode(() ->
OAuth2AuthorizationRequest.authorizationCode()
.authorizationUri(AUTHORIZATION_URI)
@@ -129,7 +129,7 @@ public void buildWhenAdditionalParametersIsNullThenDoesNotThrowAnyException() {
.redirectUri(REDIRECT_URI)
.scopes(SCOPES)
.state(STATE)
- .additionalParameters((Map) null)
+ .additionalParameters(Map::clear)
.build())
.doesNotThrowAnyException();
}
diff --git a/oauth2/oauth2-core/src/test/java/org/springframework/security/oauth2/core/http/converter/OAuth2ErrorHttpMessageConverterTests.java b/oauth2/oauth2-core/src/test/java/org/springframework/security/oauth2/core/http/converter/OAuth2ErrorHttpMessageConverterTests.java
index a57b1df1b83..11211aad561 100644
--- a/oauth2/oauth2-core/src/test/java/org/springframework/security/oauth2/core/http/converter/OAuth2ErrorHttpMessageConverterTests.java
+++ b/oauth2/oauth2-core/src/test/java/org/springframework/security/oauth2/core/http/converter/OAuth2ErrorHttpMessageConverterTests.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2018 the original author or authors.
+ * Copyright 2002-2020 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.
@@ -78,6 +78,25 @@ public void readInternalWhenErrorResponseThenReadOAuth2Error() throws Exception
assertThat(oauth2Error.getUri()).isEqualTo("https://tools.ietf.org/html/rfc6749#section-5.2");
}
+ // gh-8157
+ @Test
+ public void readInternalWhenErrorResponseWithObjectThenReadOAuth2Error() throws Exception {
+ String errorResponse = "{\n" +
+ " \"error\": \"unauthorized_client\",\n" +
+ " \"error_description\": \"The client is not authorized\",\n" +
+ " \"error_codes\": [65001],\n" +
+ " \"error_uri\": \"https://tools.ietf.org/html/rfc6749#section-5.2\"\n" +
+ "}\n";
+
+ MockClientHttpResponse response = new MockClientHttpResponse(
+ errorResponse.getBytes(), HttpStatus.BAD_REQUEST);
+
+ OAuth2Error oauth2Error = this.messageConverter.readInternal(OAuth2Error.class, response);
+ assertThat(oauth2Error.getErrorCode()).isEqualTo("unauthorized_client");
+ assertThat(oauth2Error.getDescription()).isEqualTo("The client is not authorized");
+ assertThat(oauth2Error.getUri()).isEqualTo("https://tools.ietf.org/html/rfc6749#section-5.2");
+ }
+
@Test
public void readInternalWhenConversionFailsThenThrowHttpMessageNotReadableException() {
Converter errorConverter = mock(Converter.class);
diff --git a/oauth2/oauth2-jose/src/main/java/org/springframework/security/oauth2/jwt/JWSAlgorithmMapJWSKeySelector.java b/oauth2/oauth2-jose/src/main/java/org/springframework/security/oauth2/jwt/JWSAlgorithmMapJWSKeySelector.java
deleted file mode 100644
index 2947e90ff50..00000000000
--- a/oauth2/oauth2-jose/src/main/java/org/springframework/security/oauth2/jwt/JWSAlgorithmMapJWSKeySelector.java
+++ /dev/null
@@ -1,54 +0,0 @@
-/*
- * Copyright 2002-2019 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.oauth2.jwt;
-
-import java.security.Key;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
-
-import com.nimbusds.jose.JWSAlgorithm;
-import com.nimbusds.jose.JWSHeader;
-import com.nimbusds.jose.KeySourceException;
-import com.nimbusds.jose.proc.JWSKeySelector;
-import com.nimbusds.jose.proc.SecurityContext;
-
-/**
- * Class for delegating to a Nimbus JWSKeySelector by the given JWSAlgorithm
- *
- * @author Josh Cummings
- */
-class JWSAlgorithmMapJWSKeySelector implements JWSKeySelector {
- private Map> jwsKeySelectors;
-
- JWSAlgorithmMapJWSKeySelector(Map> jwsKeySelectors) {
- this.jwsKeySelectors = jwsKeySelectors;
- }
-
- @Override
- public List extends Key> selectJWSKeys(JWSHeader header, C context) throws KeySourceException {
- JWSKeySelector keySelector = this.jwsKeySelectors.get(header.getAlgorithm());
- if (keySelector == null) {
- throw new IllegalArgumentException("Unsupported algorithm of " + header.getAlgorithm());
- }
- return keySelector.selectJWSKeys(header, context);
- }
-
- public Set getExpectedJWSAlgorithms() {
- return this.jwsKeySelectors.keySet();
- }
-}
diff --git a/oauth2/oauth2-jose/src/main/java/org/springframework/security/oauth2/jwt/NimbusJwtDecoder.java b/oauth2/oauth2-jose/src/main/java/org/springframework/security/oauth2/jwt/NimbusJwtDecoder.java
index 68c2318a1ba..03993b32e68 100644
--- a/oauth2/oauth2-jose/src/main/java/org/springframework/security/oauth2/jwt/NimbusJwtDecoder.java
+++ b/oauth2/oauth2-jose/src/main/java/org/springframework/security/oauth2/jwt/NimbusJwtDecoder.java
@@ -23,7 +23,6 @@
import java.text.ParseException;
import java.util.Arrays;
import java.util.Collections;
-import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.Map;
@@ -34,6 +33,8 @@
import com.nimbusds.jose.JOSEException;
import com.nimbusds.jose.JWSAlgorithm;
import com.nimbusds.jose.RemoteKeySourceException;
+import com.nimbusds.jose.jwk.JWKSet;
+import com.nimbusds.jose.jwk.source.JWKSetCache;
import com.nimbusds.jose.jwk.source.JWKSource;
import com.nimbusds.jose.jwk.source.RemoteJWKSet;
import com.nimbusds.jose.proc.JWSKeySelector;
@@ -49,6 +50,7 @@
import com.nimbusds.jwt.proc.DefaultJWTProcessor;
import com.nimbusds.jwt.proc.JWTProcessor;
+import org.springframework.cache.Cache;
import org.springframework.core.convert.converter.Converter;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
@@ -68,6 +70,7 @@
*
* @author Josh Cummings
* @author Joe Grandja
+ * @author Mykyta Bezverkhyi
* @since 5.2
*/
public final class NimbusJwtDecoder implements JwtDecoder {
@@ -215,10 +218,13 @@ public static final class JwkSetUriJwtDecoderBuilder {
private String jwkSetUri;
private Set signatureAlgorithms = new HashSet<>();
private RestOperations restOperations = new RestTemplate();
+ private Cache cache;
+ private Consumer> jwtProcessorCustomizer;
private JwkSetUriJwtDecoderBuilder(String jwkSetUri) {
Assert.hasText(jwkSetUri, "jwkSetUri cannot be empty");
this.jwkSetUri = jwkSetUri;
+ this.jwtProcessorCustomizer = (processor) -> {};
}
/**
@@ -264,31 +270,66 @@ public JwkSetUriJwtDecoderBuilder restOperations(RestOperations restOperations)
return this;
}
+ /**
+ * Use the given {@link Cache} to store
+ * JWK Set .
+ *
+ * @param cache the {@link Cache} to be used to store JWK Set
+ * @return a {@link JwkSetUriJwtDecoderBuilder} for further configurations
+ * @since 5.4
+ */
+ public JwkSetUriJwtDecoderBuilder cache(Cache cache) {
+ Assert.notNull(cache, "cache cannot be null");
+ this.cache = cache;
+ return this;
+ }
+
+ /**
+ * Use the given {@link Consumer} to customize the {@link JWTProcessor ConfigurableJWTProcessor} before
+ * passing it to the build {@link NimbusJwtDecoder}.
+ *
+ * @param jwtProcessorCustomizer the callback used to alter the processor
+ * @return a {@link JwkSetUriJwtDecoderBuilder} for further configurations
+ * @since 5.4
+ */
+ public JwkSetUriJwtDecoderBuilder jwtProcessorCustomizer(Consumer> jwtProcessorCustomizer) {
+ Assert.notNull(jwtProcessorCustomizer, "jwtProcessorCustomizer cannot be null");
+ this.jwtProcessorCustomizer = jwtProcessorCustomizer;
+ return this;
+ }
+
JWSKeySelector jwsKeySelector(JWKSource jwkSource) {
if (this.signatureAlgorithms.isEmpty()) {
return new JWSVerificationKeySelector<>(JWSAlgorithm.RS256, jwkSource);
- } else if (this.signatureAlgorithms.size() == 1) {
- JWSAlgorithm jwsAlgorithm = JWSAlgorithm.parse(this.signatureAlgorithms.iterator().next().getName());
- return new JWSVerificationKeySelector<>(jwsAlgorithm, jwkSource);
} else {
- Map> jwsKeySelectors = new HashMap<>();
+ Set jwsAlgorithms = new HashSet<>();
for (SignatureAlgorithm signatureAlgorithm : this.signatureAlgorithms) {
- JWSAlgorithm jwsAlg = JWSAlgorithm.parse(signatureAlgorithm.getName());
- jwsKeySelectors.put(jwsAlg, new JWSVerificationKeySelector<>(jwsAlg, jwkSource));
+ JWSAlgorithm jwsAlgorithm = JWSAlgorithm.parse(signatureAlgorithm.getName());
+ jwsAlgorithms.add(jwsAlgorithm);
}
- return new JWSAlgorithmMapJWSKeySelector<>(jwsKeySelectors);
+ return new JWSVerificationKeySelector<>(jwsAlgorithms, jwkSource);
}
}
+ JWKSource jwkSource(ResourceRetriever jwkSetRetriever) {
+ if (this.cache == null) {
+ return new RemoteJWKSet<>(toURL(this.jwkSetUri), jwkSetRetriever);
+ }
+ ResourceRetriever cachingJwkSetRetriever = new CachingResourceRetriever(this.cache, jwkSetRetriever);
+ return new RemoteJWKSet<>(toURL(this.jwkSetUri), cachingJwkSetRetriever, new NoOpJwkSetCache());
+ }
+
JWTProcessor processor() {
ResourceRetriever jwkSetRetriever = new RestOperationsResourceRetriever(this.restOperations);
- JWKSource jwkSource = new RemoteJWKSet<>(toURL(this.jwkSetUri), jwkSetRetriever);
+ JWKSource jwkSource = jwkSource(jwkSetRetriever);
ConfigurableJWTProcessor jwtProcessor = new DefaultJWTProcessor<>();
jwtProcessor.setJWSKeySelector(jwsKeySelector(jwkSource));
// Spring Security validates the claim set independent from Nimbus
jwtProcessor.setJWTClaimsSetVerifier((claims, context) -> { });
+ this.jwtProcessorCustomizer.accept(jwtProcessor);
+
return jwtProcessor;
}
@@ -309,6 +350,51 @@ private static URL toURL(String url) {
}
}
+ private static class NoOpJwkSetCache implements JWKSetCache {
+ @Override
+ public void put(JWKSet jwkSet) {
+ }
+
+ @Override
+ public JWKSet get() {
+ return null;
+ }
+
+ @Override
+ public boolean requiresRefresh() {
+ return true;
+ }
+ }
+
+ private static class CachingResourceRetriever implements ResourceRetriever {
+ private final Cache cache;
+ private final ResourceRetriever resourceRetriever;
+
+ CachingResourceRetriever(Cache cache, ResourceRetriever resourceRetriever) {
+ this.cache = cache;
+ this.resourceRetriever = resourceRetriever;
+ }
+
+ @Override
+ public Resource retrieveResource(URL url) throws IOException {
+ String jwkSet;
+ try {
+ jwkSet = this.cache.get(url.toString(),
+ () -> this.resourceRetriever.retrieveResource(url).getContent());
+ } catch (Cache.ValueRetrievalException ex) {
+ Throwable thrownByValueLoader = ex.getCause();
+ if (thrownByValueLoader instanceof IOException) {
+ throw (IOException) thrownByValueLoader;
+ }
+ throw new IOException(thrownByValueLoader);
+ } catch (Exception ex) {
+ throw new IOException(ex);
+ }
+
+ return new Resource(jwkSet, "UTF-8");
+ }
+ }
+
private static class RestOperationsResourceRetriever implements ResourceRetriever {
private static final MediaType APPLICATION_JWK_SET_JSON = new MediaType("application", "jwk-set+json");
private final RestOperations restOperations;
@@ -346,11 +432,13 @@ public Resource retrieveResource(URL url) throws IOException {
public static final class PublicKeyJwtDecoderBuilder {
private JWSAlgorithm jwsAlgorithm;
private RSAPublicKey key;
+ private Consumer> jwtProcessorCustomizer;
private PublicKeyJwtDecoderBuilder(RSAPublicKey key) {
Assert.notNull(key, "key cannot be null");
this.jwsAlgorithm = JWSAlgorithm.RS256;
this.key = key;
+ this.jwtProcessorCustomizer = (processor) -> {};
}
/**
@@ -369,6 +457,20 @@ public PublicKeyJwtDecoderBuilder signatureAlgorithm(SignatureAlgorithm signatur
return this;
}
+ /**
+ * Use the given {@link Consumer} to customize the {@link JWTProcessor ConfigurableJWTProcessor} before
+ * passing it to the build {@link NimbusJwtDecoder}.
+ *
+ * @param jwtProcessorCustomizer the callback used to alter the processor
+ * @return a {@link PublicKeyJwtDecoderBuilder} for further configurations
+ * @since 5.4
+ */
+ public PublicKeyJwtDecoderBuilder jwtProcessorCustomizer(Consumer> jwtProcessorCustomizer) {
+ Assert.notNull(jwtProcessorCustomizer, "jwtProcessorCustomizer cannot be null");
+ this.jwtProcessorCustomizer = jwtProcessorCustomizer;
+ return this;
+ }
+
JWTProcessor processor() {
if (!JWSAlgorithm.Family.RSA.contains(this.jwsAlgorithm)) {
throw new IllegalStateException("The provided key is of type RSA; " +
@@ -384,6 +486,8 @@ JWTProcessor processor() {
// Spring Security validates the claim set independent from Nimbus
jwtProcessor.setJWTClaimsSetVerifier((claims, context) -> { });
+ this.jwtProcessorCustomizer.accept(jwtProcessor);
+
return jwtProcessor;
}
@@ -403,10 +507,12 @@ public NimbusJwtDecoder build() {
public static final class SecretKeyJwtDecoderBuilder {
private final SecretKey secretKey;
private JWSAlgorithm jwsAlgorithm = JWSAlgorithm.HS256;
+ private Consumer> jwtProcessorCustomizer;
private SecretKeyJwtDecoderBuilder(SecretKey secretKey) {
Assert.notNull(secretKey, "secretKey cannot be null");
this.secretKey = secretKey;
+ this.jwtProcessorCustomizer = (processor) -> {};
}
/**
@@ -426,6 +532,20 @@ public SecretKeyJwtDecoderBuilder macAlgorithm(MacAlgorithm macAlgorithm) {
return this;
}
+ /**
+ * Use the given {@link Consumer} to customize the {@link JWTProcessor ConfigurableJWTProcessor} before
+ * passing it to the build {@link NimbusJwtDecoder}.
+ *
+ * @param jwtProcessorCustomizer the callback used to alter the processor
+ * @return a {@link SecretKeyJwtDecoderBuilder} for further configurations
+ * @since 5.4
+ */
+ public SecretKeyJwtDecoderBuilder jwtProcessorCustomizer(Consumer