Browse Source

Idiomatic Kotlin DSL for configuring HTTP security

Issue: gh-5558
Eleftheria Stein-Kousathana 5 years ago
parent
commit
2df1099da5
88 changed files with 9773 additions and 2 deletions
  1. 3 0
      build.gradle
  2. 10 0
      config/spring-security-config.gradle
  3. 42 0
      config/src/main/kotlin/org/springframework/security/config/web/servlet/AbstractRequestMatcherDsl.kt
  4. 68 0
      config/src/main/kotlin/org/springframework/security/config/web/servlet/AnonymousDsl.kt
  5. 159 0
      config/src/main/kotlin/org/springframework/security/config/web/servlet/AuthorizeRequestsDsl.kt
  6. 45 0
      config/src/main/kotlin/org/springframework/security/config/web/servlet/CorsDsl.kt
  7. 87 0
      config/src/main/kotlin/org/springframework/security/config/web/servlet/CsrfDsl.kt
  8. 92 0
      config/src/main/kotlin/org/springframework/security/config/web/servlet/ExceptionHandlingDsl.kt
  9. 85 0
      config/src/main/kotlin/org/springframework/security/config/web/servlet/FormLoginDsl.kt
  10. 199 0
      config/src/main/kotlin/org/springframework/security/config/web/servlet/HeadersDsl.kt
  11. 62 0
      config/src/main/kotlin/org/springframework/security/config/web/servlet/HttpBasicDsl.kt
  12. 651 0
      config/src/main/kotlin/org/springframework/security/config/web/servlet/HttpSecurityDsl.kt
  13. 126 0
      config/src/main/kotlin/org/springframework/security/config/web/servlet/LogoutDsl.kt
  14. 97 0
      config/src/main/kotlin/org/springframework/security/config/web/servlet/OAuth2ClientDsl.kt
  15. 225 0
      config/src/main/kotlin/org/springframework/security/config/web/servlet/OAuth2LoginDsl.kt
  16. 111 0
      config/src/main/kotlin/org/springframework/security/config/web/servlet/OAuth2ResourceServerDsl.kt
  17. 56 0
      config/src/main/kotlin/org/springframework/security/config/web/servlet/PortMapperDsl.kt
  18. 41 0
      config/src/main/kotlin/org/springframework/security/config/web/servlet/RequestCacheDsl.kt
  19. 136 0
      config/src/main/kotlin/org/springframework/security/config/web/servlet/RequiresChannelDsl.kt
  20. 91 0
      config/src/main/kotlin/org/springframework/security/config/web/servlet/Saml2Dsl.kt
  21. 117 0
      config/src/main/kotlin/org/springframework/security/config/web/servlet/SessionManagementDsl.kt
  22. 65 0
      config/src/main/kotlin/org/springframework/security/config/web/servlet/X509Dsl.kt
  23. 46 0
      config/src/main/kotlin/org/springframework/security/config/web/servlet/headers/CacheControlDsl.kt
  24. 47 0
      config/src/main/kotlin/org/springframework/security/config/web/servlet/headers/ContentSecurityPolicyDsl.kt
  25. 46 0
      config/src/main/kotlin/org/springframework/security/config/web/servlet/headers/ContentTypeOptionsDsl.kt
  26. 62 0
      config/src/main/kotlin/org/springframework/security/config/web/servlet/headers/FrameOptionsDsl.kt
  27. 75 0
      config/src/main/kotlin/org/springframework/security/config/web/servlet/headers/HttpPublicKeyPinningDsl.kt
  28. 63 0
      config/src/main/kotlin/org/springframework/security/config/web/servlet/headers/HttpStrictTransportSecurityDsl.kt
  29. 41 0
      config/src/main/kotlin/org/springframework/security/config/web/servlet/headers/ReferrerPolicyDsl.kt
  30. 55 0
      config/src/main/kotlin/org/springframework/security/config/web/servlet/headers/XssProtectionConfigDsl.kt
  31. 49 0
      config/src/main/kotlin/org/springframework/security/config/web/servlet/oauth2/client/AuthorizationCodeGrantDsl.kt
  32. 47 0
      config/src/main/kotlin/org/springframework/security/config/web/servlet/oauth2/login/AuthorizationEndpointDsl.kt
  33. 38 0
      config/src/main/kotlin/org/springframework/security/config/web/servlet/oauth2/login/RedirectionEndpointDsl.kt
  34. 41 0
      config/src/main/kotlin/org/springframework/security/config/web/servlet/oauth2/login/TokenEndpointDsl.kt
  35. 67 0
      config/src/main/kotlin/org/springframework/security/config/web/servlet/oauth2/login/UserInfoEndpointDsl.kt
  36. 49 0
      config/src/main/kotlin/org/springframework/security/config/web/servlet/oauth2/resourceserver/JwtDsl.kt
  37. 54 0
      config/src/main/kotlin/org/springframework/security/config/web/servlet/oauth2/resourceserver/OpaqueTokenDsl.kt
  38. 67 0
      config/src/main/kotlin/org/springframework/security/config/web/servlet/session/SessionConcurrencyDsl.kt
  39. 83 0
      config/src/main/kotlin/org/springframework/security/config/web/servlet/session/SessionFixationDsl.kt
  40. 163 0
      config/src/test/kotlin/org/springframework/security/config/web/servlet/AnonymousDslTests.kt
  41. 211 0
      config/src/test/kotlin/org/springframework/security/config/web/servlet/AuthorizeRequestsDslTests.kt
  42. 138 0
      config/src/test/kotlin/org/springframework/security/config/web/servlet/CorsDslTests.kt
  43. 265 0
      config/src/test/kotlin/org/springframework/security/config/web/servlet/CsrfDslTests.kt
  44. 252 0
      config/src/test/kotlin/org/springframework/security/config/web/servlet/ExceptionHandlingDslTests.kt
  45. 291 0
      config/src/test/kotlin/org/springframework/security/config/web/servlet/FormLoginDslTests.kt
  46. 94 0
      config/src/test/kotlin/org/springframework/security/config/web/servlet/HeadersDslTests.kt
  47. 205 0
      config/src/test/kotlin/org/springframework/security/config/web/servlet/HttpBasicDslTests.kt
  48. 215 0
      config/src/test/kotlin/org/springframework/security/config/web/servlet/HttpSecurityDslTests.kt
  49. 310 0
      config/src/test/kotlin/org/springframework/security/config/web/servlet/LogoutDslTests.kt
  50. 144 0
      config/src/test/kotlin/org/springframework/security/config/web/servlet/OAuth2ClientDslTests.kt
  51. 126 0
      config/src/test/kotlin/org/springframework/security/config/web/servlet/OAuth2LoginDslTests.kt
  52. 156 0
      config/src/test/kotlin/org/springframework/security/config/web/servlet/OAuth2ResourceServerDslTests.kt
  53. 93 0
      config/src/test/kotlin/org/springframework/security/config/web/servlet/PortMapperDslTests.kt
  54. 90 0
      config/src/test/kotlin/org/springframework/security/config/web/servlet/RequestCacheDslTests.kt
  55. 138 0
      config/src/test/kotlin/org/springframework/security/config/web/servlet/RequiresChannelDslTests.kt
  56. 105 0
      config/src/test/kotlin/org/springframework/security/config/web/servlet/Saml2DslTests.kt
  57. 218 0
      config/src/test/kotlin/org/springframework/security/config/web/servlet/SessionManagementDslTests.kt
  58. 221 0
      config/src/test/kotlin/org/springframework/security/config/web/servlet/X509DslTests.kt
  59. 92 0
      config/src/test/kotlin/org/springframework/security/config/web/servlet/headers/CacheControlDslTests.kt
  60. 116 0
      config/src/test/kotlin/org/springframework/security/config/web/servlet/headers/ContentSecurityPolicyDslTests.kt
  61. 88 0
      config/src/test/kotlin/org/springframework/security/config/web/servlet/headers/ContentTypeOptionsDslTests.kt
  62. 167 0
      config/src/test/kotlin/org/springframework/security/config/web/servlet/headers/FrameOptionsDslTests.kt
  63. 230 0
      config/src/test/kotlin/org/springframework/security/config/web/servlet/headers/HttpPublicKeyPinningDslTests.kt
  64. 167 0
      config/src/test/kotlin/org/springframework/security/config/web/servlet/headers/HttpStrictTransportSecurityDslTests.kt
  65. 89 0
      config/src/test/kotlin/org/springframework/security/config/web/servlet/headers/ReferrerPolicyDslTests.kt
  66. 140 0
      config/src/test/kotlin/org/springframework/security/config/web/servlet/headers/XssProtectionConfigDslTests.kt
  67. 192 0
      config/src/test/kotlin/org/springframework/security/config/web/servlet/oauth2/client/AuthorizationCodeGrantDslTests.kt
  68. 144 0
      config/src/test/kotlin/org/springframework/security/config/web/servlet/oauth2/login/AuthorizationEndpointDslTests.kt
  69. 142 0
      config/src/test/kotlin/org/springframework/security/config/web/servlet/oauth2/login/RedirectionEndpointDslTests.kt
  70. 128 0
      config/src/test/kotlin/org/springframework/security/config/web/servlet/oauth2/login/TokenEndpointDslTests.kt
  71. 139 0
      config/src/test/kotlin/org/springframework/security/config/web/servlet/oauth2/login/UserInfoEndpointDslTests.kt
  72. 129 0
      config/src/test/kotlin/org/springframework/security/config/web/servlet/oauth2/resourceserver/JwtDslTests.kt
  73. 148 0
      config/src/test/kotlin/org/springframework/security/config/web/servlet/oauth2/resourceserver/OpaqueTokenDslTests.kt
  74. 176 0
      config/src/test/kotlin/org/springframework/security/config/web/servlet/session/SessionConcurrencyDslTests.kt
  75. 195 0
      config/src/test/kotlin/org/springframework/security/config/web/servlet/session/SessionFixationDslTest.kt
  76. 1 0
      gradle.properties
  77. 4 0
      gradle/dependency-management.gradle
  78. 31 0
      samples/boot/kotlin/spring-security-samples-boot-kotlin.gradle.kts
  79. 29 0
      samples/boot/kotlin/src/main/kotlin/org/springframework/security/samples/KotlinApplication.kt
  80. 55 0
      samples/boot/kotlin/src/main/kotlin/org/springframework/security/samples/config/SecurityConfig.kt
  81. 42 0
      samples/boot/kotlin/src/main/kotlin/org/springframework/security/samples/web/MainController.kt
  82. 6 0
      samples/boot/kotlin/src/main/resources/application.yml
  83. 8 0
      samples/boot/kotlin/src/main/resources/static/css/main.css
  84. 40 0
      samples/boot/kotlin/src/main/resources/templates/index.html
  85. 20 0
      samples/boot/kotlin/src/main/resources/templates/login.html
  86. 29 0
      samples/boot/kotlin/src/main/resources/templates/user/index.html
  87. 85 0
      samples/boot/kotlin/src/test/kotlin/org/springframework/security/samples/KotlinApplicationTests.kt
  88. 8 2
      settings.gradle

+ 3 - 0
build.gradle

@@ -4,15 +4,18 @@ buildscript {
 		classpath "org.springframework.boot:spring-boot-gradle-plugin:$springBootVersion"
 		classpath 'io.spring.nohttp:nohttp-gradle:0.0.2.RELEASE'
 		classpath "io.freefair.gradle:aspectj-plugin:4.0.2"
+		classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlinVersion"
 	}
 	repositories {
 		maven { url 'https://repo.spring.io/plugins-snapshot' }
 		maven { url 'https://plugins.gradle.org/m2/' }
 	}
 }
+
 apply plugin: 'io.spring.nohttp'
 apply plugin: 'locks'
 apply plugin: 'io.spring.convention.root'
+apply plugin: 'org.jetbrains.kotlin.jvm'
 
 group = 'org.springframework.security'
 description = 'Spring Security'

+ 10 - 0
config/spring-security-config.gradle

@@ -1,5 +1,6 @@
 apply plugin: 'io.spring.convention.spring-module'
 apply plugin: 'trang'
+apply plugin: 'kotlin'
 
 dependencies {
 	// NB: Don't add other compile time dependencies to the config module as this breaks tooling
@@ -27,6 +28,8 @@ dependencies {
 	optional'org.springframework:spring-web'
 	optional'org.springframework:spring-webflux'
 	optional'org.springframework:spring-websocket'
+	optional 'org.jetbrains.kotlin:kotlin-reflect'
+	optional 'org.jetbrains.kotlin:kotlin-stdlib-jdk8'
 
 	provided 'javax.servlet:javax.servlet-api'
 
@@ -84,4 +87,11 @@ rncToXsd {
 	xslFile = new File(rncDir, 'spring-security.xsl')
 }
 
+compileKotlin {
+    kotlinOptions {
+        jvmTarget = "1.8"
+        freeCompilerArgs = ["-Xjsr305=strict"]
+    }
+}
+
 build.dependsOn rncToXsd

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

@@ -0,0 +1,42 @@
+/*
+ * 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
+
+import org.springframework.security.web.util.matcher.AnyRequestMatcher
+import org.springframework.security.web.util.matcher.RequestMatcher
+
+abstract class AbstractRequestMatcherDsl {
+
+    /**
+     * Matches any request.
+     */
+    val anyRequest: RequestMatcher = AnyRequestMatcher.INSTANCE
+
+    protected data class MatcherAuthorizationRule(val matcher: RequestMatcher,
+                                                  override val rule: String) : AuthorizationRule(rule)
+
+    protected data class PatternAuthorizationRule(val pattern: String,
+                                                  val patternType: PatternType,
+                                                  val servletPath: String?,
+                                                  override val rule: String) : AuthorizationRule(rule)
+
+    protected abstract class AuthorizationRule(open val rule: String)
+
+    protected enum class PatternType {
+        ANT, MVC
+    }
+}

+ 68 - 0
config/src/main/kotlin/org/springframework/security/config/web/servlet/AnonymousDsl.kt

@@ -0,0 +1,68 @@
+/*
+ * 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
+
+import org.springframework.security.authentication.AuthenticationProvider
+import org.springframework.security.config.annotation.web.builders.HttpSecurity
+import org.springframework.security.config.annotation.web.configurers.AnonymousConfigurer
+import org.springframework.security.core.Authentication
+import org.springframework.security.core.GrantedAuthority
+import org.springframework.security.web.authentication.AnonymousAuthenticationFilter
+
+/**
+ * A Kotlin DSL to configure [HttpSecurity] anonymous authentication using idiomatic
+ * Kotlin code.
+ *
+ * @author Eleftheria Stein
+ * @since 5.3
+ * @property key the key to identify tokens created for anonymous authentication
+ * @property principal the principal for [Authentication] objects of anonymous users
+ * @property authorities the [Authentication.getAuthorities] for anonymous users
+ * @property authenticationProvider the [AuthenticationProvider] used to validate an
+ * anonymous user
+ * @property authenticationFilter the [AnonymousAuthenticationFilter] used to populate
+ * an anonymous user.
+ */
+class AnonymousDsl {
+    var key: String? = null
+    var principal: Any? = null
+    var authorities: List<GrantedAuthority>? = null
+    var authenticationProvider: AuthenticationProvider? = null
+    var authenticationFilter: AnonymousAuthenticationFilter? = null
+
+    private var disabled = false
+
+    /**
+     * Disable anonymous authentication
+     */
+    fun disable() {
+        disabled = true
+    }
+
+    internal fun get(): (AnonymousConfigurer<HttpSecurity>) -> Unit {
+        return { anonymous ->
+            key?.also { anonymous.key(key) }
+            principal?.also { anonymous.principal(principal) }
+            authorities?.also { anonymous.authorities(authorities) }
+            authenticationProvider?.also { anonymous.authenticationProvider(authenticationProvider) }
+            authenticationFilter?.also { anonymous.authenticationFilter(authenticationFilter) }
+            if (disabled) {
+                anonymous.disable()
+            }
+        }
+    }
+}

+ 159 - 0
config/src/main/kotlin/org/springframework/security/config/web/servlet/AuthorizeRequestsDsl.kt

@@ -0,0 +1,159 @@
+/*
+ * 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
+
+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
+import org.springframework.security.web.util.matcher.RequestMatcher
+import org.springframework.util.ClassUtils
+
+/**
+ * A Kotlin DSL to configure [HttpSecurity] request authorization using idiomatic Kotlin code.
+ *
+ * @author Eleftheria Stein
+ * @since 5.3
+ */
+class AuthorizeRequestsDsl : AbstractRequestMatcherDsl() {
+    private val authorizationRules = mutableListOf<AuthorizationRule>()
+
+    private val HANDLER_MAPPING_INTROSPECTOR = "org.springframework.web.servlet.handler.HandlerMappingIntrospector"
+    private val MVC_PRESENT = ClassUtils.isPresent(
+            HANDLER_MAPPING_INTROSPECTOR,
+            AuthorizeRequestsDsl::class.java.classLoader)
+
+    /**
+     * Adds a request authorization rule.
+     *
+     * @param matches the [RequestMatcher] 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(matches: RequestMatcher = AnyRequestMatcher.INSTANCE,
+                  access: String = "authenticated") {
+        authorizationRules.add(MatcherAuthorizationRule(matches, 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 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(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))
+        }
+    }
+
+    /**
+     * 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 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(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))
+        }
+    }
+
+    /**
+     * Specify that URLs require a particular authority.
+     *
+     * @param authority the authority to require (i.e. ROLE_USER, ROLE_ADMIN, etc).
+     * @return the SpEL expression "hasAuthority" with the given authority as a
+     * parameter
+     */
+    fun hasAuthority(authority: String) = "hasAuthority('$authority')"
+
+    /**
+     * Specify that URLs are allowed by anyone.
+     */
+    val permitAll = "permitAll"
+
+    /**
+     * Specify that URLs are allowed by anonymous users.
+     */
+    val anonymous = "anonymous"
+
+    /**
+     * Specify that URLs are allowed by users that have been remembered.
+     */
+    val rememberMe = "rememberMe"
+
+    /**
+     * Specify that URLs are not allowed by anyone.
+     */
+    val denyAll = "denyAll"
+
+    /**
+     * Specify that URLs are allowed by any authenticated user.
+     */
+    val authenticated = "authenticated"
+
+    /**
+     * Specify that URLs are allowed by users who have authenticated and were not
+     * "remembered".
+     */
+    val fullyAuthenticated = "fullyAuthenticated"
+
+    internal fun get(): (ExpressionUrlAuthorizationConfigurer<HttpSecurity>.ExpressionInterceptUrlRegistry) -> Unit {
+        return { requests ->
+            authorizationRules.forEach { rule ->
+                when (rule) {
+                    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)
+                            }
+                        }
+                    }
+                }
+            }
+        }
+    }
+}
+

+ 45 - 0
config/src/main/kotlin/org/springframework/security/config/web/servlet/CorsDsl.kt

@@ -0,0 +1,45 @@
+/*
+ * 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
+
+import org.springframework.security.config.annotation.web.builders.HttpSecurity
+import org.springframework.security.config.annotation.web.configurers.CorsConfigurer
+
+/**
+ * A Kotlin DSL to configure [HttpSecurity] CORS using idiomatic Kotlin code.
+ *
+ * @author Eleftheria Stein
+ * @since 5.3
+ */
+class CorsDsl {
+    private var disabled = false
+
+    /**
+     * Disable CORS.
+     */
+    fun disable() {
+        disabled = true
+    }
+
+    internal fun get(): (CorsConfigurer<HttpSecurity>) -> Unit {
+        return { cors ->
+            if (disabled) {
+                cors.disable()
+            }
+        }
+    }
+}

+ 87 - 0
config/src/main/kotlin/org/springframework/security/config/web/servlet/CsrfDsl.kt

@@ -0,0 +1,87 @@
+/*
+ * 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
+
+import org.springframework.security.config.annotation.web.builders.HttpSecurity
+import org.springframework.security.config.annotation.web.configurers.CsrfConfigurer
+import org.springframework.security.web.authentication.session.SessionAuthenticationStrategy
+import org.springframework.security.web.csrf.CsrfTokenRepository
+import org.springframework.security.web.util.matcher.RequestMatcher
+import javax.servlet.http.HttpServletRequest
+
+/**
+ * A Kotlin DSL to configure [HttpSecurity] CSRF protection
+ * using idiomatic Kotlin code.
+ *
+ * @author Eleftheria Stein
+ * @since 5.3
+ * @property csrfTokenRepository the [CsrfTokenRepository] to use.
+ * @property requireCsrfProtectionMatcher specify the [RequestMatcher] to use for
+ * determining when CSRF should be applied.
+ * @property sessionAuthenticationStrategy the [SessionAuthenticationStrategy] to use.
+ */
+class CsrfDsl {
+    var csrfTokenRepository: CsrfTokenRepository? = null
+    var requireCsrfProtectionMatcher: RequestMatcher? = null
+    var sessionAuthenticationStrategy: SessionAuthenticationStrategy? = null
+
+    private var ignoringAntMatchers: Array<out String>? = null
+    private var ignoringRequestMatchers: Array<out RequestMatcher>? = null
+    private var disabled = false
+
+    /**
+     * Allows specifying [HttpServletRequest]s that should not use CSRF Protection
+     * even if they match the [requireCsrfProtectionMatcher].
+     *
+     * @param antMatchers the ANT pattern matchers that should not use CSRF
+     * protection
+     */
+    fun ignoringAntMatchers(vararg antMatchers: String) {
+        ignoringAntMatchers = antMatchers
+    }
+
+    /**
+     * Allows specifying [HttpServletRequest]s that should not use CSRF Protection
+     * even if they match the [requireCsrfProtectionMatcher].
+     *
+     * @param requestMatchers the request matchers that should not use CSRF
+     * protection
+     */
+    fun ignoringRequestMatchers(vararg requestMatchers: RequestMatcher) {
+        ignoringRequestMatchers = requestMatchers
+    }
+
+    /**
+     * Disable CSRF protection
+     */
+    fun disable() {
+        disabled = true
+    }
+
+    internal fun get(): (CsrfConfigurer<HttpSecurity>) -> Unit {
+        return { csrf ->
+            csrfTokenRepository?.also { csrf.csrfTokenRepository(csrfTokenRepository) }
+            requireCsrfProtectionMatcher?.also { csrf.requireCsrfProtectionMatcher(requireCsrfProtectionMatcher) }
+            sessionAuthenticationStrategy?.also { csrf.sessionAuthenticationStrategy(sessionAuthenticationStrategy) }
+            ignoringAntMatchers?.also { csrf.ignoringAntMatchers(*ignoringAntMatchers!!) }
+            ignoringRequestMatchers?.also { csrf.ignoringRequestMatchers(*ignoringRequestMatchers!!) }
+            if (disabled) {
+                csrf.disable()
+            }
+        }
+    }
+}

+ 92 - 0
config/src/main/kotlin/org/springframework/security/config/web/servlet/ExceptionHandlingDsl.kt

@@ -0,0 +1,92 @@
+/*
+ * 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
+
+import org.springframework.security.config.annotation.web.builders.HttpSecurity
+import org.springframework.security.config.annotation.web.configurers.ExceptionHandlingConfigurer
+import org.springframework.security.web.AuthenticationEntryPoint
+import org.springframework.security.web.access.AccessDeniedHandler
+import org.springframework.security.web.util.matcher.RequestMatcher
+import java.util.*
+
+/**
+ * A Kotlin DSL to configure [HttpSecurity] exception handling using idiomatic Kotlin
+ * code.
+ *
+ * @author Eleftheria Stein
+ * @since 5.3
+ * @property accessDeniedPage the URL to the access denied page
+ * @property accessDeniedHandler the [AccessDeniedHandler] to use
+ * @property authenticationEntryPoint the [AuthenticationEntryPoint] to use
+ */
+class ExceptionHandlingDsl {
+    var accessDeniedPage: String? = null
+    var accessDeniedHandler: AccessDeniedHandler? = null
+    var authenticationEntryPoint: AuthenticationEntryPoint? = null
+
+    private var defaultDeniedHandlerMappings: LinkedHashMap<RequestMatcher, AccessDeniedHandler> = linkedMapOf()
+    private var defaultEntryPointMappings: LinkedHashMap<RequestMatcher, AuthenticationEntryPoint> = linkedMapOf()
+    private var disabled = false
+
+    /**
+     * Sets a default [AccessDeniedHandler] to be used which prefers being
+     * invoked for the provided [RequestMatcher].
+     *
+     * @param deniedHandler the [AccessDeniedHandler] to use
+     * @param preferredMatcher the [RequestMatcher] for this default
+     * [AccessDeniedHandler]
+     */
+    fun defaultAccessDeniedHandlerFor(deniedHandler: AccessDeniedHandler, preferredMatcher: RequestMatcher) {
+        defaultDeniedHandlerMappings[preferredMatcher] = deniedHandler
+    }
+
+    /**
+     * Sets a default [AuthenticationEntryPoint] to be used which prefers being
+     * invoked for the provided [RequestMatcher].
+     *
+     * @param entryPoint the [AuthenticationEntryPoint] to use
+     * @param preferredMatcher the [RequestMatcher] for this default
+     * [AccessDeniedHandler]
+     */
+    fun defaultAuthenticationEntryPointFor(entryPoint: AuthenticationEntryPoint, preferredMatcher: RequestMatcher) {
+        defaultEntryPointMappings[preferredMatcher] = entryPoint
+    }
+
+    /**
+     * Disable exception handling.
+     */
+    fun disable() {
+        disabled = true
+    }
+
+    internal fun get(): (ExceptionHandlingConfigurer<HttpSecurity>) -> Unit {
+        return { exceptionHandling ->
+            accessDeniedPage?.also { exceptionHandling.accessDeniedPage(accessDeniedPage) }
+            accessDeniedHandler?.also { exceptionHandling.accessDeniedHandler(accessDeniedHandler) }
+            authenticationEntryPoint?.also { exceptionHandling.authenticationEntryPoint(authenticationEntryPoint) }
+            defaultDeniedHandlerMappings.forEach { (matcher, handler) ->
+                exceptionHandling.defaultAccessDeniedHandlerFor(handler, matcher)
+            }
+            defaultEntryPointMappings.forEach { (matcher, entryPoint) ->
+                exceptionHandling.defaultAuthenticationEntryPointFor(entryPoint, matcher)
+            }
+            if (disabled) {
+                exceptionHandling.disable()
+            }
+        }
+    }
+}

+ 85 - 0
config/src/main/kotlin/org/springframework/security/config/web/servlet/FormLoginDsl.kt

@@ -0,0 +1,85 @@
+/*
+ * 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
+
+import org.springframework.security.config.annotation.web.HttpSecurityBuilder
+import org.springframework.security.config.annotation.web.builders.HttpSecurity
+import org.springframework.security.config.annotation.web.configurers.FormLoginConfigurer
+import org.springframework.security.web.authentication.AuthenticationFailureHandler
+import org.springframework.security.web.authentication.AuthenticationSuccessHandler
+
+/**
+ * A Kotlin DSL to configure [HttpSecurity] form login using idiomatic Kotlin code.
+ *
+ * @author Eleftheria Stein
+ * @since 5.3
+ * @property loginPage the login page to redirect to if authentication is required (i.e.
+ * "/login")
+ * @property authenticationSuccessHandler the [AuthenticationSuccessHandler] used after
+ * authentication success
+ * @property authenticationFailureHandler the [AuthenticationFailureHandler] used after
+ * authentication success
+ * @property failureUrl the URL to send users if authentication fails
+ * @property loginProcessingUrl the URL to validate the credentials
+ * @property permitAll whether to grant access to the urls for [failureUrl] as well as
+ * for the [HttpSecurityBuilder], the [loginPage] and [loginProcessingUrl] for every user
+ */
+class FormLoginDsl {
+    var loginPage: String? = null
+    var authenticationSuccessHandler: AuthenticationSuccessHandler? = null
+    var authenticationFailureHandler: AuthenticationFailureHandler? = null
+    var failureUrl: String? = null
+    var loginProcessingUrl: String? = null
+    var permitAll: Boolean? = null
+
+    private var defaultSuccessUrlOption: Pair<String, Boolean>? = null
+
+    /**
+     * Grants access to the urls for [failureUrl] as well as for the [HttpSecurityBuilder], the
+     * [loginPage] and [loginProcessingUrl] for every user.
+     */
+    fun permitAll() {
+        permitAll = true
+    }
+
+    /**
+     * Specifies where users will be redirected after authenticating successfully if
+     * they have not visited a secured page prior to authenticating or [alwaysUse]
+     * is true.
+     *
+     * @param defaultSuccessUrl the default success url
+     * @param alwaysUse true if the [defaultSuccessUrl] should be used after
+     * authentication despite if a protected page had been previously visited
+     */
+    fun defaultSuccessUrl(defaultSuccessUrl: String, alwaysUse: Boolean) {
+        defaultSuccessUrlOption = Pair(defaultSuccessUrl, alwaysUse)
+    }
+
+    internal fun get(): (FormLoginConfigurer<HttpSecurity>) -> Unit {
+        return { login ->
+            loginPage?.also { login.loginPage(loginPage) }
+            failureUrl?.also { login.failureUrl(failureUrl) }
+            loginProcessingUrl?.also { login.loginProcessingUrl(loginProcessingUrl) }
+            permitAll?.also { login.permitAll(permitAll!!) }
+            defaultSuccessUrlOption?.also {
+                login.defaultSuccessUrl(defaultSuccessUrlOption!!.first, defaultSuccessUrlOption!!.second)
+            }
+            authenticationSuccessHandler?.also { login.successHandler(authenticationSuccessHandler) }
+            authenticationFailureHandler?.also { login.failureHandler(authenticationFailureHandler) }
+        }
+    }
+}

+ 199 - 0
config/src/main/kotlin/org/springframework/security/config/web/servlet/HeadersDsl.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.servlet
+
+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.headers.*
+import org.springframework.security.web.header.writers.*
+import org.springframework.security.web.header.writers.frameoptions.XFrameOptionsHeaderWriter
+
+/**
+ * A Kotlin DSL to configure [HttpSecurity] headers using idiomatic Kotlin code.
+ *
+ * @author Eleftheria Stein
+ * @since 5.3
+ * @property defaultsDisabled whether all of the default headers should be included in the response
+ */
+class HeadersDsl {
+    private var contentTypeOptions: ((HeadersConfigurer<HttpSecurity>.ContentTypeOptionsConfig) -> Unit)? = null
+    private var xssProtection: ((HeadersConfigurer<HttpSecurity>.XXssConfig) -> Unit)? = null
+    private var cacheControl: ((HeadersConfigurer<HttpSecurity>.CacheControlConfig) -> Unit)? = null
+    private var hsts: ((HeadersConfigurer<HttpSecurity>.HstsConfig) -> Unit)? = null
+    private var frameOptions: ((HeadersConfigurer<HttpSecurity>.FrameOptionsConfig) -> Unit)? = null
+    private var hpkp: ((HeadersConfigurer<HttpSecurity>.HpkpConfig) -> Unit)? = null
+    private var contentSecurityPolicy: ((HeadersConfigurer<HttpSecurity>.ContentSecurityPolicyConfig) -> Unit)? = null
+    private var referrerPolicy: ((HeadersConfigurer<HttpSecurity>.ReferrerPolicyConfig) -> Unit)? = null
+    private var featurePolicyDirectives: String? = null
+
+    var defaultsDisabled: Boolean? = null
+
+    /**
+     * Configures the [XContentTypeOptionsHeaderWriter] which inserts the <a href=
+     * "https://msdn.microsoft.com/en-us/library/ie/gg622941(v=vs.85).aspx"
+     * >X-Content-Type-Options header</a>
+     *
+     * @param contentTypeOptionsConfig the customization to apply to the header
+     */
+    fun contentTypeOptions(contentTypeOptionsConfig: ContentTypeOptionsDsl.() -> Unit) {
+        this.contentTypeOptions = ContentTypeOptionsDsl().apply(contentTypeOptionsConfig).get()
+    }
+
+    /**
+     * <strong>Note this is not comprehensive XSS protection!</strong>
+     *
+     * <p>
+     * Allows customizing the [XXssProtectionHeaderWriter] which adds the <a href=
+     * "https://blogs.msdn.com/b/ieinternals/archive/2011/01/31/controlling-the-internet-explorer-xss-filter-with-the-x-xss-protection-http-header.aspx"
+     * >X-XSS-Protection header</a>
+     * </p>
+     *
+     * @param xssProtectionConfig the customization to apply to the header
+     */
+    fun xssProtection(xssProtectionConfig: XssProtectionConfigDsl.() -> Unit) {
+        this.xssProtection = XssProtectionConfigDsl().apply(xssProtectionConfig).get()
+    }
+
+    /**
+     * Allows customizing the [CacheControlHeadersWriter]. Specifically it adds the
+     * following headers:
+     * <ul>
+     * <li>Cache-Control: no-cache, no-store, max-age=0, must-revalidate</li>
+     * <li>Pragma: no-cache</li>
+     * <li>Expires: 0</li>
+     * </ul>
+     *
+     * @param cacheControlConfig the customization to apply to the header
+     */
+    fun cacheControl(cacheControlConfig: CacheControlDsl.() -> Unit) {
+        this.cacheControl = CacheControlDsl().apply(cacheControlConfig).get()
+    }
+
+    /**
+     * Allows customizing the [HstsHeaderWriter] which provides support for <a
+     * href="https://tools.ietf.org/html/rfc6797">HTTP Strict Transport Security
+     * (HSTS)</a>.
+     *
+     * @param hstsConfig the customization to apply to the header
+     */
+    fun httpStrictTransportSecurity(hstsConfig: HttpStrictTransportSecurityDsl.() -> Unit) {
+        this.hsts = HttpStrictTransportSecurityDsl().apply(hstsConfig).get()
+    }
+
+    /**
+     * Allows customizing the [XFrameOptionsHeaderWriter] which add the X-Frame-Options
+     * header.
+     *
+     * @param frameOptionsConfig the customization to apply to the header
+     */
+    fun frameOptions(frameOptionsConfig: FrameOptionsDsl.() -> Unit) {
+        this.frameOptions = FrameOptionsDsl().apply(frameOptionsConfig).get()
+    }
+
+    /**
+     * Allows customizing the [HpkpHeaderWriter] which provides support for <a
+     * href="https://tools.ietf.org/html/rfc7469">HTTP Public Key Pinning (HPKP)</a>.
+     *
+     * @param hpkpConfig the customization to apply to the header
+     */
+    fun httpPublicKeyPinning(hpkpConfig: HttpPublicKeyPinningDsl.() -> Unit) {
+        this.hpkp = HttpPublicKeyPinningDsl().apply(hpkpConfig).get()
+    }
+
+    /**
+     * Allows configuration for <a href="https://www.w3.org/TR/CSP2/">Content Security Policy (CSP) Level 2</a>.
+     *
+     * <p>
+     * Calling this method automatically enables (includes) the Content-Security-Policy header in the response
+     * using the supplied security policy directive(s).
+     * </p>
+     *
+     * @param contentSecurityPolicyConfig the customization to apply to the header
+     */
+    fun contentSecurityPolicy(contentSecurityPolicyConfig: ContentSecurityPolicyDsl.() -> Unit) {
+        this.contentSecurityPolicy = ContentSecurityPolicyDsl().apply(contentSecurityPolicyConfig).get()
+    }
+
+    /**
+     * Allows configuration for <a href="https://www.w3.org/TR/referrer-policy/">Referrer Policy</a>.
+     *
+     * <p>
+     * Configuration is provided to the [ReferrerPolicyHeaderWriter] which support the writing
+     * of the header as detailed in the W3C Technical Report:
+     * </p>
+     * <ul>
+     *  <li>Referrer-Policy</li>
+     * </ul>
+     *
+     * @param referrerPolicyConfig the customization to apply to the header
+     */
+    fun referrerPolicy(referrerPolicyConfig: ReferrerPolicyDsl.() -> Unit) {
+        this.referrerPolicy = ReferrerPolicyDsl().apply(referrerPolicyConfig).get()
+    }
+
+    /**
+     * Allows configuration for <a href="https://wicg.github.io/feature-policy/">Feature
+     * Policy</a>.
+     *
+     * <p>
+     * Calling this method automatically enables (includes) the Feature-Policy
+     * header in the response using the supplied policy directive(s).
+     * <p>
+     *
+     * @param policyDirectives policyDirectives the security policy directive(s)
+     */
+    fun featurePolicy(policyDirectives: String) {
+        this.featurePolicyDirectives = policyDirectives
+    }
+
+    internal fun get(): (HeadersConfigurer<HttpSecurity>) -> Unit {
+        return { headers ->
+            defaultsDisabled?.also {
+                if (defaultsDisabled!!) {
+                    headers.defaultsDisabled()
+                }
+            }
+            contentTypeOptions?.also {
+                headers.contentTypeOptions(contentTypeOptions)
+            }
+            xssProtection?.also {
+                headers.xssProtection(xssProtection)
+            }
+            cacheControl?.also {
+                headers.cacheControl(cacheControl)
+            }
+            hsts?.also {
+                headers.httpStrictTransportSecurity(hsts)
+            }
+            frameOptions?.also {
+                headers.frameOptions(frameOptions)
+            }
+            hpkp?.also {
+                headers.httpPublicKeyPinning(hpkp)
+            }
+            contentSecurityPolicy?.also {
+                headers.contentSecurityPolicy(contentSecurityPolicy)
+            }
+            referrerPolicy?.also {
+                headers.referrerPolicy(referrerPolicy)
+            }
+            featurePolicyDirectives?.also {
+                headers.featurePolicy(featurePolicyDirectives)
+            }
+        }
+    }
+}

+ 62 - 0
config/src/main/kotlin/org/springframework/security/config/web/servlet/HttpBasicDsl.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.servlet
+
+import org.springframework.security.authentication.AuthenticationDetailsSource
+import org.springframework.security.config.annotation.web.builders.HttpSecurity
+import org.springframework.security.config.annotation.web.configurers.HttpBasicConfigurer
+import org.springframework.security.web.AuthenticationEntryPoint
+import org.springframework.security.web.authentication.www.BasicAuthenticationFilter
+import javax.servlet.http.HttpServletRequest
+
+/**
+ * A Kotlin DSL to configure [HttpSecurity] basic authentication using idiomatic Kotlin code.
+ *
+ * @author Eleftheria Stein
+ * @since 5.3
+ * @property realmName the HTTP Basic realm to use. If [authenticationEntryPoint]
+ * has been invoked, invoking this method will result in an error.
+ * @property authenticationEntryPoint the [AuthenticationEntryPoint] to be populated on
+ * [BasicAuthenticationFilter] in the event that authentication fails.
+ * @property authenticationDetailsSource the custom [AuthenticationDetailsSource] to use for
+ * basic authentication.
+ */
+class HttpBasicDsl {
+    var realmName: String? = null
+    var authenticationEntryPoint: AuthenticationEntryPoint? = null
+    var authenticationDetailsSource: AuthenticationDetailsSource<HttpServletRequest, *>? = null
+
+    private var disabled = false
+
+    /**
+     * Disables HTTP basic authentication
+     */
+    fun disable() {
+        disabled = true
+    }
+
+    internal fun get(): (HttpBasicConfigurer<HttpSecurity>) -> Unit {
+        return { httpBasic ->
+            realmName?.also { httpBasic.realmName(realmName) }
+            authenticationEntryPoint?.also { httpBasic.authenticationEntryPoint(authenticationEntryPoint) }
+            authenticationDetailsSource?.also { httpBasic.authenticationDetailsSource(authenticationDetailsSource) }
+            if (disabled) {
+                httpBasic.disable()
+            }
+        }
+    }
+}

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

@@ -0,0 +1,651 @@
+/*
+ * 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
+
+import org.springframework.context.ApplicationContext
+import org.springframework.security.config.annotation.web.builders.HttpSecurity
+import org.springframework.security.oauth2.client.registration.ClientRegistrationRepository
+import org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistrationRepository
+import org.springframework.security.web.util.matcher.RequestMatcher
+import org.springframework.util.ClassUtils
+import javax.servlet.http.HttpServletRequest
+
+/**
+ * Configures [HttpSecurity] using a [HttpSecurity Kotlin DSL][HttpSecurityDsl].
+ *
+ * Example:
+ *
+ * ```
+ * @EnableWebSecurity
+ * class SecurityConfig : WebSecurityConfigurerAdapter() {
+ *
+ *  override fun configure(http: HttpSecurity) {
+ *      http {
+ *          authorizeRequests {
+ *              request("/public", permitAll)
+ *              request(anyRequest, authenticated)
+ *          }
+ *          formLogin {
+ *              loginPage = "/log-in"
+ *          }
+ *      }
+ *  }
+ * }
+ * ```
+ *
+ * @author Eleftheria Stein
+ * @since 5.3
+ * @param httpConfiguration the configurations to apply to [HttpSecurity]
+ */
+operator fun HttpSecurity.invoke(httpConfiguration: HttpSecurityDsl.() -> Unit) =
+        HttpSecurityDsl(this, httpConfiguration).build()
+
+/**
+ * An [HttpSecurity] Kotlin DSL created by [`http { }`][invoke]
+ * in order to configure [HttpSecurity] using idiomatic Kotlin code.
+ *
+ * @author Eleftheria Stein
+ * @since 5.3
+ * @param http the [HttpSecurity] which all configurations will be applied to
+ * @param init the configurations to apply to the provided [HttpSecurity]
+ */
+class HttpSecurityDsl(private val http: HttpSecurity, private val init: HttpSecurityDsl.() -> Unit) {
+    private val HANDLER_MAPPING_INTROSPECTOR = "org.springframework.web.servlet.handler.HandlerMappingIntrospector"
+
+    /**
+     * Allows configuring the [HttpSecurity] to only be invoked when 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.
+     *
+     * Example:
+     *
+     * ```
+     * @EnableWebSecurity
+     * class SecurityConfig : WebSecurityConfigurerAdapter() {
+     *
+     *  override fun configure(http: HttpSecurity) {
+     *      http {
+     *          securityMatcher("/private/&ast;&ast;")
+     *          formLogin {
+     *              loginPage = "/log-in"
+     *          }
+     *      }
+     *  }
+     * }
+     * ```
+     *
+     * @param pattern one or more patterns used to determine whether this
+     * configuration should be invoked.
+     */
+    fun securityMatcher(vararg pattern: String) {
+        val mvcPresent = ClassUtils.isPresent(
+                HANDLER_MAPPING_INTROSPECTOR,
+                AuthorizeRequestsDsl::class.java.classLoader)
+        this.http.requestMatchers {
+            if (mvcPresent) {
+                it.mvcMatchers(*pattern)
+            } else {
+                it.antMatchers(*pattern)
+            }
+        }
+    }
+
+    /**
+     * Allows configuring the [HttpSecurity] to only be invoked when matching the
+     * provided [RequestMatcher].
+     *
+     * Example:
+     *
+     * ```
+     * @EnableWebSecurity
+     * class SecurityConfig : WebSecurityConfigurerAdapter() {
+     *
+     *  override fun configure(http: HttpSecurity) {
+     *      http {
+     *          securityMatcher(AntPathRequestMatcher("/private/&ast;&ast;"))
+     *          formLogin {
+     *              loginPage = "/log-in"
+     *          }
+     *      }
+     *  }
+     * }
+     * ```
+     *
+     * @param requestMatcher one or more [RequestMatcher] used to determine whether
+     * this configuration should be invoked.
+     */
+    fun securityMatcher(vararg requestMatcher: RequestMatcher) {
+        this.http.requestMatchers {
+            it.requestMatchers(*requestMatcher)
+        }
+    }
+
+    /**
+     * Enables form based authentication.
+     *
+     * Example:
+     *
+     * ```
+     * @EnableWebSecurity
+     * class SecurityConfig : WebSecurityConfigurerAdapter() {
+     *
+     *  override fun configure(http: HttpSecurity) {
+     *      http {
+     *          formLogin {
+     *              loginPage = "/log-in"
+     *          }
+     *      }
+     *  }
+     * }
+     * ```
+     *
+     * @param formLoginConfiguration custom configurations to be applied
+     * to the form based authentication
+     * @see [FormLoginDsl]
+     */
+    fun formLogin(formLoginConfiguration: FormLoginDsl.() -> Unit) {
+        val loginCustomizer = FormLoginDsl().apply(formLoginConfiguration).get()
+        this.http.formLogin(loginCustomizer)
+    }
+
+    /**
+     * Allows restricting access based upon the [HttpServletRequest]
+     *
+     * Example:
+     *
+     * ```
+     * @EnableWebSecurity
+     * class SecurityConfig : WebSecurityConfigurerAdapter() {
+     *
+     *  override fun configure(http: HttpSecurity) {
+     *      http {
+     *          authorizeRequests {
+     *              request("/public", permitAll)
+     *              request(anyRequest, authenticated)
+     *          }
+     *      }
+     *  }
+     * }
+     * ```
+     *
+     * @param authorizeRequestsConfiguration custom configuration that specifies
+     * access for requests
+     * @see [AuthorizeRequestsDsl]
+     */
+    fun authorizeRequests(authorizeRequestsConfiguration: AuthorizeRequestsDsl.() -> Unit) {
+        val authorizeRequestsCustomizer = AuthorizeRequestsDsl().apply(authorizeRequestsConfiguration).get()
+        this.http.authorizeRequests(authorizeRequestsCustomizer)
+    }
+
+    /**
+     * Enables HTTP basic authentication.
+     *
+     * Example:
+     *
+     * ```
+     * @EnableWebSecurity
+     * class SecurityConfig : WebSecurityConfigurerAdapter() {
+     *
+     *  override fun configure(http: HttpSecurity) {
+     *      http {
+     *          httpBasic {
+     *              realmName = "Custom Realm"
+     *          }
+     *      }
+     *  }
+     * }
+     * ```
+     *
+     * @param httpBasicConfiguration custom configurations to be applied to the
+     * HTTP basic authentication
+     * @see [HttpBasicDsl]
+     */
+    fun httpBasic(httpBasicConfiguration: HttpBasicDsl.() -> Unit) {
+        val httpBasicCustomizer = HttpBasicDsl().apply(httpBasicConfiguration).get()
+        this.http.httpBasic(httpBasicCustomizer)
+    }
+
+    /**
+     * Allows configuring response headers.
+     *
+     * Example:
+     *
+     * ```
+     * @EnableWebSecurity
+     * class SecurityConfig : WebSecurityConfigurerAdapter() {
+     *
+     *  override fun configure(http: HttpSecurity) {
+     *      http {
+     *          headers {
+     *              referrerPolicy {
+     *                  policy = ReferrerPolicy.SAME_ORIGIN
+     *              }
+     *          }
+     *      }
+     *  }
+     * }
+     * ```
+     *
+     * @param headersConfiguration custom configurations to configure the
+     * response headers
+     * @see [HeadersDsl]
+     */
+    fun headers(headersConfiguration: HeadersDsl.() -> Unit) {
+        val headersCustomizer = HeadersDsl().apply(headersConfiguration).get()
+        this.http.headers(headersCustomizer)
+    }
+
+    /**
+     * Enables CORS.
+     *
+     * Example:
+     *
+     * ```
+     * @EnableWebSecurity
+     * class SecurityConfig : WebSecurityConfigurerAdapter() {
+     *
+     *  override fun configure(http: HttpSecurity) {
+     *      http {
+     *          cors {
+     *              disable()
+     *          }
+     *      }
+     *  }
+     * }
+     * ```
+     *
+     * @param corsConfiguration custom configurations to configure the
+     * response headers
+     * @see [CorsDsl]
+     */
+    fun cors(corsConfiguration: CorsDsl.() -> Unit) {
+        val corsCustomizer = CorsDsl().apply(corsConfiguration).get()
+        this.http.cors(corsCustomizer)
+    }
+
+    /**
+     * Allows configuring session management.
+     *
+     * Example:
+     *
+     * ```
+     * @EnableWebSecurity
+     * class SecurityConfig : WebSecurityConfigurerAdapter() {
+     *
+     *  override fun configure(http: HttpSecurity) {
+     *      http {
+     *          sessionManagement {
+     *              invalidSessionUrl = "/invalid-session"
+     *              sessionConcurrency {
+     *                  maximumSessions = 1
+     *              }
+     *          }
+     *      }
+     *  }
+     * }
+     * ```
+     *
+     * @param sessionManagementConfiguration custom configurations to configure
+     * session management
+     * @see [SessionManagementDsl]
+     */
+    fun sessionManagement(sessionManagementConfiguration: SessionManagementDsl.() -> Unit) {
+        val sessionManagementCustomizer = SessionManagementDsl().apply(sessionManagementConfiguration).get()
+        this.http.sessionManagement(sessionManagementCustomizer)
+    }
+
+    /**
+     * Allows configuring a port mapper.
+     *
+     * Example:
+     *
+     * ```
+     * @EnableWebSecurity
+     * class SecurityConfig : WebSecurityConfigurerAdapter() {
+     *
+     *  override fun configure(http: HttpSecurity) {
+     *      http {
+     *          portMapper {
+     *              map(80, 443)
+     *          }
+     *      }
+     *  }
+     * }
+     * ```
+     *
+     * @param portMapperConfiguration custom configurations to configure
+     * the port mapper
+     * @see [PortMapperDsl]
+     */
+    fun portMapper(portMapperConfiguration: PortMapperDsl.() -> Unit) {
+        val portMapperCustomizer = PortMapperDsl().apply(portMapperConfiguration).get()
+        this.http.portMapper(portMapperCustomizer)
+    }
+
+    /**
+     * Allows configuring channel security based upon the [HttpServletRequest]
+     *
+     * Example:
+     *
+     * ```
+     * @EnableWebSecurity
+     * class SecurityConfig : WebSecurityConfigurerAdapter() {
+     *
+     *  override fun configure(http: HttpSecurity) {
+     *      http {
+     *          requiresChannel {
+     *              secure("/public", requiresInsecure)
+     *              secure(anyRequest, requiresSecure)
+     *          }
+     *      }
+     *  }
+     * }
+     * ```
+     *
+     * @param requiresChannelConfiguration custom configuration that specifies
+     * channel security
+     * @see [RequiresChannelDsl]
+     */
+    fun requiresChannel(requiresChannelConfiguration: RequiresChannelDsl.() -> Unit) {
+        val requiresChannelCustomizer = RequiresChannelDsl().apply(requiresChannelConfiguration).get()
+        this.http.requiresChannel(requiresChannelCustomizer)
+    }
+
+    /**
+     * Adds X509 based pre authentication to an application
+     *
+     * Example:
+     *
+     * ```
+     * @EnableWebSecurity
+     * class SecurityConfig : WebSecurityConfigurerAdapter() {
+     *
+     *  override fun configure(http: HttpSecurity) {
+     *      http {
+     *          x509 { }
+     *      }
+     *  }
+     * }
+     * ```
+     *
+     * @param x509Configuration custom configuration to apply to the
+     * X509 based pre authentication
+     * @see [X509Dsl]
+     */
+    fun x509(x509Configuration: X509Dsl.() -> Unit) {
+        val x509Customizer = X509Dsl().apply(x509Configuration).get()
+        this.http.x509(x509Customizer)
+    }
+
+    /**
+     * Enables request caching. Specifically this ensures that requests that
+     * are saved (i.e. after authentication is required) are later replayed.
+     *
+     * Example:
+     *
+     * ```
+     * @EnableWebSecurity
+     * class SecurityConfig : WebSecurityConfigurerAdapter() {
+     *
+     *  override fun configure(http: HttpSecurity) {
+     *      http {
+     *          requestCache { }
+     *      }
+     *  }
+     * }
+     * ```
+     *
+     * @param requestCacheConfiguration custom configuration to apply to the
+     * request cache
+     * @see [RequestCacheDsl]
+     */
+    fun requestCache(requestCacheConfiguration: RequestCacheDsl.() -> Unit) {
+        val requestCacheCustomizer = RequestCacheDsl().apply(requestCacheConfiguration).get()
+        this.http.requestCache(requestCacheCustomizer)
+    }
+
+    /**
+     * Allows configuring exception handling.
+     *
+     * Example:
+     *
+     * ```
+     * @EnableWebSecurity
+     * class SecurityConfig : WebSecurityConfigurerAdapter() {
+     *
+     *  override fun configure(http: HttpSecurity) {
+     *      http {
+     *          exceptionHandling {
+     *              accessDeniedPage = "/access-denied"
+     *          }
+     *      }
+     *  }
+     * }
+     * ```
+     *
+     * @param exceptionHandlingConfiguration custom configuration to apply to the
+     * exception handling
+     * @see [ExceptionHandlingDsl]
+     */
+    fun exceptionHandling(exceptionHandlingConfiguration: ExceptionHandlingDsl.() -> Unit) {
+        val exceptionHandlingCustomizer = ExceptionHandlingDsl().apply(exceptionHandlingConfiguration).get()
+        this.http.exceptionHandling(exceptionHandlingCustomizer)
+    }
+
+    /**
+     * Enables CSRF protection.
+     *
+     * Example:
+     *
+     * ```
+     * @EnableWebSecurity
+     * class SecurityConfig : WebSecurityConfigurerAdapter() {
+     *
+     *  override fun configure(http: HttpSecurity) {
+     *      http {
+     *          csrf { }
+     *      }
+     *  }
+     * }
+     * ```
+     *
+     * @param csrfConfiguration custom configuration to apply to CSRF
+     * @see [CsrfDsl]
+     */
+    fun csrf(csrfConfiguration: CsrfDsl.() -> Unit) {
+        val csrfCustomizer = CsrfDsl().apply(csrfConfiguration).get()
+        this.http.csrf(csrfCustomizer)
+    }
+
+    /**
+     * Provides logout support.
+     *
+     * Example:
+     *
+     * ```
+     * @EnableWebSecurity
+     * class SecurityConfig : WebSecurityConfigurerAdapter() {
+     *
+     *  override fun configure(http: HttpSecurity) {
+     *      http {
+     *          logout {
+     *              logoutUrl = "/log-out"
+     *          }
+     *      }
+     *  }
+     * }
+     * ```
+     *
+     * @param logoutConfiguration custom configuration to apply to logout
+     * @see [LogoutDsl]
+     */
+    fun logout(logoutConfiguration: LogoutDsl.() -> Unit) {
+        val logoutCustomizer = LogoutDsl().apply(logoutConfiguration).get()
+        this.http.logout(logoutCustomizer)
+    }
+
+    /**
+     * Configures authentication support using a SAML 2.0 Service Provider.
+     * A [RelyingPartyRegistrationRepository] is required and must be registered with
+     * the [ApplicationContext] or configured via
+     * [Saml2Dsl.relyingPartyRegistrationRepository]
+     *
+     * Example:
+     *
+     * ```
+     * @EnableWebSecurity
+     * class SecurityConfig : WebSecurityConfigurerAdapter() {
+     *
+     *  override fun configure(http: HttpSecurity) {
+     *      http {
+     *          saml2Login {
+     *              relyingPartyRegistration = getSaml2RelyingPartyRegistration()
+     *          }
+     *      }
+     *  }
+     * }
+     * ```
+     *
+     * @param saml2LoginConfiguration custom configuration to configure the
+     * SAML2 service provider
+     * @see [Saml2Dsl]
+     */
+    fun saml2Login(saml2LoginConfiguration: Saml2Dsl.() -> Unit) {
+        val saml2LoginCustomizer = Saml2Dsl().apply(saml2LoginConfiguration).get()
+        this.http.saml2Login(saml2LoginCustomizer)
+    }
+
+    /**
+     * Allows configuring how an anonymous user is represented.
+     *
+     * Example:
+     *
+     * ```
+     * @EnableWebSecurity
+     * class SecurityConfig : WebSecurityConfigurerAdapter() {
+     *
+     *  override fun configure(http: HttpSecurity) {
+     *      http {
+     *          anonymous {
+     *              authorities = listOf(SimpleGrantedAuthority("ROLE_ANON"))
+     *          }
+     *      }
+     *  }
+     * }
+     * ```
+     *
+     * @param anonymousConfiguration custom configuration to configure the
+     * anonymous user
+     * @see [AnonymousDsl]
+     */
+    fun anonymous(anonymousConfiguration: AnonymousDsl.() -> Unit) {
+        val anonymousCustomizer = AnonymousDsl().apply(anonymousConfiguration).get()
+        this.http.anonymous(anonymousCustomizer)
+    }
+
+    /**
+     * Configures authentication support using an OAuth 2.0 and/or OpenID Connect 1.0 Provider.
+     * A [ClientRegistrationRepository] is required and must be registered as a Bean or
+     * configured via [OAuth2LoginDsl.clientRegistrationRepository]
+     *
+     * Example:
+     *
+     * ```
+     * @EnableWebSecurity
+     * class SecurityConfig : WebSecurityConfigurerAdapter() {
+     *
+     *  override fun configure(http: HttpSecurity) {
+     *      http {
+     *          oauth2Login {
+     *              clientRegistrationRepository = getClientRegistrationRepository()
+     *          }
+     *      }
+     *  }
+     * }
+     * ```
+     *
+     * @param oauth2LoginConfiguration custom configuration to configure the
+     * OAuth 2.0 Login
+     * @see [OAuth2LoginDsl]
+     */
+    fun oauth2Login(oauth2LoginConfiguration: OAuth2LoginDsl.() -> Unit) {
+        val oauth2LoginCustomizer = OAuth2LoginDsl().apply(oauth2LoginConfiguration).get()
+        this.http.oauth2Login(oauth2LoginCustomizer)
+    }
+
+    /**
+     * Configures OAuth 2.0 client support.
+     *
+     * Example:
+     *
+     * ```
+     * @EnableWebSecurity
+     * class SecurityConfig : WebSecurityConfigurerAdapter() {
+     *
+     *  override fun configure(http: HttpSecurity) {
+     *      http {
+     *          oauth2Client { }
+     *      }
+     *  }
+     * }
+     * ```
+     *
+     * @param oauth2ClientConfiguration custom configuration to configure the
+     * OAuth 2.0 client support
+     * @see [OAuth2ClientDsl]
+     */
+    fun oauth2Client(oauth2ClientConfiguration: OAuth2ClientDsl.() -> Unit) {
+        val oauth2ClientCustomizer = OAuth2ClientDsl().apply(oauth2ClientConfiguration).get()
+        this.http.oauth2Client(oauth2ClientCustomizer)
+    }
+
+    /**
+     * Configures OAuth 2.0 resource server support.
+     *
+     * Example:
+     *
+     * ```
+     * @EnableWebSecurity
+     * class SecurityConfig : WebSecurityConfigurerAdapter() {
+     *
+     *  override fun configure(http: HttpSecurity) {
+     *      http {
+     *          oauth2ResourceServer {
+     *              jwt { }
+     *          }
+     *      }
+     *  }
+     * }
+     * ```
+     *
+     * @param oauth2ResourceServerConfiguration custom configuration to configure the
+     * OAuth 2.0 resource server support
+     * @see [OAuth2ResourceServerDsl]
+     */
+    fun oauth2ResourceServer(oauth2ResourceServerConfiguration: OAuth2ResourceServerDsl.() -> Unit) {
+        val oauth2ResourceServerCustomizer = OAuth2ResourceServerDsl().apply(oauth2ResourceServerConfiguration).get()
+        this.http.oauth2ResourceServer(oauth2ResourceServerCustomizer)
+    }
+
+    /**
+     * Apply all configurations to the provided [HttpSecurity]
+     */
+    internal fun build() {
+        init()
+    }
+}

+ 126 - 0
config/src/main/kotlin/org/springframework/security/config/web/servlet/LogoutDsl.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.servlet
+
+import org.springframework.security.config.annotation.web.builders.HttpSecurity
+import org.springframework.security.config.annotation.web.configurers.LogoutConfigurer
+import org.springframework.security.core.Authentication
+import org.springframework.security.web.access.AccessDeniedHandler
+import org.springframework.security.web.authentication.logout.LogoutHandler
+import org.springframework.security.web.authentication.logout.LogoutSuccessHandler
+import org.springframework.security.web.authentication.logout.SecurityContextLogoutHandler
+import org.springframework.security.web.util.matcher.RequestMatcher
+import java.util.*
+import javax.servlet.http.HttpSession
+
+/**
+ * A Kotlin DSL to configure [HttpSecurity] logout support
+ * using idiomatic Kotlin code.
+ *
+ * @author Eleftheria Stein
+ * @since 5.3
+ * @property clearAuthentication whether the [SecurityContextLogoutHandler] should clear
+ * the [Authentication] at the time of logout.
+ * @property clearAuthentication whether to invalidate the [HttpSession] at the time of logout.
+ * @property logoutUrl the URL that triggers log out to occur.
+ * @property logoutRequestMatcher the [RequestMatcher] that triggers log out to occur.
+ * @property logoutSuccessUrl the URL to redirect to after logout has occurred.
+ * @property logoutSuccessHandler the [LogoutSuccessHandler] to use after logout has occurred.
+ * If this is specified, [logoutSuccessUrl] is ignored.
+ */
+class LogoutDsl {
+    var clearAuthentication: Boolean? = null
+    var invalidateHttpSession: Boolean? = null
+    var logoutUrl: String? = null
+    var logoutRequestMatcher: RequestMatcher? = null
+    var logoutSuccessUrl: String? = null
+    var logoutSuccessHandler: LogoutSuccessHandler? = null
+    var permitAll: Boolean? = null
+
+    private var logoutHandlers = mutableListOf<LogoutHandler>()
+    private var deleteCookies: Array<out String>? = null
+    private var defaultLogoutSuccessHandlerMappings: LinkedHashMap<RequestMatcher, LogoutSuccessHandler> = linkedMapOf()
+    private var disabled = false
+
+
+    /**
+     * Adds a [LogoutHandler]. The [SecurityContextLogoutHandler] is added as
+     * the last [LogoutHandler] by default.
+     *
+     * @param logoutHandler the [LogoutHandler] to add
+     */
+    fun addLogoutHandler(logoutHandler: LogoutHandler) {
+        this.logoutHandlers.add(logoutHandler)
+    }
+
+    /**
+     * Allows specifying the names of cookies to be removed on logout success.
+     *
+     * @param cookieNamesToClear the names of cookies to be removed on logout success.
+     */
+    fun deleteCookies(vararg cookieNamesToClear: String) {
+        this.deleteCookies = cookieNamesToClear
+    }
+
+    /**
+     * Sets a default [LogoutSuccessHandler] to be used which prefers being
+     * invoked for the provided [RequestMatcher].
+     *
+     * @param logoutHandler the [LogoutSuccessHandler] to use
+     * @param preferredMatcher the [RequestMatcher] for this default
+     * [AccessDeniedHandler]
+     */
+    fun defaultLogoutSuccessHandlerFor(logoutHandler: LogoutSuccessHandler, preferredMatcher: RequestMatcher) {
+        defaultLogoutSuccessHandlerMappings[preferredMatcher] = logoutHandler
+    }
+
+    /**
+     * Disables logout
+     */
+    fun disable() {
+        disabled = true
+    }
+
+    /**
+     * Grants access to the [logoutSuccessUrl] and the [logoutUrl] for every user.
+     */
+    fun permitAll() {
+        permitAll = true
+    }
+
+    internal fun get(): (LogoutConfigurer<HttpSecurity>) -> Unit {
+        return { logout ->
+            clearAuthentication?.also { logout.clearAuthentication(clearAuthentication!!) }
+            invalidateHttpSession?.also { logout.invalidateHttpSession(invalidateHttpSession!!) }
+            logoutUrl?.also { logout.logoutUrl(logoutUrl) }
+            logoutRequestMatcher?.also { logout.logoutRequestMatcher(logoutRequestMatcher) }
+            logoutSuccessUrl?.also { logout.logoutSuccessUrl(logoutSuccessUrl) }
+            logoutSuccessHandler?.also { logout.logoutSuccessHandler(logoutSuccessHandler) }
+            deleteCookies?.also { logout.deleteCookies(*deleteCookies!!) }
+            permitAll?.also { logout.permitAll(permitAll!!) }
+            defaultLogoutSuccessHandlerMappings.forEach { (matcher, handler) ->
+                logout.defaultLogoutSuccessHandlerFor(handler, matcher)
+            }
+            logoutHandlers.forEach { logoutHandler ->
+                logout.addLogoutHandler(logoutHandler)
+            }
+            if (disabled) {
+                logout.disable()
+            }
+        }
+    }
+}

+ 97 - 0
config/src/main/kotlin/org/springframework/security/config/web/servlet/OAuth2ClientDsl.kt

@@ -0,0 +1,97 @@
+/*
+ * 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
+
+/*
+ * 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.
+ */
+
+import org.springframework.security.config.annotation.web.builders.HttpSecurity
+import org.springframework.security.config.web.servlet.oauth2.client.AuthorizationCodeGrantDsl
+import org.springframework.security.config.web.servlet.oauth2.login.AuthorizationEndpointDsl
+import org.springframework.security.config.annotation.web.configurers.oauth2.client.OAuth2ClientConfigurer
+import org.springframework.security.oauth2.client.OAuth2AuthorizedClientService
+import org.springframework.security.oauth2.client.registration.ClientRegistrationRepository
+import org.springframework.security.oauth2.client.web.OAuth2AuthorizedClientRepository
+
+/**
+ * A Kotlin DSL to configure [HttpSecurity] OAuth 2.0 client support using idiomatic
+ * Kotlin code.
+ *
+ * @author Eleftheria Stein
+ * @since 5.3
+ * @property clientRegistrationRepository the repository of client registrations.
+ * @property authorizedClientRepository the repository for authorized client(s).
+ * @property authorizedClientService the service for authorized client(s).
+ */
+class OAuth2ClientDsl {
+    var clientRegistrationRepository: ClientRegistrationRepository? = null
+    var authorizedClientRepository: OAuth2AuthorizedClientRepository? = null
+    var authorizedClientService: OAuth2AuthorizedClientService? = null
+
+    private var authorizationCodeGrant: ((OAuth2ClientConfigurer<HttpSecurity>.AuthorizationCodeGrantConfigurer) -> Unit)? = null
+
+    /**
+     * Configures the OAuth 2.0 Authorization Code Grant.
+     *
+     * Example:
+     *
+     * ```
+     * @EnableWebSecurity
+     * class SecurityConfig : WebSecurityConfigurerAdapter() {
+     *
+     *  override fun configure(http: HttpSecurity) {
+     *      httpSecurity(http) {
+     *          oauth2Client {
+     *              authorizationCodeGrant {
+     *                  authorizationRequestResolver = getAuthorizationRequestResolver()
+     *              }
+     *          }
+     *      }
+     *  }
+     * }
+     * ```
+     *
+     * @param authorizationCodeGrantConfig custom configurations to configure the authorization
+     * code grant
+     * @see [AuthorizationEndpointDsl]
+     */
+    fun authorizationCodeGrant(authorizationCodeGrantConfig: AuthorizationCodeGrantDsl.() -> Unit) {
+        this.authorizationCodeGrant = AuthorizationCodeGrantDsl().apply(authorizationCodeGrantConfig).get()
+    }
+
+    internal fun get(): (OAuth2ClientConfigurer<HttpSecurity>) -> Unit {
+        return { oauth2Client ->
+            clientRegistrationRepository?.also { oauth2Client.clientRegistrationRepository(clientRegistrationRepository) }
+            authorizedClientRepository?.also { oauth2Client.authorizedClientRepository(authorizedClientRepository) }
+            authorizedClientService?.also { oauth2Client.authorizedClientService(authorizedClientService) }
+            authorizationCodeGrant?.also { oauth2Client.authorizationCodeGrant(authorizationCodeGrant) }
+        }
+    }
+}

+ 225 - 0
config/src/main/kotlin/org/springframework/security/config/web/servlet/OAuth2LoginDsl.kt

@@ -0,0 +1,225 @@
+/*
+ * 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
+
+import org.springframework.security.config.annotation.web.HttpSecurityBuilder
+import org.springframework.security.config.annotation.web.builders.HttpSecurity
+import org.springframework.security.config.web.servlet.oauth2.login.AuthorizationEndpointDsl
+import org.springframework.security.config.web.servlet.oauth2.login.RedirectionEndpointDsl
+import org.springframework.security.config.web.servlet.oauth2.login.TokenEndpointDsl
+import org.springframework.security.config.web.servlet.oauth2.login.UserInfoEndpointDsl
+import org.springframework.security.config.annotation.web.configurers.oauth2.client.OAuth2LoginConfigurer
+import org.springframework.security.oauth2.client.OAuth2AuthorizedClientService
+import org.springframework.security.oauth2.client.registration.ClientRegistrationRepository
+import org.springframework.security.oauth2.client.web.OAuth2AuthorizedClientRepository
+import org.springframework.security.web.authentication.AuthenticationFailureHandler
+import org.springframework.security.web.authentication.AuthenticationSuccessHandler
+
+/**
+ * A Kotlin DSL to configure [HttpSecurity] OAuth 2.0 login using idiomatic Kotlin code.
+ *
+ * @author Eleftheria Stein
+ * @since 5.3
+ * @property clientRegistrationRepository the repository of client registrations.
+ * @property authorizedClientRepository the repository for authorized client(s).
+ * @property authorizedClientService the service for authorized client(s).
+ * @property loginPage the login page to redirect to if authentication is required (i.e.
+ * "/login")
+ * @property authenticationSuccessHandler the [AuthenticationSuccessHandler] used after
+ * authentication success
+ * @property authenticationFailureHandler the [AuthenticationFailureHandler] used after
+ * authentication success
+ * @property failureUrl the URL to send users if authentication fails
+ * @property loginProcessingUrl the URL to validate the credentials
+ * @property permitAll whether to grant access to the urls for [failureUrl] as well as
+ * for the [HttpSecurityBuilder], the [loginPage] and [loginProcessingUrl] for every user
+ */
+class OAuth2LoginDsl {
+    var clientRegistrationRepository: ClientRegistrationRepository? = null
+    var authorizedClientRepository: OAuth2AuthorizedClientRepository? = null
+    var authorizedClientService: OAuth2AuthorizedClientService? = null
+    var loginPage: String? = null
+    var authenticationSuccessHandler: AuthenticationSuccessHandler? = null
+    var authenticationFailureHandler: AuthenticationFailureHandler? = null
+    var failureUrl: String? = null
+    var loginProcessingUrl: String? = null
+    var permitAll: Boolean? = null
+
+    private var defaultSuccessUrlOption: Pair<String, Boolean>? = null
+    private var authorizationEndpoint: ((OAuth2LoginConfigurer<HttpSecurity>.AuthorizationEndpointConfig) -> Unit)? = null
+    private var tokenEndpoint: ((OAuth2LoginConfigurer<HttpSecurity>.TokenEndpointConfig) -> Unit)? = null
+    private var redirectionEndpoint: ((OAuth2LoginConfigurer<HttpSecurity>.RedirectionEndpointConfig) -> Unit)? = null
+    private var userInfoEndpoint: ((OAuth2LoginConfigurer<HttpSecurity>.UserInfoEndpointConfig) -> Unit)? = null
+
+    /**
+     * Grants access to the urls for [failureUrl] as well as for the [HttpSecurityBuilder], the
+     * [loginPage] and [loginProcessingUrl] for every user.
+     */
+    fun permitAll() {
+        permitAll = true
+    }
+
+    /**
+     * Specifies where users will be redirected after authenticating successfully if
+     * they have not visited a secured page prior to authenticating or [alwaysUse]
+     * is true.
+     *
+     * @param defaultSuccessUrl the default success url
+     * @param alwaysUse true if the [defaultSuccessUrl] should be used after
+     * authentication despite if a protected page had been previously visited
+     */
+    fun defaultSuccessUrl(defaultSuccessUrl: String, alwaysUse: Boolean) {
+        defaultSuccessUrlOption = Pair(defaultSuccessUrl, alwaysUse)
+    }
+
+    /**
+     * Configures the Authorization Server's Authorization Endpoint.
+     *
+     * Example:
+     *
+     * ```
+     * @EnableWebSecurity
+     * class SecurityConfig : WebSecurityConfigurerAdapter() {
+     *
+     *  override fun configure(http: HttpSecurity) {
+     *      httpSecurity(http) {
+     *          oauth2Login {
+     *              authorizationEndpoint {
+     *                  baseUri = "/auth"
+     *              }
+     *          }
+     *      }
+     *  }
+     * }
+     * ```
+     *
+     * @param authorizationEndpointConfig custom configurations to configure the authorization
+     * endpoint
+     * @see [AuthorizationEndpointDsl]
+     */
+    fun authorizationEndpoint(authorizationEndpointConfig: AuthorizationEndpointDsl.() -> Unit) {
+        this.authorizationEndpoint = AuthorizationEndpointDsl().apply(authorizationEndpointConfig).get()
+    }
+
+    /**
+     * Configures the Authorization Server's Token Endpoint.
+     *
+     * Example:
+     *
+     * ```
+     * @EnableWebSecurity
+     * class SecurityConfig : WebSecurityConfigurerAdapter() {
+     *
+     *  override fun configure(http: HttpSecurity) {
+     *      httpSecurity(http) {
+     *          oauth2Login {
+     *              tokenEndpoint {
+     *                  accessTokenResponseClient = getAccessTokenResponseClient()
+     *              }
+     *          }
+     *      }
+     *  }
+     * }
+     * ```
+     *
+     * @param tokenEndpointConfig custom configurations to configure the token
+     * endpoint
+     * @see [TokenEndpointDsl]
+     */
+    fun tokenEndpoint(tokenEndpointConfig: TokenEndpointDsl.() -> Unit) {
+        this.tokenEndpoint = TokenEndpointDsl().apply(tokenEndpointConfig).get()
+    }
+
+    /**
+     * Configures the Authorization Server's Redirection Endpoint.
+     *
+     * Example:
+     *
+     * ```
+     * @EnableWebSecurity
+     * class SecurityConfig : WebSecurityConfigurerAdapter() {
+     *
+     *  override fun configure(http: HttpSecurity) {
+     *      httpSecurity(http) {
+     *          oauth2Login {
+     *              redirectionEndpoint {
+     *                  baseUri = "/home"
+     *              }
+     *          }
+     *      }
+     *  }
+     * }
+     * ```
+     *
+     * @param redirectionEndpointConfig custom configurations to configure the redirection
+     * endpoint
+     * @see [RedirectionEndpointDsl]
+     */
+    fun redirectionEndpoint(redirectionEndpointConfig: RedirectionEndpointDsl.() -> Unit) {
+        this.redirectionEndpoint = RedirectionEndpointDsl().apply(redirectionEndpointConfig).get()
+    }
+
+    /**
+     * Configures the Authorization Server's UserInfo Endpoint.
+     *
+     * Example:
+     *
+     * ```
+     * @EnableWebSecurity
+     * class SecurityConfig : WebSecurityConfigurerAdapter() {
+     *
+     *  override fun configure(http: HttpSecurity) {
+     *      httpSecurity(http) {
+     *          oauth2Login {
+     *              userInfoEndpoint {
+     *                  userService = getUserService()
+     *              }
+     *          }
+     *      }
+     *  }
+     * }
+     * ```
+     *
+     * @param userInfoEndpointConfig custom configurations to configure the user info
+     * endpoint
+     * @see [UserInfoEndpointDsl]
+     */
+    fun userInfoEndpoint(userInfoEndpointConfig: UserInfoEndpointDsl.() -> Unit) {
+        this.userInfoEndpoint = UserInfoEndpointDsl().apply(userInfoEndpointConfig).get()
+    }
+
+    internal fun get(): (OAuth2LoginConfigurer<HttpSecurity>) -> Unit {
+        return { oauth2Login ->
+            clientRegistrationRepository?.also { oauth2Login.clientRegistrationRepository(clientRegistrationRepository) }
+            authorizedClientRepository?.also { oauth2Login.authorizedClientRepository(authorizedClientRepository) }
+            authorizedClientService?.also { oauth2Login.authorizedClientService(authorizedClientService) }
+            loginPage?.also { oauth2Login.loginPage(loginPage) }
+            failureUrl?.also { oauth2Login.failureUrl(failureUrl) }
+            loginProcessingUrl?.also { oauth2Login.loginProcessingUrl(loginProcessingUrl) }
+            permitAll?.also { oauth2Login.permitAll(permitAll!!) }
+            defaultSuccessUrlOption?.also {
+                oauth2Login.defaultSuccessUrl(defaultSuccessUrlOption!!.first, defaultSuccessUrlOption!!.second)
+            }
+            authenticationSuccessHandler?.also { oauth2Login.successHandler(authenticationSuccessHandler) }
+            authenticationFailureHandler?.also { oauth2Login.failureHandler(authenticationFailureHandler) }
+            authorizationEndpoint?.also { oauth2Login.authorizationEndpoint(authorizationEndpoint) }
+            tokenEndpoint?.also { oauth2Login.tokenEndpoint(tokenEndpoint) }
+            redirectionEndpoint?.also { oauth2Login.redirectionEndpoint(redirectionEndpoint) }
+            userInfoEndpoint?.also { oauth2Login.userInfoEndpoint(userInfoEndpoint) }
+        }
+    }
+}

+ 111 - 0
config/src/main/kotlin/org/springframework/security/config/web/servlet/OAuth2ResourceServerDsl.kt

@@ -0,0 +1,111 @@
+/*
+ * 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
+
+import org.springframework.security.config.annotation.web.builders.HttpSecurity
+import org.springframework.security.config.web.servlet.oauth2.resourceserver.JwtDsl
+import org.springframework.security.config.web.servlet.oauth2.resourceserver.OpaqueTokenDsl
+import org.springframework.security.config.annotation.web.configurers.oauth2.server.resource.OAuth2ResourceServerConfigurer
+import org.springframework.security.oauth2.server.resource.web.BearerTokenResolver
+import org.springframework.security.web.AuthenticationEntryPoint
+import org.springframework.security.web.access.AccessDeniedHandler
+
+/**
+ * A Kotlin DSL to configure [HttpSecurity] OAuth 2.0 resource server support using
+ * idiomatic Kotlin code.
+ *
+ * @author Eleftheria Stein
+ * @since 5.3
+ * @property accessDeniedHandler the [AccessDeniedHandler] to use for requests authenticating
+ * with <a href="https://tools.ietf.org/html/rfc6750#section-1.2" target="_blank">Bearer Token</a>s.
+ * @property authenticationEntryPoint the [AuthenticationEntryPoint] to use for requests authenticating
+ * with <a href="https://tools.ietf.org/html/rfc6750#section-1.2" target="_blank">Bearer Token</a>s.
+ * @property bearerTokenResolver the [BearerTokenResolver] to use for requests authenticating
+ * with <a href="https://tools.ietf.org/html/rfc6750#section-1.2" target="_blank">Bearer Token</a>s.
+ */
+class OAuth2ResourceServerDsl {
+    var accessDeniedHandler: AccessDeniedHandler? = null
+    var authenticationEntryPoint: AuthenticationEntryPoint? = null
+    var bearerTokenResolver: BearerTokenResolver? = null
+
+    private var jwt: ((OAuth2ResourceServerConfigurer<HttpSecurity>.JwtConfigurer) -> Unit)? = null
+    private var opaqueToken: ((OAuth2ResourceServerConfigurer<HttpSecurity>.OpaqueTokenConfigurer) -> Unit)? = null
+
+    /**
+     * Enables JWT-encoded bearer token support.
+     *
+     * Example:
+     *
+     * ```
+     * @EnableWebSecurity
+     * class SecurityConfig : WebSecurityConfigurerAdapter() {
+     *
+     *  override fun configure(http: HttpSecurity) {
+     *      httpSecurity(http) {
+     *          oauth2ResourceServer {
+     *              jwt {
+     *                  jwkSetUri = "https://example.com/oauth2/jwk"
+     *              }
+     *          }
+     *      }
+     *  }
+     * }
+     * ```
+     *
+     * @param jwtConfig custom configurations to configure JWT resource server support
+     * @see [JwtDsl]
+     */
+    fun jwt(jwtConfig: JwtDsl.() -> Unit) {
+        this.jwt = JwtDsl().apply(jwtConfig).get()
+    }
+
+    /**
+     * Enables opaque token support.
+     *
+     * Example:
+     *
+     * ```
+     * @EnableWebSecurity
+     * class SecurityConfig : WebSecurityConfigurerAdapter() {
+     *
+     *  override fun configure(http: HttpSecurity) {
+     *      httpSecurity(http) {
+     *          oauth2ResourceServer {
+     *              opaqueToken { }
+     *          }
+     *      }
+     *  }
+     * }
+     * ```
+     *
+     * @param opaqueTokenConfig custom configurations to configure opaque token resource server support
+     * @see [OpaqueTokenDsl]
+     */
+    fun opaqueToken(opaqueTokenConfig: OpaqueTokenDsl.() -> Unit) {
+        this.opaqueToken = OpaqueTokenDsl().apply(opaqueTokenConfig).get()
+    }
+
+    internal fun get(): (OAuth2ResourceServerConfigurer<HttpSecurity>) -> Unit {
+        return { oauth2ResourceServer ->
+            accessDeniedHandler?.also { oauth2ResourceServer.accessDeniedHandler(accessDeniedHandler) }
+            authenticationEntryPoint?.also { oauth2ResourceServer.authenticationEntryPoint(authenticationEntryPoint) }
+            bearerTokenResolver?.also { oauth2ResourceServer.bearerTokenResolver(bearerTokenResolver) }
+            jwt?.also { oauth2ResourceServer.jwt(jwt) }
+            opaqueToken?.also { oauth2ResourceServer.opaqueToken(opaqueToken) }
+        }
+    }
+}

+ 56 - 0
config/src/main/kotlin/org/springframework/security/config/web/servlet/PortMapperDsl.kt

@@ -0,0 +1,56 @@
+/*
+ * 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
+
+import org.springframework.security.config.annotation.web.builders.HttpSecurity
+import org.springframework.security.config.annotation.web.configurers.PortMapperConfigurer
+import org.springframework.security.web.PortMapper
+
+/**
+ * A Kotlin DSL to configure a [PortMapper] for [HttpSecurity] using idiomatic
+ * Kotlin code.
+ *
+ * @author Eleftheria Stein
+ * @since 5.3
+ * @property portMapper allows specifying the [PortMapper] instance.
+ */
+class PortMapperDsl {
+    private val mappings = mutableListOf<Pair<Int, Int>>()
+
+    var portMapper: PortMapper? = null
+
+    /**
+     * Adds a mapping to the port mapper.
+     *
+     * @param fromPort the HTTP port number to map from
+     * @param toPort the HTTPS port number to map to
+     */
+    fun map(fromPort: Int, toPort: Int) {
+        mappings.add(Pair(fromPort, toPort))
+    }
+
+    internal fun get(): (PortMapperConfigurer<HttpSecurity>) -> Unit {
+        return { portMapperConfig ->
+            portMapper?.also {
+                portMapperConfig.portMapper(portMapper)
+            }
+            this.mappings.forEach {
+                portMapperConfig.http(it.first).mapsTo(it.second)
+            }
+        }
+    }
+}

+ 41 - 0
config/src/main/kotlin/org/springframework/security/config/web/servlet/RequestCacheDsl.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.servlet
+
+import org.springframework.security.config.annotation.web.builders.HttpSecurity
+import org.springframework.security.config.annotation.web.configurers.RequestCacheConfigurer
+import org.springframework.security.web.savedrequest.RequestCache
+
+/**
+ * A Kotlin DSL to enable request caching for [HttpSecurity] using idiomatic
+ * Kotlin code.
+ *
+ * @author Eleftheria Stein
+ * @since 5.3
+ * @property requestCache allows explicit configuration of the [RequestCache] to be used
+ */
+class RequestCacheDsl {
+    var requestCache: RequestCache? = null
+
+    internal fun get(): (RequestCacheConfigurer<HttpSecurity>) -> Unit {
+        return { requestCacheConfig ->
+            requestCache?.also {
+                requestCacheConfig.requestCache(requestCache)
+            }
+        }
+    }
+}

+ 136 - 0
config/src/main/kotlin/org/springframework/security/config/web/servlet/RequiresChannelDsl.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.servlet
+
+import org.springframework.security.config.annotation.web.builders.HttpSecurity
+import org.springframework.security.config.annotation.web.configurers.ChannelSecurityConfigurer
+import org.springframework.security.web.access.channel.ChannelDecisionManagerImpl
+import org.springframework.security.web.access.channel.ChannelProcessor
+import org.springframework.security.web.util.matcher.AnyRequestMatcher
+import org.springframework.security.web.util.matcher.RequestMatcher
+import org.springframework.util.ClassUtils
+
+/**
+ * A Kotlin DSL to configure [HttpSecurity] channel security using idiomatic
+ * Kotlin code.
+ *
+ * @author Eleftheria Stein
+ * @since 5.3
+ * @property channelProcessors the [ChannelProcessor] instances to use in
+ * [ChannelDecisionManagerImpl]
+ */
+class RequiresChannelDsl : AbstractRequestMatcherDsl() {
+    private val channelSecurityRules = mutableListOf<AuthorizationRule>()
+
+    private val HANDLER_MAPPING_INTROSPECTOR = "org.springframework.web.servlet.handler.HandlerMappingIntrospector"
+    private val MVC_PRESENT = ClassUtils.isPresent(
+            HANDLER_MAPPING_INTROSPECTOR,
+            RequiresChannelDsl::class.java.classLoader)
+
+    var channelProcessors: List<ChannelProcessor>? = null
+
+    /**
+     * Adds a channel security rule.
+     *
+     * @param matches the [RequestMatcher] to match incoming requests against
+     * @param attribute the configuration attribute to secure the matching request
+     * (i.e. "REQUIRES_SECURE_CHANNEL")
+     */
+    fun secure(matches: RequestMatcher = AnyRequestMatcher.INSTANCE,
+               attribute: String = "REQUIRES_SECURE_CHANNEL") {
+        channelSecurityRules.add(MatcherAuthorizationRule(matches, attribute))
+    }
+
+    /**
+     * Adds a request authorization rule for an endpoint matching the provided
+     * pattern.
+     * If Spring MVC is not an the classpath, it will use an ant matcher.
+     * If Spring MVC is on the classpath, it will use an MVC 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 pattern the pattern to match incoming requests against.
+     * @param attribute the configuration attribute to secure the matching request
+     * (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))
+        }
+    }
+
+    /**
+     * Adds a request authorization rule for an endpoint matching the provided
+     * pattern.
+     * If Spring MVC is not an the classpath, it will use an ant matcher.
+     * If Spring MVC is on the classpath, it will use an MVC 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 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 attribute the configuration attribute to secure the matching request
+     * (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))
+        }
+    }
+
+    /**
+     * Specify channel security is active.
+     */
+    val requiresSecure = "REQUIRES_SECURE_CHANNEL"
+
+    /**
+     * Specify channel security is inactive.
+     */
+    val requiresInsecure = "REQUIRES_INSECURE_CHANNEL"
+
+    internal fun get(): (ChannelSecurityConfigurer<HttpSecurity>.ChannelRequestMatcherRegistry) -> Unit {
+        return { channelSecurity ->
+            channelProcessors?.also { channelSecurity.channelProcessors(channelProcessors) }
+            channelSecurityRules.forEach { rule ->
+                when (rule) {
+                    is MatcherAuthorizationRule -> channelSecurity.requestMatchers(rule.matcher).requires(rule.rule)
+                    is PatternAuthorizationRule -> {
+                        when (rule.patternType) {
+                            PatternType.ANT -> channelSecurity.antMatchers(rule.pattern).requires(rule.rule)
+                            PatternType.MVC -> {
+                                val mvcMatchersRequiresChannel = channelSecurity.mvcMatchers(rule.pattern)
+                                rule.servletPath?.also { mvcMatchersRequiresChannel.servletPath(rule.servletPath) }
+                                mvcMatchersRequiresChannel.requires(rule.rule)
+                            }
+                        }
+                    }
+                }
+            }
+        }
+    }
+}

+ 91 - 0
config/src/main/kotlin/org/springframework/security/config/web/servlet/Saml2Dsl.kt

@@ -0,0 +1,91 @@
+/*
+ * 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
+
+import org.springframework.security.config.annotation.web.HttpSecurityBuilder
+import org.springframework.security.config.annotation.web.builders.HttpSecurity
+import org.springframework.security.config.annotation.web.configurers.saml2.Saml2LoginConfigurer
+import org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistrationRepository
+import org.springframework.security.web.authentication.AuthenticationFailureHandler
+import org.springframework.security.web.authentication.AuthenticationSuccessHandler
+
+/**
+ * A Kotlin DSL to configure [HttpSecurity] SAML2 login using idiomatic Kotlin code.
+ *
+ * @author Eleftheria Stein
+ * @since 5.3
+ * @property relyingPartyRegistrationRepository the [RelyingPartyRegistrationRepository] of relying parties,
+ * each party representing a service provider, SP and this host, and identity provider, IDP pair that
+ * communicate with each other.
+ * @property loginPage the login page to redirect to if authentication is required (i.e.
+ * "/login")
+ * @property authenticationSuccessHandler the [AuthenticationSuccessHandler] used after
+ * authentication success
+ * @property authenticationFailureHandler the [AuthenticationFailureHandler] used after
+ * authentication success
+ * @property failureUrl the URL to send users if authentication fails
+ * @property loginProcessingUrl the URL to validate the credentials
+ * @property permitAll whether to grant access to the urls for [failureUrl] as well as
+ * for the [HttpSecurityBuilder], the [loginPage] and [loginProcessingUrl] for every user
+ */
+class Saml2Dsl {
+    var relyingPartyRegistrationRepository: RelyingPartyRegistrationRepository? = null
+    var loginPage: String? = null
+    var authenticationSuccessHandler: AuthenticationSuccessHandler? = null
+    var authenticationFailureHandler: AuthenticationFailureHandler? = null
+    var failureUrl: String? = null
+    var loginProcessingUrl: String? = null
+    var permitAll: Boolean? = null
+
+    private var defaultSuccessUrlOption: Pair<String, Boolean>? = null
+
+    /**
+     * Grants access to the urls for [failureUrl] as well as for the [HttpSecurityBuilder], the
+     * [loginPage] and [loginProcessingUrl] for every user.
+     */
+    fun permitAll() {
+        permitAll = true
+    }
+
+    /**
+     * Specifies where users will be redirected after authenticating successfully if
+     * they have not visited a secured page prior to authenticating or [alwaysUse]
+     * is true.
+     *
+     * @param defaultSuccessUrl the default success url
+     * @param alwaysUse true if the [defaultSuccessUrl] should be used after
+     * authentication despite if a protected page had been previously visited
+     */
+    fun defaultSuccessUrl(defaultSuccessUrl: String, alwaysUse: Boolean) {
+        defaultSuccessUrlOption = Pair(defaultSuccessUrl, alwaysUse)
+    }
+
+    internal fun get(): (Saml2LoginConfigurer<HttpSecurity>) -> Unit {
+        return { saml2Login ->
+            relyingPartyRegistrationRepository?.also { saml2Login.relyingPartyRegistrationRepository(relyingPartyRegistrationRepository) }
+            loginPage?.also { saml2Login.loginPage(loginPage) }
+            failureUrl?.also { saml2Login.failureUrl(failureUrl) }
+            loginProcessingUrl?.also { saml2Login.loginProcessingUrl(loginProcessingUrl) }
+            permitAll?.also { saml2Login.permitAll(permitAll!!) }
+            defaultSuccessUrlOption?.also {
+                saml2Login.defaultSuccessUrl(defaultSuccessUrlOption!!.first, defaultSuccessUrlOption!!.second)
+            }
+            authenticationSuccessHandler?.also { saml2Login.successHandler(authenticationSuccessHandler) }
+            authenticationFailureHandler?.also { saml2Login.failureHandler(authenticationFailureHandler) }
+        }
+    }
+}

+ 117 - 0
config/src/main/kotlin/org/springframework/security/config/web/servlet/SessionManagementDsl.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.servlet
+
+import org.springframework.security.config.annotation.web.builders.HttpSecurity
+import org.springframework.security.config.web.servlet.session.SessionConcurrencyDsl
+import org.springframework.security.config.web.servlet.session.SessionFixationDsl
+import org.springframework.security.config.annotation.web.configurers.SessionManagementConfigurer
+import org.springframework.security.config.http.SessionCreationPolicy
+import org.springframework.security.web.authentication.AuthenticationFailureHandler
+import org.springframework.security.web.authentication.session.SessionAuthenticationStrategy
+import org.springframework.security.web.session.InvalidSessionStrategy
+
+/**
+ * A Kotlin DSL to configure [HttpSecurity] session management using idiomatic
+ * Kotlin code.
+ *
+ * @author Eleftheria Stein
+ * @since 5.3
+ */
+class SessionManagementDsl {
+    var invalidSessionUrl: String? = null
+    var invalidSessionStrategy: InvalidSessionStrategy? = null
+    var sessionAuthenticationErrorUrl: String? = null
+    var sessionAuthenticationFailureHandler: AuthenticationFailureHandler? = null
+    var enableSessionUrlRewriting: Boolean? = null
+    var sessionCreationPolicy: SessionCreationPolicy? = null
+    var sessionAuthenticationStrategy: SessionAuthenticationStrategy? = null
+    private var sessionFixation: ((SessionManagementConfigurer<HttpSecurity>.SessionFixationConfigurer) -> Unit)? = null
+    private var sessionConcurrency: ((SessionManagementConfigurer<HttpSecurity>.ConcurrencyControlConfigurer) -> Unit)? = null
+
+    /**
+     * Enables session fixation protection.
+     *
+     * Example:
+     *
+     * ```
+     * @EnableWebSecurity
+     * class SecurityConfig : WebSecurityConfigurerAdapter() {
+     *
+     *  override fun configure(http: HttpSecurity) {
+     *      httpSecurity(http) {
+     *          sessionManagement {
+     *              sessionFixation { }
+     *          }
+     *      }
+     *  }
+     * }
+     * ```
+     *
+     * @param sessionFixationConfig custom configurations to configure session fixation
+     * protection
+     * @see [SessionFixationDsl]
+     */
+    fun sessionFixation(sessionFixationConfig: SessionFixationDsl.() -> Unit) {
+        this.sessionFixation = SessionFixationDsl().apply(sessionFixationConfig).get()
+    }
+
+    /**
+     * Controls the behaviour of multiple sessions for a user.
+     *
+     * Example:
+     *
+     * ```
+     * @EnableWebSecurity
+     * class SecurityConfig : WebSecurityConfigurerAdapter() {
+     *
+     *  override fun configure(http: HttpSecurity) {
+     *      httpSecurity(http) {
+     *          sessionManagement {
+     *              sessionConcurrency {
+     *                  maximumSessions = 1
+     *                  maxSessionsPreventsLogin = true
+     *              }
+     *          }
+     *      }
+     *  }
+     * }
+     * ```
+     *
+     * @param sessionConcurrencyConfig custom configurations to configure concurrency
+     * control
+     * @see [SessionConcurrencyDsl]
+     */
+    fun sessionConcurrency(sessionConcurrencyConfig: SessionConcurrencyDsl.() -> Unit) {
+        this.sessionConcurrency = SessionConcurrencyDsl().apply(sessionConcurrencyConfig).get()
+    }
+
+    internal fun get(): (SessionManagementConfigurer<HttpSecurity>) -> Unit {
+        return { sessionManagement ->
+            invalidSessionUrl?.also { sessionManagement.invalidSessionUrl(invalidSessionUrl) }
+            invalidSessionStrategy?.also { sessionManagement.invalidSessionStrategy(invalidSessionStrategy) }
+            sessionAuthenticationErrorUrl?.also { sessionManagement.sessionAuthenticationErrorUrl(sessionAuthenticationErrorUrl) }
+            sessionAuthenticationFailureHandler?.also { sessionManagement.sessionAuthenticationFailureHandler(sessionAuthenticationFailureHandler) }
+            enableSessionUrlRewriting?.also { sessionManagement.enableSessionUrlRewriting(enableSessionUrlRewriting!!) }
+            sessionCreationPolicy?.also { sessionManagement.sessionCreationPolicy(sessionCreationPolicy) }
+            sessionAuthenticationStrategy?.also { sessionManagement.sessionAuthenticationStrategy(sessionAuthenticationStrategy) }
+            sessionFixation?.also { sessionManagement.sessionFixation(sessionFixation) }
+            sessionConcurrency?.also { sessionManagement.sessionConcurrency(sessionConcurrency) }
+        }
+    }
+}
+

+ 65 - 0
config/src/main/kotlin/org/springframework/security/config/web/servlet/X509Dsl.kt

@@ -0,0 +1,65 @@
+/*
+ * 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
+
+import org.springframework.security.authentication.AuthenticationDetailsSource
+import org.springframework.security.config.annotation.web.builders.HttpSecurity
+import org.springframework.security.config.annotation.web.configurers.X509Configurer
+import org.springframework.security.core.userdetails.AuthenticationUserDetailsService
+import org.springframework.security.core.userdetails.UserDetailsByNameServiceWrapper
+import org.springframework.security.core.userdetails.UserDetailsService
+import org.springframework.security.web.authentication.preauth.PreAuthenticatedAuthenticationToken
+import org.springframework.security.web.authentication.preauth.PreAuthenticatedGrantedAuthoritiesWebAuthenticationDetails
+import org.springframework.security.web.authentication.preauth.x509.X509AuthenticationFilter
+import org.springframework.security.web.authentication.preauth.x509.X509PrincipalExtractor
+import javax.servlet.http.HttpServletRequest
+
+/**
+ * A Kotlin DSL to configure [HttpSecurity] X509 based pre authentication
+ * using idiomatic Kotlin code.
+ *
+ * @author Eleftheria Stein
+ * @since 5.3
+ * @property x509AuthenticationFilter the entire [X509AuthenticationFilter]. If
+ * this is specified, the properties on [X509Configurer] will not be populated
+ * on the {@link X509AuthenticationFilter}.
+ * @property x509PrincipalExtractor the [X509PrincipalExtractor]
+ * @property authenticationDetailsSource the [X509PrincipalExtractor]
+ * @property userDetailsService shortcut for invoking
+ * [authenticationUserDetailsService] with a [UserDetailsByNameServiceWrapper]
+ * @property authenticationUserDetailsService the [AuthenticationUserDetailsService] to use
+ * @property subjectPrincipalRegex the regex to extract the principal from the certificate
+ */
+class X509Dsl {
+    var x509AuthenticationFilter: X509AuthenticationFilter? = null
+    var x509PrincipalExtractor: X509PrincipalExtractor? = null
+    var authenticationDetailsSource: AuthenticationDetailsSource<HttpServletRequest, PreAuthenticatedGrantedAuthoritiesWebAuthenticationDetails>? = null
+    var userDetailsService: UserDetailsService? = null
+    var authenticationUserDetailsService: AuthenticationUserDetailsService<PreAuthenticatedAuthenticationToken>? = null
+    var subjectPrincipalRegex: String? = null
+
+    internal fun get(): (X509Configurer<HttpSecurity>) -> Unit {
+        return { x509 ->
+            x509AuthenticationFilter?.also { x509.x509AuthenticationFilter(x509AuthenticationFilter) }
+            x509PrincipalExtractor?.also { x509.x509PrincipalExtractor(x509PrincipalExtractor) }
+            authenticationDetailsSource?.also { x509.authenticationDetailsSource(authenticationDetailsSource) }
+            userDetailsService?.also { x509.userDetailsService(userDetailsService) }
+            authenticationUserDetailsService?.also { x509.authenticationUserDetailsService(authenticationUserDetailsService) }
+            subjectPrincipalRegex?.also { x509.subjectPrincipalRegex(subjectPrincipalRegex) }
+        }
+    }
+}

+ 46 - 0
config/src/main/kotlin/org/springframework/security/config/web/servlet/headers/CacheControlDsl.kt

@@ -0,0 +1,46 @@
+/*
+ * 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
+
+import org.springframework.security.config.annotation.web.builders.HttpSecurity
+import org.springframework.security.config.annotation.web.configurers.HeadersConfigurer
+
+/**
+ * A Kotlin DSL to configure the [HttpSecurity] cache control headers using idiomatic
+ * Kotlin code.
+ *
+ * @author Eleftheria Stein
+ * @since 5.3
+ */
+class CacheControlDsl {
+    private var disabled = false
+
+    /**
+     * Disable cache control headers.
+     */
+    fun disable() {
+        disabled = true
+    }
+
+    internal fun get(): (HeadersConfigurer<HttpSecurity>.CacheControlConfig) -> Unit {
+        return { cacheControl ->
+            if (disabled) {
+                cacheControl.disable()
+            }
+        }
+    }
+}

+ 47 - 0
config/src/main/kotlin/org/springframework/security/config/web/servlet/headers/ContentSecurityPolicyDsl.kt

@@ -0,0 +1,47 @@
+/*
+ * 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
+
+import org.springframework.security.config.annotation.web.builders.HttpSecurity
+import org.springframework.security.config.annotation.web.configurers.HeadersConfigurer
+
+/**
+ * A Kotlin DSL to configure the [HttpSecurity] Content-Security-Policy header using
+ * idiomatic Kotlin code.
+ *
+ * @author Eleftheria Stein
+ * @since 5.3
+ * @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.
+ */
+class ContentSecurityPolicyDsl {
+    var policyDirectives: String? = null
+    var reportOnly: Boolean? = null
+
+    internal fun get(): (HeadersConfigurer<HttpSecurity>.ContentSecurityPolicyConfig) -> Unit {
+        return { contentSecurityPolicy ->
+            policyDirectives?.also {
+                contentSecurityPolicy.policyDirectives(policyDirectives)
+            }
+            reportOnly?.also {
+                if (reportOnly!!) {
+                    contentSecurityPolicy.reportOnly()
+                }
+            }
+        }
+    }
+}

+ 46 - 0
config/src/main/kotlin/org/springframework/security/config/web/servlet/headers/ContentTypeOptionsDsl.kt

@@ -0,0 +1,46 @@
+/*
+ * 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
+
+import org.springframework.security.config.annotation.web.builders.HttpSecurity
+import org.springframework.security.config.annotation.web.configurers.HeadersConfigurer
+
+/**
+ * A Kotlin DSL to configure [HttpSecurity] X-Content-Type-Options header using idiomatic
+ * Kotlin code.
+ *
+ * @author Eleftheria Stein
+ * @since 5.3
+ */
+class ContentTypeOptionsDsl {
+    private var disabled = false
+
+    /**
+     * Disable the X-Content-Type-Options header.
+     */
+    fun disable() {
+        disabled = true
+    }
+
+    internal fun get(): (HeadersConfigurer<HttpSecurity>.ContentTypeOptionsConfig) -> Unit {
+        return { contentTypeOptions ->
+            if (disabled) {
+                contentTypeOptions.disable()
+            }
+        }
+    }
+}

+ 62 - 0
config/src/main/kotlin/org/springframework/security/config/web/servlet/headers/FrameOptionsDsl.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.servlet.headers
+
+import org.springframework.security.config.annotation.web.builders.HttpSecurity
+import org.springframework.security.config.annotation.web.configurers.HeadersConfigurer
+
+/**
+ * A Kotlin DSL to configure the [HttpSecurity] X-Frame-Options header using
+ * idiomatic Kotlin code.
+ *
+ * @author Eleftheria Stein
+ * @since 5.3
+ * @property sameOrigin allow any request that comes from the same origin to frame this
+ * application.
+ * @property deny deny framing any content from this application.
+ */
+class FrameOptionsDsl {
+    var sameOrigin: Boolean? = null
+    var deny: Boolean? = null
+
+    private var disabled = false
+
+    /**
+     * Disable the X-Frame-Options header.
+     */
+    fun disable() {
+        disabled = true
+    }
+
+    internal fun get(): (HeadersConfigurer<HttpSecurity>.FrameOptionsConfig) -> Unit {
+        return { frameOptions ->
+            sameOrigin?.also {
+                if (sameOrigin!!) {
+                    frameOptions.sameOrigin()
+                }
+            }
+            deny?.also {
+                if (deny!!) {
+                    frameOptions.deny()
+                }
+            }
+            if (disabled) {
+                frameOptions.disable()
+            }
+        }
+    }
+}

+ 75 - 0
config/src/main/kotlin/org/springframework/security/config/web/servlet/headers/HttpPublicKeyPinningDsl.kt

@@ -0,0 +1,75 @@
+/*
+ * 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
+
+import org.springframework.security.config.annotation.web.builders.HttpSecurity
+import org.springframework.security.config.annotation.web.configurers.HeadersConfigurer
+
+/**
+ * A Kotlin DSL to configure the [HttpSecurity] HTTP Public Key Pinning header using
+ * idiomatic Kotlin code.
+ *
+ * @author Eleftheria Stein
+ * @since 5.3
+ * @property pins the value for the pin- directive of the Public-Key-Pins header.
+ * @property maxAgeInSeconds the value (in seconds) for the max-age directive of the
+ * Public-Key-Pins header.
+ * @property includeSubDomains if true, the pinning policy applies to this pinned host
+ * as well as any subdomains of the host's domain name.
+ * @property reportOnly if true, the browser should not terminate the connection with
+ * the server.
+ * @property reportUri the URI to which the browser should report pin validation failures.
+ */
+class HttpPublicKeyPinningDsl {
+    var pins: Map<String, String>? = null
+    var maxAgeInSeconds: Long? = null
+    var includeSubDomains: Boolean? = null
+    var reportOnly: Boolean? = null
+    var reportUri: String? = null
+
+    private var disabled = false
+
+    /**
+     * Disable the HTTP Public Key Pinning header.
+     */
+    fun disable() {
+        disabled = true
+    }
+
+    internal fun get(): (HeadersConfigurer<HttpSecurity>.HpkpConfig) -> Unit {
+        return { hpkp ->
+            pins?.also {
+                hpkp.withPins(pins)
+            }
+            maxAgeInSeconds?.also {
+                hpkp.maxAgeInSeconds(maxAgeInSeconds!!)
+            }
+            includeSubDomains?.also {
+                hpkp.includeSubDomains(includeSubDomains!!)
+            }
+            reportOnly?.also {
+                hpkp.reportOnly(reportOnly!!)
+            }
+            reportUri?.also {
+                hpkp.reportUri(reportUri)
+            }
+            if (disabled) {
+                hpkp.disable()
+            }
+        }
+    }
+}

+ 63 - 0
config/src/main/kotlin/org/springframework/security/config/web/servlet/headers/HttpStrictTransportSecurityDsl.kt

@@ -0,0 +1,63 @@
+/*
+ * 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
+
+import org.springframework.security.config.annotation.web.builders.HttpSecurity
+import org.springframework.security.config.annotation.web.configurers.HeadersConfigurer
+import org.springframework.security.web.util.matcher.RequestMatcher
+
+/**
+ * A Kotlin DSL to configure the [HttpSecurity] HTTP Strict Transport Security header using
+ * idiomatic Kotlin code.
+ *
+ * @author Eleftheria Stein
+ * @since 5.3
+ * @property maxAgeInSeconds the value (in seconds) for the max-age directive of the
+ * Strict-Transport-Security header.
+ * @property requestMatcher the [RequestMatcher] used to determine if the
+ * "Strict-Transport-Security" header should be added. If true the header is added,
+ * else the header is not added.
+ * @property includeSubDomains if true, subdomains should be considered HSTS Hosts too.
+ * @property preload if true, preload will be included in HSTS Header.
+ */
+class HttpStrictTransportSecurityDsl {
+    var maxAgeInSeconds: Long? = null
+    var requestMatcher: RequestMatcher? = null
+    var includeSubDomains: Boolean? = null
+    var preload: Boolean? = null
+
+    private var disabled = false
+
+    /**
+     * Disable the HTTP Strict Transport Security header.
+     */
+    fun disable() {
+        disabled = true
+    }
+
+    internal fun get(): (HeadersConfigurer<HttpSecurity>.HstsConfig) -> Unit {
+        return { hsts ->
+            maxAgeInSeconds?.also { hsts.maxAgeInSeconds(maxAgeInSeconds!!) }
+            requestMatcher?.also { hsts.requestMatcher(requestMatcher) }
+            includeSubDomains?.also { hsts.includeSubDomains(includeSubDomains!!) }
+            preload?.also { hsts.preload(preload!!) }
+            if (disabled) {
+                hsts.disable()
+            }
+        }
+    }
+}

+ 41 - 0
config/src/main/kotlin/org/springframework/security/config/web/servlet/headers/ReferrerPolicyDsl.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.servlet.headers
+
+import org.springframework.security.config.annotation.web.builders.HttpSecurity
+import org.springframework.security.config.annotation.web.configurers.HeadersConfigurer
+import org.springframework.security.web.header.writers.ReferrerPolicyHeaderWriter
+
+/**
+ * A Kotlin DSL to configure the [HttpSecurity] referrer policy header using
+ * idiomatic Kotlin code.
+ *
+ * @author Eleftheria Stein
+ * @since 5.3
+ * @property policy the policy to be used in the response header.
+ */
+class ReferrerPolicyDsl {
+    var policy: ReferrerPolicyHeaderWriter.ReferrerPolicy? = null
+
+    internal fun get(): (HeadersConfigurer<HttpSecurity>.ReferrerPolicyConfig) -> Unit {
+        return { referrerPolicy ->
+            policy?.also {
+                referrerPolicy.policy(policy)
+            }
+        }
+    }
+}

+ 55 - 0
config/src/main/kotlin/org/springframework/security/config/web/servlet/headers/XssProtectionConfigDsl.kt

@@ -0,0 +1,55 @@
+/*
+ * 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
+
+import org.springframework.security.config.annotation.web.builders.HttpSecurity
+import org.springframework.security.config.annotation.web.configurers.HeadersConfigurer
+
+/**
+ * A Kotlin DSL to configure the [HttpSecurity] XSS protection header using
+ * idiomatic Kotlin code.
+ *
+ * @author Eleftheria Stein
+ * @since 5.3
+ * @property block whether to specify the mode as blocked
+ * @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.
+ */
+class XssProtectionConfigDsl {
+    var block: Boolean? = null
+    var xssProtectionEnabled: Boolean? = null
+
+    private var disabled = false
+
+    /**
+     * Do not include the X-XSS-Protection header in the response.
+     */
+    fun disable() {
+        disabled = true
+    }
+
+    internal fun get(): (HeadersConfigurer<HttpSecurity>.XXssConfig) -> Unit {
+        return { xssProtection ->
+            block?.also { xssProtection.block(block!!) }
+            xssProtectionEnabled?.also { xssProtection.xssProtectionEnabled(xssProtectionEnabled!!) }
+
+            if (disabled) {
+                xssProtection.disable()
+            }
+        }
+    }
+}

+ 49 - 0
config/src/main/kotlin/org/springframework/security/config/web/servlet/oauth2/client/AuthorizationCodeGrantDsl.kt

@@ -0,0 +1,49 @@
+/*
+ * 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
+
+import org.springframework.security.config.annotation.web.builders.HttpSecurity
+import org.springframework.security.config.annotation.web.configurers.oauth2.client.OAuth2ClientConfigurer
+import org.springframework.security.oauth2.client.endpoint.OAuth2AccessTokenResponseClient
+import org.springframework.security.oauth2.client.endpoint.OAuth2AuthorizationCodeGrantRequest
+import org.springframework.security.oauth2.client.web.AuthorizationRequestRepository
+import org.springframework.security.oauth2.client.web.OAuth2AuthorizationRequestResolver
+import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationRequest
+
+/**
+ * A Kotlin DSL to configure OAuth 2.0 Authorization Code Grant.
+ *
+ * @author Eleftheria Stein
+ * @since 5.3
+ * @property authorizationRequestResolver the resolver used for resolving [OAuth2AuthorizationRequest]'s.
+ * @property authorizationRequestRepository the repository used for storing [OAuth2AuthorizationRequest]'s.
+ * @property accessTokenResponseClient the client used for requesting the access token credential
+ * from the Token Endpoint.
+ */
+class AuthorizationCodeGrantDsl {
+    var authorizationRequestResolver: OAuth2AuthorizationRequestResolver? = null
+    var authorizationRequestRepository: AuthorizationRequestRepository<OAuth2AuthorizationRequest>? = null
+    var accessTokenResponseClient: OAuth2AccessTokenResponseClient<OAuth2AuthorizationCodeGrantRequest>? = null
+
+    internal fun get(): (OAuth2ClientConfigurer<HttpSecurity>.AuthorizationCodeGrantConfigurer) -> Unit {
+        return { authorizationCodeGrant ->
+            authorizationRequestResolver?.also { authorizationCodeGrant.authorizationRequestResolver(authorizationRequestResolver) }
+            authorizationRequestRepository?.also { authorizationCodeGrant.authorizationRequestRepository(authorizationRequestRepository) }
+            accessTokenResponseClient?.also { authorizationCodeGrant.accessTokenResponseClient(accessTokenResponseClient) }
+        }
+    }
+}

+ 47 - 0
config/src/main/kotlin/org/springframework/security/config/web/servlet/oauth2/login/AuthorizationEndpointDsl.kt

@@ -0,0 +1,47 @@
+/*
+ * 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
+
+import org.springframework.security.config.annotation.web.builders.HttpSecurity
+import org.springframework.security.config.annotation.web.configurers.oauth2.client.OAuth2LoginConfigurer
+import org.springframework.security.oauth2.client.web.AuthorizationRequestRepository
+import org.springframework.security.oauth2.client.web.OAuth2AuthorizationRequestResolver
+import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationRequest
+
+/**
+ * A Kotlin DSL to configure the Authorization Server's Authorization Endpoint using
+ * idiomatic Kotlin code.
+ *
+ * @author Eleftheria Stein
+ * @since 5.3
+ * @property baseUri the base URI used for authorization requests.
+ * @property authorizationRequestResolver the resolver used for resolving [OAuth2AuthorizationRequest]'s.
+ * @property authorizationRequestRepository the repository used for storing [OAuth2AuthorizationRequest]'s.
+ */
+class AuthorizationEndpointDsl {
+    var baseUri: String? = null
+    var authorizationRequestResolver: OAuth2AuthorizationRequestResolver? = null
+    var authorizationRequestRepository: AuthorizationRequestRepository<OAuth2AuthorizationRequest>? = null
+
+    internal fun get(): (OAuth2LoginConfigurer<HttpSecurity>.AuthorizationEndpointConfig) -> Unit {
+        return { authorizationEndpoint ->
+            baseUri?.also { authorizationEndpoint.baseUri(baseUri) }
+            authorizationRequestResolver?.also { authorizationEndpoint.authorizationRequestResolver(authorizationRequestResolver) }
+            authorizationRequestRepository?.also { authorizationEndpoint.authorizationRequestRepository(authorizationRequestRepository) }
+        }
+    }
+}

+ 38 - 0
config/src/main/kotlin/org/springframework/security/config/web/servlet/oauth2/login/RedirectionEndpointDsl.kt

@@ -0,0 +1,38 @@
+/*
+ * 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
+
+import org.springframework.security.config.annotation.web.builders.HttpSecurity
+import org.springframework.security.config.annotation.web.configurers.oauth2.client.OAuth2LoginConfigurer
+
+/**
+ * A Kotlin DSL to configure the Authorization Server's Redirection Endpoint using
+ * idiomatic Kotlin code.
+ *
+ * @author Eleftheria Stein
+ * @since 5.3
+ * @property baseUri the URI where the authorization response will be processed.
+ */
+class RedirectionEndpointDsl {
+    var baseUri: String? = null
+
+    internal fun get(): (OAuth2LoginConfigurer<HttpSecurity>.RedirectionEndpointConfig) -> Unit {
+        return { redirectionEndpoint ->
+            baseUri?.also { redirectionEndpoint.baseUri(baseUri) }
+        }
+    }
+}

+ 41 - 0
config/src/main/kotlin/org/springframework/security/config/web/servlet/oauth2/login/TokenEndpointDsl.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.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.oauth2.client.endpoint.OAuth2AccessTokenResponseClient
+import org.springframework.security.oauth2.client.endpoint.OAuth2AuthorizationCodeGrantRequest
+
+/**
+ * A Kotlin DSL to configure the Authorization Server's Token Endpoint using
+ * idiomatic Kotlin code.
+ *
+ * @author Eleftheria Stein
+ * @since 5.3
+ * @property accessTokenResponseClient the client used for requesting the access token credential
+ * from the Token Endpoint.
+ */
+class TokenEndpointDsl {
+    var accessTokenResponseClient: OAuth2AccessTokenResponseClient<OAuth2AuthorizationCodeGrantRequest>? = null
+
+    internal fun get(): (OAuth2LoginConfigurer<HttpSecurity>.TokenEndpointConfig) -> Unit {
+        return { tokenEndpoint ->
+            accessTokenResponseClient?.also { tokenEndpoint.accessTokenResponseClient(accessTokenResponseClient) }
+        }
+    }
+}

+ 67 - 0
config/src/main/kotlin/org/springframework/security/config/web/servlet/oauth2/login/UserInfoEndpointDsl.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.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.core.authority.mapping.GrantedAuthoritiesMapper
+import org.springframework.security.oauth2.client.oidc.userinfo.OidcUserRequest
+import org.springframework.security.oauth2.client.registration.ClientRegistration
+import org.springframework.security.oauth2.client.userinfo.OAuth2UserRequest
+import org.springframework.security.oauth2.client.userinfo.OAuth2UserService
+import org.springframework.security.oauth2.core.oidc.user.OidcUser
+import org.springframework.security.oauth2.core.user.OAuth2User
+
+/**
+ * A Kotlin DSL to configure the Authorization Server's UserInfo Endpoint using
+ * idiomatic Kotlin code.
+ *
+ * @author Eleftheria Stein
+ * @since 5.3
+ * @property userService the OAuth 2.0 service used for obtaining the user attributes of the End-User
+ * from the UserInfo Endpoint.
+ * @property oidcUserService the OpenID Connect 1.0 service used for obtaining the user attributes of the
+ * End-User from the UserInfo Endpoint.
+ * @property userAuthoritiesMapper the [GrantedAuthoritiesMapper] used for mapping [OAuth2User.getAuthorities]
+ */
+class UserInfoEndpointDsl {
+    var userService: OAuth2UserService<OAuth2UserRequest, OAuth2User>? = null
+    var oidcUserService: OAuth2UserService<OidcUserRequest, OidcUser>? = null
+    var userAuthoritiesMapper: GrantedAuthoritiesMapper? = null
+
+    private var customUserTypePair: Pair<Class<out OAuth2User>, String>? = null
+
+    /**
+     * Sets a custom [OAuth2User] type and associates it to the provided
+     * client [ClientRegistration.getRegistrationId] registration identifier.
+     *
+     * @param customUserType a custom [OAuth2User] type
+     * @param clientRegistrationId the client registration identifier
+     */
+    fun customUserType(customUserType: Class<out OAuth2User>, clientRegistrationId: String) {
+        customUserTypePair = Pair(customUserType, clientRegistrationId)
+    }
+
+    internal fun get(): (OAuth2LoginConfigurer<HttpSecurity>.UserInfoEndpointConfig) -> Unit {
+        return { userInfoEndpoint ->
+            userService?.also { userInfoEndpoint.userService(userService) }
+            oidcUserService?.also { userInfoEndpoint.oidcUserService(oidcUserService) }
+            userAuthoritiesMapper?.also { userInfoEndpoint.userAuthoritiesMapper(userAuthoritiesMapper) }
+            customUserTypePair?.also { userInfoEndpoint.customUserType(customUserTypePair!!.first, customUserTypePair!!.second) }
+        }
+    }
+}

+ 49 - 0
config/src/main/kotlin/org/springframework/security/config/web/servlet/oauth2/resourceserver/JwtDsl.kt

@@ -0,0 +1,49 @@
+/*
+ * 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
+
+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.oauth2.jwt.Jwt
+import org.springframework.security.oauth2.jwt.JwtDecoder
+
+/**
+ * A Kotlin DSL to configure JWT Resource Server Support using idiomatic Kotlin code.
+ *
+ * @author Eleftheria Stein
+ * @since 5.3
+ * @property jwtAuthenticationConverter the [Converter] to use for converting a [Jwt] into
+ * an [AbstractAuthenticationToken].
+ * @property jwtDecoder the [JwtDecoder] to use.
+ * @property jwkSetUri configures a [JwtDecoder] using a
+ * <a target="_blank" href="https://tools.ietf.org/html/rfc7517">JSON Web Key (JWK)</a> URL
+ */
+class JwtDsl {
+    var jwtAuthenticationConverter: Converter<Jwt, out AbstractAuthenticationToken>? = null
+    var jwtDecoder: JwtDecoder? = null
+    var jwkSetUri: String? = null
+
+    internal fun get(): (OAuth2ResourceServerConfigurer<HttpSecurity>.JwtConfigurer) -> Unit {
+        return { jwt ->
+            jwtAuthenticationConverter?.also { jwt.jwtAuthenticationConverter(jwtAuthenticationConverter) }
+            jwtDecoder?.also { jwt.decoder(jwtDecoder) }
+            jwkSetUri?.also { jwt.jwkSetUri(jwkSetUri) }
+        }
+    }
+}

+ 54 - 0
config/src/main/kotlin/org/springframework/security/config/web/servlet/oauth2/resourceserver/OpaqueTokenDsl.kt

@@ -0,0 +1,54 @@
+/*
+ * 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
+
+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.oauth2.server.resource.introspection.OpaqueTokenIntrospector
+
+/**
+ * A Kotlin DSL to configure JWT Resource Server Support using idiomatic Kotlin code.
+ *
+ * @author Eleftheria Stein
+ * @since 5.3
+ * @property introspectionUri the URI of the Introspection endpoint.
+ * @property introspector the [OpaqueTokenIntrospector] to use.
+ */
+class OpaqueTokenDsl {
+    var introspectionUri: String? = null
+    var introspector: OpaqueTokenIntrospector? = null
+
+    private var clientCredentials: Pair<String, String>? = 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)
+    }
+
+    internal fun get(): (OAuth2ResourceServerConfigurer<HttpSecurity>.OpaqueTokenConfigurer) -> Unit {
+        return { opaqueToken ->
+            introspectionUri?.also { opaqueToken.introspectionUri(introspectionUri) }
+            introspector?.also { opaqueToken.introspector(introspector) }
+            clientCredentials?.also { opaqueToken.introspectionClientCredentials(clientCredentials!!.first, clientCredentials!!.second) }
+        }
+    }
+}

+ 67 - 0
config/src/main/kotlin/org/springframework/security/config/web/servlet/session/SessionConcurrencyDsl.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.servlet.session
+
+import org.springframework.security.config.annotation.web.builders.HttpSecurity
+import org.springframework.security.config.annotation.web.configurers.SessionManagementConfigurer
+import org.springframework.security.core.session.SessionRegistry
+import org.springframework.security.web.session.SessionInformationExpiredStrategy
+
+/**
+ * A Kotlin DSL to configure the behaviour of multiple sessions using idiomatic
+ * Kotlin code.
+ *
+ * @author Eleftheria Stein
+ * @since 5.3
+ * @property maximumSessions controls the maximum number of sessions for a user.
+ * @property expiredUrl the URL to redirect to if a user tries to access a resource and
+ * their session has been expired due to too many sessions for the current user.
+ * @property expiredSessionStrategy determines the behaviour when an expired session
+ * is detected.
+ * @property maxSessionsPreventsLogin if true, prevents a user from authenticating when the
+ * [maximumSessions] has been reached. Otherwise (default), the user who authenticates
+ * is allowed access and an existing user's session is expired.
+ * @property sessionRegistry the [SessionRegistry] implementation used.
+ *
+ */
+class SessionConcurrencyDsl {
+    var maximumSessions: Int? = null
+    var expiredUrl: String? = null
+    var expiredSessionStrategy: SessionInformationExpiredStrategy? = null
+    var maxSessionsPreventsLogin: Boolean? = null
+    var sessionRegistry: SessionRegistry? = null
+
+    internal fun get(): (SessionManagementConfigurer<HttpSecurity>.ConcurrencyControlConfigurer) -> Unit {
+        return { sessionConcurrencyControl ->
+            maximumSessions?.also {
+                sessionConcurrencyControl.maximumSessions(maximumSessions!!)
+            }
+            expiredUrl?.also {
+                sessionConcurrencyControl.expiredUrl(expiredUrl)
+            }
+            expiredSessionStrategy?.also {
+                sessionConcurrencyControl.expiredSessionStrategy(expiredSessionStrategy)
+            }
+            maxSessionsPreventsLogin?.also {
+                sessionConcurrencyControl.maxSessionsPreventsLogin(maxSessionsPreventsLogin!!)
+            }
+            sessionRegistry?.also {
+                sessionConcurrencyControl.sessionRegistry(sessionRegistry)
+            }
+        }
+    }
+}

+ 83 - 0
config/src/main/kotlin/org/springframework/security/config/web/servlet/session/SessionFixationDsl.kt

@@ -0,0 +1,83 @@
+/*
+ * 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
+
+import org.springframework.security.config.annotation.web.builders.HttpSecurity
+import org.springframework.security.config.annotation.web.configurers.SessionManagementConfigurer
+import javax.servlet.http.HttpServletRequest
+import javax.servlet.http.HttpSession
+
+/**
+ * A Kotlin DSL to configure session fixation protection using idiomatic
+ * Kotlin code.
+ *
+ * @author Eleftheria Stein
+ * @since 5.3
+ */
+class SessionFixationDsl {
+    private var strategy: SessionFixationStrategy? = null
+
+    /**
+     * Specifies that a new session should be created, but the session attributes from
+     * the original [HttpSession] should not be retained.
+     */
+    fun newSession() {
+        this.strategy = SessionFixationStrategy.NEW
+    }
+
+    /**
+     * Specifies that a new session should be created and the session attributes from
+     * the original [HttpSession] should be retained.
+     */
+    fun migrateSession() {
+        this.strategy = SessionFixationStrategy.MIGRATE
+    }
+
+    /**
+     * Specifies that the Servlet container-provided session fixation protection
+     * should be used. When a session authenticates, the Servlet method
+     * [HttpServletRequest.changeSessionId] is called to change the session ID
+     * and retain all session attributes.
+     */
+    fun changeSessionId() {
+        this.strategy = SessionFixationStrategy.CHANGE_ID
+    }
+
+    /**
+     * Specifies that no session fixation protection should be enabled.
+     */
+    fun none() {
+        this.strategy = SessionFixationStrategy.NONE
+    }
+
+    internal fun get(): (SessionManagementConfigurer<HttpSecurity>.SessionFixationConfigurer) -> Unit {
+        return { sessionFixation ->
+            strategy?.also {
+                when (strategy) {
+                    SessionFixationStrategy.NEW -> sessionFixation.newSession()
+                    SessionFixationStrategy.MIGRATE -> sessionFixation.migrateSession()
+                    SessionFixationStrategy.CHANGE_ID -> sessionFixation.changeSessionId()
+                    SessionFixationStrategy.NONE -> sessionFixation.none()
+                }
+            }
+        }
+    }
+}
+
+private enum class SessionFixationStrategy {
+    NEW, MIGRATE, CHANGE_ID, NONE
+}

+ 163 - 0
config/src/test/kotlin/org/springframework/security/config/web/servlet/AnonymousDslTests.kt

@@ -0,0 +1,163 @@
+/*
+ * 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
+
+import org.junit.Rule
+import org.junit.Test
+import org.springframework.beans.factory.annotation.Autowired
+import org.springframework.security.authentication.AnonymousAuthenticationToken
+import org.springframework.security.config.annotation.web.builders.HttpSecurity
+import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity
+import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter
+import org.springframework.security.config.test.SpringTestRule
+import org.springframework.security.core.annotation.AuthenticationPrincipal
+import org.springframework.security.core.authority.SimpleGrantedAuthority
+import org.springframework.security.core.context.SecurityContextHolder
+import org.springframework.test.web.servlet.MockMvc
+import org.springframework.test.web.servlet.get
+import org.springframework.web.bind.annotation.GetMapping
+import org.springframework.web.bind.annotation.RestController
+import org.springframework.web.servlet.config.annotation.EnableWebMvc
+import java.util.*
+
+/**
+ * Tests for [AnonymousDsl]
+ *
+ * @author Eleftheria Stein
+ */
+class AnonymousDslTests {
+    @Rule
+    @JvmField
+    val spring = SpringTestRule()
+
+    @Autowired
+    lateinit var mockMvc: MockMvc
+
+    @Test
+    fun `anonymous when custom principal then custom principal used`() {
+        this.spring.register(PrincipalConfig::class.java, PrincipalController::class.java).autowire()
+
+        this.mockMvc.get("/principal")
+                .andExpect {
+                    content { string("principal") }
+                }
+    }
+
+    @EnableWebSecurity
+    @EnableWebMvc
+    open class PrincipalConfig : WebSecurityConfigurerAdapter() {
+        override fun configure(http: HttpSecurity) {
+            http {
+                anonymous {
+                    principal = "principal"
+                }
+            }
+        }
+    }
+
+    @Test
+    fun `anonymous when custom key then custom key used`() {
+        this.spring.register(KeyConfig::class.java, PrincipalController::class.java).autowire()
+
+        this.mockMvc.get("/key")
+                .andExpect {
+                    content { string("key".hashCode().toString()) }
+                }
+    }
+
+    @EnableWebSecurity
+    @EnableWebMvc
+    open class KeyConfig : WebSecurityConfigurerAdapter() {
+        override fun configure(http: HttpSecurity) {
+            http {
+                anonymous {
+                    key = "key"
+                }
+            }
+        }
+    }
+
+    @Test
+    fun `anonymous when disabled then responds with forbidden`() {
+        this.spring.register(AnonymousDisabledConfig::class.java, PrincipalController::class.java).autowire()
+
+        this.mockMvc.get("/principal")
+                .andExpect {
+                    content { string("") }
+                }
+    }
+
+    @EnableWebSecurity
+    @EnableWebMvc
+    open class AnonymousDisabledConfig : WebSecurityConfigurerAdapter() {
+        override fun configure(http: HttpSecurity) {
+            http {
+                anonymous {
+                    disable()
+                }
+            }
+        }
+    }
+
+    @Test
+    fun `anonymous when custom authorities then authorities used`() {
+        this.spring.register(AnonymousAuthoritiesConfig::class.java, PrincipalController::class.java).autowire()
+
+        this.mockMvc.get("/principal")
+                .andExpect {
+                    status { isOk }
+                }
+    }
+
+    @EnableWebSecurity
+    @EnableWebMvc
+    open class AnonymousAuthoritiesConfig : WebSecurityConfigurerAdapter() {
+        override fun configure(http: HttpSecurity) {
+            http {
+                anonymous {
+                    authorities = listOf(SimpleGrantedAuthority("TEST"))
+                }
+                authorizeRequests {
+                    authorize(anyRequest, hasAuthority("TEST"))
+                }
+            }
+        }
+    }
+
+    @RestController
+    internal class PrincipalController {
+        @GetMapping("/principal")
+        fun principal(@AuthenticationPrincipal principal: String?): String? {
+            return principal
+        }
+
+        @GetMapping("/key")
+        fun key(): String {
+            return anonymousToken()
+                    .map { it.keyHash }
+                    .map { it.toString() }
+                    .orElse(null)
+        }
+
+        private fun anonymousToken(): Optional<AnonymousAuthenticationToken> {
+            return Optional.of(SecurityContextHolder.getContext())
+                    .map { it.authentication }
+                    .filter { it is AnonymousAuthenticationToken }
+                    .map { AnonymousAuthenticationToken::class.java.cast(it) }
+        }
+    }
+}

+ 211 - 0
config/src/test/kotlin/org/springframework/security/config/web/servlet/AuthorizeRequestsDslTests.kt

@@ -0,0 +1,211 @@
+/*
+ * 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
+
+import org.junit.Rule
+import org.junit.Test
+import org.springframework.beans.factory.annotation.Autowired
+import org.springframework.security.config.annotation.web.builders.HttpSecurity
+import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity
+import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter
+import org.springframework.security.config.test.SpringTestRule
+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.request.MockMvcRequestBuilders
+import org.springframework.test.web.servlet.result.MockMvcResultMatchers.status
+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
+
+/**
+ * Tests for [AuthorizeRequestsDsl]
+ *
+ * @author Eleftheria Stein
+ */
+class AuthorizeRequestsDslTests {
+    @Rule
+    @JvmField
+    val spring = SpringTestRule()
+
+    @Autowired
+    lateinit var mockMvc: MockMvc
+
+    @Test
+    fun `request when secured by regex matcher then responds with forbidden`() {
+        this.spring.register(AuthorizeRequestsByRegexConfig::class.java).autowire()
+
+        this.mockMvc.get("/private")
+                .andExpect {
+                    status { isForbidden }
+                }
+    }
+
+    @Test
+    fun `request when allowed by regex matcher then responds with ok`() {
+        this.spring.register(AuthorizeRequestsByRegexConfig::class.java).autowire()
+
+        this.mockMvc.get("/path")
+                .andExpect {
+                    status { isOk }
+                }
+    }
+
+    @EnableWebSecurity
+    open class AuthorizeRequestsByRegexConfig : WebSecurityConfigurerAdapter() {
+        override fun configure(http: HttpSecurity) {
+            http {
+                authorizeRequests {
+                    authorize(RegexRequestMatcher("/path", null), permitAll)
+                    authorize(RegexRequestMatcher(".*", null), authenticated)
+                }
+            }
+        }
+
+        @RestController
+        internal class PathController {
+            @RequestMapping("/path")
+            fun path() {
+            }
+        }
+    }
+
+    @Test
+    fun `request when secured by mvc then responds with forbidden`() {
+        this.spring.register(AuthorizeRequestsByMvcConfig::class.java).autowire()
+
+        this.mockMvc.get("/private")
+                .andExpect {
+                    status { isForbidden }
+                }
+    }
+
+    @Test
+    fun `request when allowed by mvc then responds with OK`() {
+        this.spring.register(AuthorizeRequestsByMvcConfig::class.java).autowire()
+
+        this.mockMvc.get("/path")
+                .andExpect {
+                    status { isOk }
+                }
+
+        this.mockMvc.get("/path.html")
+                .andExpect {
+                    status { isOk }
+                }
+
+        this.mockMvc.get("/path/")
+                .andExpect {
+                    status { isOk }
+                }
+    }
+
+    @EnableWebSecurity
+    @EnableWebMvc
+    open class AuthorizeRequestsByMvcConfig : WebSecurityConfigurerAdapter() {
+        override fun configure(http: HttpSecurity) {
+            http {
+                authorizeRequests {
+                    authorize("/path", permitAll)
+                    authorize("/**", authenticated)
+                }
+            }
+        }
+
+        @RestController
+        internal class PathController {
+            @RequestMapping("/path")
+            fun path() {
+            }
+        }
+    }
+
+    @Test
+    fun `request when secured by mvc path variables then responds based on path variable value`() {
+        this.spring.register(MvcMatcherPathVariablesConfig::class.java).autowire()
+
+        this.mockMvc.get("/user/user")
+                .andExpect {
+                    status { isOk }
+                }
+
+        this.mockMvc.get("/user/deny")
+                .andExpect {
+                    status { isForbidden }
+                }
+    }
+
+    @EnableWebSecurity
+    @EnableWebMvc
+    open class MvcMatcherPathVariablesConfig : WebSecurityConfigurerAdapter() {
+        override fun configure(http: HttpSecurity) {
+            http {
+                authorizeRequests {
+                    authorize("/user/{userName}", "#userName == 'user'")
+                }
+            }
+        }
+
+        @RestController
+        internal class PathController {
+            @RequestMapping("/user/{user}")
+            fun path(@PathVariable user: String) {
+            }
+        }
+    }
+
+    @Test
+    fun `request when secured by mvc with servlet path then responds based on servlet path`() {
+        this.spring.register(MvcMatcherServletPathConfig::class.java).autowire()
+
+        this.mockMvc.perform(MockMvcRequestBuilders.get("/spring/path")
+                .with { request ->
+                    request.servletPath = "/spring"
+                    request
+                })
+                .andExpect(status().isForbidden)
+
+        this.mockMvc.perform(MockMvcRequestBuilders.get("/other/path")
+                .with { request ->
+                    request.servletPath = "/other"
+                    request
+                })
+                .andExpect(status().isOk)
+    }
+
+    @EnableWebSecurity
+    @EnableWebMvc
+    open class MvcMatcherServletPathConfig : WebSecurityConfigurerAdapter() {
+        override fun configure(http: HttpSecurity) {
+            http {
+                authorizeRequests {
+                    authorize("/path",
+                            "/spring",
+                            denyAll)
+                }
+            }
+        }
+
+        @RestController
+        internal class PathController {
+            @RequestMapping("/path")
+            fun path() {
+            }
+        }
+    }
+}

+ 138 - 0
config/src/test/kotlin/org/springframework/security/config/web/servlet/CorsDslTests.kt

@@ -0,0 +1,138 @@
+/*
+ * 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
+
+import org.assertj.core.api.Assertions.assertThatThrownBy
+import org.junit.Rule
+import org.junit.Test
+import org.springframework.beans.factory.BeanCreationException
+import org.springframework.beans.factory.annotation.Autowired
+import org.springframework.context.annotation.Bean
+import org.springframework.http.HttpHeaders
+import org.springframework.security.config.annotation.web.builders.HttpSecurity
+import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity
+import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter
+import org.springframework.security.config.test.SpringTestRule
+import org.springframework.test.web.servlet.MockMvc
+import org.springframework.test.web.servlet.get
+import org.springframework.web.bind.annotation.RequestMethod
+import org.springframework.web.cors.CorsConfiguration
+import org.springframework.web.cors.CorsConfigurationSource
+import org.springframework.web.cors.UrlBasedCorsConfigurationSource
+import org.springframework.web.servlet.config.annotation.EnableWebMvc
+
+/**
+ * Tests for [CorsDsl]
+ *
+ * @author Eleftheria Stein
+ */
+class CorsDslTests {
+    @Rule
+    @JvmField
+    val spring = SpringTestRule()
+
+    @Autowired
+    lateinit var mockMvc: MockMvc
+
+    @Test
+    fun `CORS when no MVC then exception`() {
+        assertThatThrownBy { this.spring.register(DefaultCorsConfig::class.java).autowire() }
+                .isInstanceOf(BeanCreationException::class.java)
+                .hasMessageContaining("Please ensure Spring Security & Spring MVC are configured in a shared ApplicationContext")
+
+    }
+
+    @EnableWebSecurity
+    open class DefaultCorsConfig : WebSecurityConfigurerAdapter() {
+        override fun configure(http: HttpSecurity) {
+            http {
+                cors { }
+            }
+        }
+    }
+
+    @Test
+    fun `CORS when CORS configuration source bean then responds with CORS header`() {
+        this.spring.register(CorsCrossOriginConfig::class.java).autowire()
+
+        this.mockMvc.get("/")
+        {
+            header(HttpHeaders.ORIGIN, "https://example.com")
+        }.andExpect {
+            header { exists("Access-Control-Allow-Origin") }
+        }
+    }
+
+    @EnableWebMvc
+    @EnableWebSecurity
+    open class CorsCrossOriginConfig : WebSecurityConfigurerAdapter() {
+        override fun configure(http: HttpSecurity) {
+            http {
+                cors { }
+            }
+        }
+
+        @Bean
+        open fun corsConfigurationSource(): CorsConfigurationSource {
+            val source = UrlBasedCorsConfigurationSource()
+            val corsConfiguration = CorsConfiguration()
+            corsConfiguration.allowedOrigins = listOf("*")
+            corsConfiguration.allowedMethods = listOf(
+                    RequestMethod.GET.name,
+                    RequestMethod.POST.name)
+            source.registerCorsConfiguration("/**", corsConfiguration)
+            return source
+        }
+    }
+
+    @Test
+    fun `CORS when disabled then response does not include CORS header`() {
+        this.spring.register(CorsDisabledConfig::class.java).autowire()
+
+        this.mockMvc.get("/")
+        {
+            header(HttpHeaders.ORIGIN, "https://example.com")
+        }.andExpect {
+            header { doesNotExist("Access-Control-Allow-Origin") }
+        }
+    }
+
+    @EnableWebMvc
+    @EnableWebSecurity
+    open class CorsDisabledConfig : WebSecurityConfigurerAdapter() {
+        override fun configure(http: HttpSecurity) {
+            http.cors()
+            http {
+                cors {
+                    disable()
+                }
+            }
+        }
+
+        @Bean
+        open fun corsConfigurationSource(): CorsConfigurationSource {
+            val source = UrlBasedCorsConfigurationSource()
+            val corsConfiguration = CorsConfiguration()
+            corsConfiguration.allowedOrigins = listOf("*")
+            corsConfiguration.allowedMethods = listOf(
+                    RequestMethod.GET.name,
+                    RequestMethod.POST.name)
+            source.registerCorsConfiguration("/**", corsConfiguration)
+            return source
+        }
+    }
+}

+ 265 - 0
config/src/test/kotlin/org/springframework/security/config/web/servlet/CsrfDslTests.kt

@@ -0,0 +1,265 @@
+/*
+ * 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
+
+import org.junit.Rule
+import org.junit.Test
+import org.mockito.Mockito.*
+import org.springframework.beans.factory.annotation.Autowired
+import org.springframework.context.annotation.Bean
+import org.springframework.security.config.annotation.web.builders.HttpSecurity
+import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity
+import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter
+import org.springframework.security.config.test.SpringTestRule
+import org.springframework.security.core.Authentication
+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.SecurityMockMvcRequestBuilders.formLogin
+import org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.csrf
+import org.springframework.security.web.authentication.session.SessionAuthenticationStrategy
+import org.springframework.security.web.csrf.CsrfTokenRepository
+import org.springframework.security.web.csrf.DefaultCsrfToken
+import org.springframework.security.web.util.matcher.AntPathRequestMatcher
+import org.springframework.test.web.servlet.MockMvc
+import org.springframework.test.web.servlet.get
+import org.springframework.test.web.servlet.post
+import org.springframework.web.bind.annotation.PostMapping
+import org.springframework.web.bind.annotation.RestController
+import javax.servlet.http.HttpServletRequest
+import javax.servlet.http.HttpServletResponse
+
+/**
+ * Tests for [CsrfDsl]
+ *
+ * @author Eleftheria Stein
+ */
+class CsrfDslTests {
+    @Rule
+    @JvmField
+    val spring = SpringTestRule()
+
+    @Autowired
+    lateinit var mockMvc: MockMvc
+
+    @Test
+    fun `POST when CSRF enabled and no CSRF token then forbidden`() {
+        this.spring.register(DefaultCsrfConfig::class.java).autowire()
+
+        this.mockMvc.post("/test1")
+                .andExpect {
+                    status { isForbidden }
+                }
+    }
+
+    @Test
+    fun `POST when CSRF enabled and CSRF token then status OK`() {
+        this.spring.register(DefaultCsrfConfig::class.java, BasicController::class.java).autowire()
+
+        this.mockMvc.post("/test1") {
+            with(csrf())
+        }.andExpect {
+            status { isOk }
+        }
+
+    }
+
+    @EnableWebSecurity
+    open class DefaultCsrfConfig : WebSecurityConfigurerAdapter() {
+        override fun configure(http: HttpSecurity) {
+            http {
+                csrf { }
+            }
+        }
+    }
+
+    @Test
+    fun `POST when CSRF disabled and no CSRF token then status OK`() {
+        this.spring.register(CsrfDisabledConfig::class.java, BasicController::class.java).autowire()
+
+        this.mockMvc.post("/test1")
+                .andExpect {
+                    status { isOk }
+                }
+    }
+
+    @EnableWebSecurity
+    open class CsrfDisabledConfig : WebSecurityConfigurerAdapter() {
+        override fun configure(http: HttpSecurity) {
+            http {
+                csrf {
+                    disable()
+                }
+            }
+        }
+    }
+
+    @Test
+    fun `CSRF when custom CSRF token repository then repo used`() {
+        `when`(CustomRepositoryConfig.REPO.loadToken(any<HttpServletRequest>()))
+                .thenReturn(DefaultCsrfToken("X-CSRF-TOKEN", "_csrf", "token"))
+
+        this.spring.register(CustomRepositoryConfig::class.java).autowire()
+
+        this.mockMvc.get("/test1")
+
+        verify(CustomRepositoryConfig.REPO).loadToken(any<HttpServletRequest>())
+    }
+
+    @EnableWebSecurity
+    open class CustomRepositoryConfig : WebSecurityConfigurerAdapter() {
+        companion object {
+            var REPO: CsrfTokenRepository = mock(CsrfTokenRepository::class.java)
+        }
+
+        override fun configure(http: HttpSecurity) {
+            http {
+                csrf {
+                    csrfTokenRepository = REPO
+                }
+            }
+        }
+    }
+
+    @Test
+    fun `CSRF when require CSRF protection matcher then CSRF protection on matching requests`() {
+        this.spring.register(RequireCsrfProtectionMatcherConfig::class.java, BasicController::class.java).autowire()
+
+        this.mockMvc.post("/test1")
+                .andExpect {
+                    status { isForbidden }
+                }
+
+        this.mockMvc.post("/test2")
+                .andExpect {
+                    status { isOk }
+                }
+    }
+
+    @EnableWebSecurity
+    open class RequireCsrfProtectionMatcherConfig : WebSecurityConfigurerAdapter() {
+        override fun configure(http: HttpSecurity) {
+            http {
+                csrf {
+                    requireCsrfProtectionMatcher = AntPathRequestMatcher("/test1")
+                }
+            }
+        }
+    }
+
+    @Test
+    fun `CSRF when custom session authentication strategy then strategy used`() {
+        this.spring.register(CustomStrategyConfig::class.java).autowire()
+
+        this.mockMvc.perform(formLogin())
+
+        verify(CustomStrategyConfig.STRATEGY, atLeastOnce())
+                .onAuthentication(any(Authentication::class.java), any(HttpServletRequest::class.java), any(HttpServletResponse::class.java))
+
+    }
+
+    @EnableWebSecurity
+    open class CustomStrategyConfig : WebSecurityConfigurerAdapter() {
+        companion object {
+            var STRATEGY: SessionAuthenticationStrategy = mock(SessionAuthenticationStrategy::class.java)
+        }
+
+        override fun configure(http: HttpSecurity) {
+            http {
+                formLogin { }
+                csrf {
+                    sessionAuthenticationStrategy = STRATEGY
+                }
+            }
+        }
+
+        @Bean
+        override fun userDetailsService(): UserDetailsService {
+            val userDetails = User.withDefaultPasswordEncoder()
+                    .username("user")
+                    .password("password")
+                    .roles("USER")
+                    .build()
+            return InMemoryUserDetailsManager(userDetails)
+        }
+    }
+
+    @Test
+    fun `CSRF when ignoring request matchers then CSRF disabled on matching requests`() {
+        this.spring.register(IgnoringRequestMatchersConfig::class.java, BasicController::class.java).autowire()
+
+        this.mockMvc.post("/test1")
+                .andExpect {
+                    status { isForbidden }
+                }
+
+        this.mockMvc.post("/test2")
+                .andExpect {
+                    status { isOk }
+                }
+    }
+
+    @EnableWebSecurity
+    open class IgnoringRequestMatchersConfig : WebSecurityConfigurerAdapter() {
+        override fun configure(http: HttpSecurity) {
+            http {
+                csrf {
+                    requireCsrfProtectionMatcher = AntPathRequestMatcher("/**")
+                    ignoringRequestMatchers(AntPathRequestMatcher("/test2"))
+                }
+            }
+        }
+    }
+
+    @Test
+    fun `CSRF when ignoring ant matchers then CSRF disabled on matching requests`() {
+        this.spring.register(IgnoringAntMatchersConfig::class.java, BasicController::class.java).autowire()
+
+        this.mockMvc.post("/test1")
+                .andExpect {
+                    status { isForbidden }
+                }
+
+        this.mockMvc.post("/test2")
+                .andExpect {
+                    status { isOk }
+                }
+    }
+
+    @EnableWebSecurity
+    open class IgnoringAntMatchersConfig : WebSecurityConfigurerAdapter() {
+        override fun configure(http: HttpSecurity) {
+            http {
+                csrf {
+                    requireCsrfProtectionMatcher = AntPathRequestMatcher("/**")
+                    ignoringAntMatchers("/test2")
+                }
+            }
+        }
+    }
+
+    @RestController
+    internal class BasicController {
+        @PostMapping("/test1")
+        fun test1() {
+        }
+
+        @PostMapping("/test2")
+        fun test2() {
+        }
+    }
+}

+ 252 - 0
config/src/test/kotlin/org/springframework/security/config/web/servlet/ExceptionHandlingDslTests.kt

@@ -0,0 +1,252 @@
+/*
+ * 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
+
+import org.junit.Rule
+import org.junit.Test
+import org.springframework.beans.factory.annotation.Autowired
+import org.springframework.security.access.AccessDeniedException
+import org.springframework.security.config.annotation.web.builders.HttpSecurity
+import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity
+import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter
+import org.springframework.security.config.test.SpringTestRule
+import org.springframework.security.core.userdetails.User.withUsername
+import org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.user
+import org.springframework.security.web.access.AccessDeniedHandlerImpl
+import org.springframework.security.web.authentication.LoginUrlAuthenticationEntryPoint
+import org.springframework.security.web.util.matcher.AntPathRequestMatcher
+import org.springframework.test.web.servlet.MockMvc
+import org.springframework.test.web.servlet.get
+import org.springframework.web.servlet.config.annotation.EnableWebMvc
+
+/**
+ * Tests for [ExceptionHandlingDsl]
+ *
+ * @author Eleftheria Stein
+ */
+class ExceptionHandlingDslTests {
+    @Rule
+    @JvmField
+    val spring = SpringTestRule()
+
+    @Autowired
+    lateinit var mockMvc: MockMvc
+
+    @Test
+    fun `request when exception handling enabled then returns forbidden`() {
+        this.spring.register(ExceptionHandlingConfig::class.java).autowire()
+
+        this.mockMvc.get("/")
+                .andExpect {
+                    status { isForbidden }
+                }
+    }
+
+    @EnableWebSecurity
+    @EnableWebMvc
+    open class ExceptionHandlingConfig : WebSecurityConfigurerAdapter() {
+        override fun configure(http: HttpSecurity) {
+            http {
+                authorizeRequests {
+                    authorize(anyRequest, authenticated)
+                }
+                exceptionHandling { }
+            }
+        }
+    }
+
+    @Test(expected = AccessDeniedException::class)
+    fun `request when exception handling disabled then throws exception`() {
+        this.spring.register(ExceptionHandlingDisabledConfig::class.java).autowire()
+
+        this.mockMvc.get("/")
+    }
+
+    @EnableWebSecurity
+    open class ExceptionHandlingDisabledConfig : WebSecurityConfigurerAdapter() {
+        override fun configure(http: HttpSecurity) {
+            http {
+                authorizeRequests {
+                    authorize(anyRequest, authenticated)
+                }
+                exceptionHandling {
+                    disable()
+                }
+            }
+        }
+    }
+
+    @Test
+    fun `exception handling when custom access denied page then redirects to custom page`() {
+        this.spring.register(AccessDeniedPageConfig::class.java).autowire()
+
+        this.mockMvc.get("/admin") {
+            with(user(withUsername("user").password("password").roles("USER").build()))
+        }.andExpect {
+            status { isForbidden }
+            forwardedUrl("/access-denied")
+        }
+    }
+
+    @EnableWebSecurity
+    @EnableWebMvc
+    open class AccessDeniedPageConfig : WebSecurityConfigurerAdapter() {
+        override fun configure(http: HttpSecurity) {
+            http {
+                authorizeRequests {
+                    authorize("/admin", hasAuthority("ROLE_ADMIN"))
+                    authorize(anyRequest, authenticated)
+                }
+                exceptionHandling {
+                    accessDeniedPage = "/access-denied"
+                }
+            }
+        }
+    }
+
+    @Test
+    fun `exception handling when custom access denied handler then handler used`() {
+        this.spring.register(AccessDeniedHandlerConfig::class.java).autowire()
+
+        this.mockMvc.get("/admin") {
+            with(user(withUsername("user").password("password").roles("USER").build()))
+        }.andExpect {
+            status { isForbidden }
+            forwardedUrl("/access-denied")
+        }
+    }
+
+    @EnableWebSecurity
+    @EnableWebMvc
+    open class AccessDeniedHandlerConfig : WebSecurityConfigurerAdapter() {
+        override fun configure(http: HttpSecurity) {
+            val customAccessDeniedHandler = AccessDeniedHandlerImpl()
+            customAccessDeniedHandler.setErrorPage("/access-denied")
+            http {
+                authorizeRequests {
+                    authorize("/admin", hasAuthority("ROLE_ADMIN"))
+                    authorize(anyRequest, authenticated)
+                }
+                exceptionHandling {
+                    accessDeniedHandler = customAccessDeniedHandler
+                }
+            }
+        }
+    }
+
+    @Test
+    fun `exception handling when default access denied handler for page then handlers used`() {
+        this.spring.register(AccessDeniedHandlerForConfig::class.java).autowire()
+
+        this.mockMvc.get("/admin1") {
+            with(user(withUsername("user").password("password").roles("USER").build()))
+        }.andExpect {
+            status { isForbidden }
+            forwardedUrl("/access-denied1")
+        }
+
+        this.mockMvc.get("/admin2") {
+            with(user(withUsername("user").password("password").roles("USER").build()))
+        }.andExpect {
+            status { isForbidden }
+            forwardedUrl("/access-denied2")
+        }
+    }
+
+    @EnableWebSecurity
+    @EnableWebMvc
+    open class AccessDeniedHandlerForConfig : WebSecurityConfigurerAdapter() {
+        override fun configure(http: HttpSecurity) {
+            val customAccessDeniedHandler1 = AccessDeniedHandlerImpl()
+            customAccessDeniedHandler1.setErrorPage("/access-denied1")
+            val customAccessDeniedHandler2 = AccessDeniedHandlerImpl()
+            customAccessDeniedHandler2.setErrorPage("/access-denied2")
+            http {
+                authorizeRequests {
+                    authorize("/admin1", hasAuthority("ROLE_ADMIN"))
+                    authorize("/admin2", hasAuthority("ROLE_ADMIN"))
+                    authorize(anyRequest, authenticated)
+                }
+                exceptionHandling {
+                    defaultAccessDeniedHandlerFor(customAccessDeniedHandler1, AntPathRequestMatcher("/admin1"))
+                    defaultAccessDeniedHandlerFor(customAccessDeniedHandler2, AntPathRequestMatcher("/admin2"))
+                }
+            }
+        }
+    }
+
+    @Test
+    fun `exception handling when custom authentication entry point then entry point used`() {
+        this.spring.register(AuthenticationEntryPointConfig::class.java).autowire()
+
+        this.mockMvc.get("/")
+                .andExpect {
+                    status { isFound }
+                    redirectedUrl("http://localhost/custom-login")
+                }
+    }
+
+    @EnableWebSecurity
+    @EnableWebMvc
+    open class AuthenticationEntryPointConfig : WebSecurityConfigurerAdapter() {
+        override fun configure(http: HttpSecurity) {
+            http {
+                authorizeRequests {
+                    authorize(anyRequest, authenticated)
+                }
+                exceptionHandling {
+                    authenticationEntryPoint = LoginUrlAuthenticationEntryPoint("/custom-login")
+                }
+            }
+        }
+    }
+
+    @Test
+    fun `exception handling when authentication entry point for page then entry points used`() {
+        this.spring.register(AuthenticationEntryPointForConfig::class.java).autowire()
+
+        this.mockMvc.get("/secured1")
+                .andExpect {
+                    status { isFound }
+                    redirectedUrl("http://localhost/custom-login1")
+                }
+
+        this.mockMvc.get("/secured2")
+                .andExpect {
+                    status { isFound }
+                    redirectedUrl("http://localhost/custom-login2")
+                }
+    }
+
+    @EnableWebSecurity
+    @EnableWebMvc
+    open class AuthenticationEntryPointForConfig : WebSecurityConfigurerAdapter() {
+        override fun configure(http: HttpSecurity) {
+            val customAuthenticationEntryPoint1 = LoginUrlAuthenticationEntryPoint("/custom-login1")
+            val customAuthenticationEntryPoint2 = LoginUrlAuthenticationEntryPoint("/custom-login2")
+            http {
+                authorizeRequests {
+                    authorize(anyRequest, authenticated)
+                }
+                exceptionHandling {
+                    defaultAuthenticationEntryPointFor(customAuthenticationEntryPoint1, AntPathRequestMatcher("/secured1"))
+                    defaultAuthenticationEntryPointFor(customAuthenticationEntryPoint2, AntPathRequestMatcher("/secured2"))
+                }
+            }
+        }
+    }
+}

+ 291 - 0
config/src/test/kotlin/org/springframework/security/config/web/servlet/FormLoginDslTests.kt

@@ -0,0 +1,291 @@
+/*
+ * 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
+
+import org.junit.Rule
+import org.junit.Test
+import org.springframework.beans.factory.annotation.Autowired
+import org.springframework.context.annotation.Configuration
+import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder
+import org.springframework.security.config.annotation.web.builders.HttpSecurity
+import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity
+import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter
+import org.springframework.security.config.test.SpringTestRule
+import org.springframework.security.core.userdetails.User
+import org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestBuilders.formLogin
+import org.springframework.security.web.authentication.SimpleUrlAuthenticationFailureHandler
+import org.springframework.security.web.authentication.SimpleUrlAuthenticationSuccessHandler
+import org.springframework.stereotype.Controller
+import org.springframework.test.web.servlet.MockMvc
+import org.springframework.test.web.servlet.get
+import org.springframework.test.web.servlet.result.MockMvcResultMatchers.redirectedUrl
+import org.springframework.test.web.servlet.result.MockMvcResultMatchers.status
+import org.springframework.web.bind.annotation.GetMapping
+
+/**
+ * Tests for [FormLoginDsl]
+ *
+ * @author Eleftheria Stein
+ */
+class FormLoginDslTests {
+    @Rule
+    @JvmField
+    val spring = SpringTestRule()
+
+    @Autowired
+    lateinit var mockMvc: MockMvc
+
+    @Test
+    fun `login page when form login configured then default login page created`() {
+        this.spring.register(FormLoginConfig::class.java, UserConfig::class.java).autowire()
+
+        this.mockMvc.get("/login")
+                .andExpect {
+                    status { isOk }
+                }
+    }
+
+    @Test
+    fun `login when success then redirects to home`() {
+        this.spring.register(FormLoginConfig::class.java, UserConfig::class.java).autowire()
+
+        this.mockMvc.perform(formLogin())
+                .andExpect {
+                    status().isFound
+                    redirectedUrl("/")
+                }
+    }
+
+    @Test
+    fun `login when failure then redirects to login page with error`() {
+        this.spring.register(FormLoginConfig::class.java, UserConfig::class.java).autowire()
+
+        this.mockMvc.perform(formLogin().password("invalid"))
+                .andExpect {
+                    status().isFound
+                    redirectedUrl("/login?error")
+                }
+    }
+
+    @EnableWebSecurity
+    open class FormLoginConfig : WebSecurityConfigurerAdapter() {
+        override fun configure(http: HttpSecurity) {
+            http {
+                formLogin {}
+            }
+        }
+    }
+
+    @Test
+    fun `request when secure then redirects to default login page`() {
+        this.spring.register(AllSecuredConfig::class.java, UserConfig::class.java).autowire()
+
+        this.mockMvc.get("/")
+                .andExpect {
+                    status { isFound }
+                    redirectedUrl("http://localhost/login")
+                }
+    }
+
+    @EnableWebSecurity
+    open class AllSecuredConfig : WebSecurityConfigurerAdapter() {
+        override fun configure(http: HttpSecurity) {
+            http {
+                formLogin {}
+                authorizeRequests {
+                    authorize(anyRequest, authenticated)
+                }
+            }
+        }
+    }
+
+    @Test
+    fun `request when secure and custom login page then redirects to custom login page`() {
+        this.spring.register(LoginPageConfig::class.java, UserConfig::class.java).autowire()
+
+        this.mockMvc.get("/")
+                .andExpect {
+                    status { isFound }
+                    redirectedUrl("http://localhost/log-in")
+                }
+    }
+
+    @EnableWebSecurity
+    open class LoginPageConfig : WebSecurityConfigurerAdapter() {
+        override fun configure(http: HttpSecurity) {
+            http {
+                formLogin {
+                    loginPage = "/log-in"
+                }
+                authorizeRequests {
+                    authorize(anyRequest, authenticated)
+                }
+            }
+        }
+    }
+
+    @Test
+    fun `login when custom success handler then used`() {
+        this.spring.register(SuccessHandlerConfig::class.java, UserConfig::class.java).autowire()
+
+        this.mockMvc.perform(formLogin())
+                .andExpect {
+                    status().isFound
+                    redirectedUrl("/success")
+                }
+    }
+
+    @EnableWebSecurity
+    open class SuccessHandlerConfig : WebSecurityConfigurerAdapter() {
+        override fun configure(http: HttpSecurity) {
+            http {
+                formLogin {
+                    authenticationSuccessHandler = SimpleUrlAuthenticationSuccessHandler("/success")
+                }
+            }
+        }
+    }
+
+    @Test
+    fun `login when custom failure handler then used`() {
+        this.spring.register(FailureHandlerConfig::class.java, UserConfig::class.java).autowire()
+
+        this.mockMvc.perform(formLogin().password("invalid"))
+                .andExpect {
+                    status().isFound
+                    redirectedUrl("/failure")
+                }
+    }
+
+    @EnableWebSecurity
+    open class FailureHandlerConfig : WebSecurityConfigurerAdapter() {
+        override fun configure(http: HttpSecurity) {
+            http {
+                formLogin {
+                    authenticationFailureHandler = SimpleUrlAuthenticationFailureHandler("/failure")
+                }
+            }
+        }
+    }
+
+    @Test
+    fun `login when custom failure url then used`() {
+        this.spring.register(FailureHandlerConfig::class.java, UserConfig::class.java).autowire()
+
+        this.mockMvc.perform(formLogin().password("invalid"))
+                .andExpect {
+                    status().isFound
+                    redirectedUrl("/failure")
+                }
+    }
+
+    @EnableWebSecurity
+    open class FailureUrlConfig : WebSecurityConfigurerAdapter() {
+        override fun configure(http: HttpSecurity) {
+            http {
+                formLogin {
+                    failureUrl = "/failure"
+                }
+            }
+        }
+    }
+
+    @Test
+    fun `login when custom login processing url then used`() {
+        this.spring.register(LoginProcessingUrlConfig::class.java, UserConfig::class.java).autowire()
+
+        this.mockMvc.perform(formLogin("/custom"))
+                .andExpect {
+                    status().isFound
+                    redirectedUrl("/")
+                }
+    }
+
+    @EnableWebSecurity
+    open class LoginProcessingUrlConfig : WebSecurityConfigurerAdapter() {
+        override fun configure(http: HttpSecurity) {
+            http {
+                formLogin {
+                    loginProcessingUrl = "/custom"
+                }
+            }
+        }
+    }
+
+    @Test
+    fun `login when default success url then redirected to url`() {
+        this.spring.register(DefaultSuccessUrlConfig::class.java, UserConfig::class.java).autowire()
+
+        this.mockMvc.perform(formLogin())
+                .andExpect {
+                    status().isFound
+                    redirectedUrl("/custom")
+                }
+    }
+
+    @EnableWebSecurity
+    open class DefaultSuccessUrlConfig : WebSecurityConfigurerAdapter() {
+        override fun configure(http: HttpSecurity) {
+            http {
+                formLogin {
+                    defaultSuccessUrl("/custom", true)
+                }
+            }
+        }
+    }
+
+    @Test
+    fun `login when permit all then login page not protected`() {
+        this.spring.register(PermitAllConfig::class.java, UserConfig::class.java).autowire()
+
+        this.mockMvc.get("/custom/login")
+                .andExpect {
+                    status { isOk }
+                }
+    }
+
+    @EnableWebSecurity
+    open class PermitAllConfig : WebSecurityConfigurerAdapter() {
+        override fun configure(http: HttpSecurity) {
+            http {
+                authorizeRequests {
+                    authorize(anyRequest, authenticated)
+                }
+                formLogin {
+                    loginPage = "/custom/login"
+                    permitAll()
+                }
+            }
+        }
+
+        @Controller
+        class LoginController {
+            @GetMapping("/custom/login")
+            fun loginPage() {}
+        }
+    }
+
+    @Configuration
+    open class UserConfig {
+        @Autowired
+        fun configureGlobal(auth: AuthenticationManagerBuilder) {
+            auth
+                    .inMemoryAuthentication()
+                    .withUser(User.withDefaultPasswordEncoder().username("user").password("password").roles("USER"))
+        }
+    }
+}

+ 94 - 0
config/src/test/kotlin/org/springframework/security/config/web/servlet/HeadersDslTests.kt

@@ -0,0 +1,94 @@
+/*
+ * 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
+
+import org.junit.Rule
+import org.junit.Test
+import org.springframework.beans.factory.annotation.Autowired
+import org.springframework.http.HttpHeaders
+import org.springframework.security.config.annotation.web.builders.HttpSecurity
+import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity
+import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter
+import org.springframework.security.config.test.SpringTestRule
+import org.springframework.security.web.header.writers.frameoptions.XFrameOptionsHeaderWriter
+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.servlet.MockMvc
+import org.springframework.test.web.servlet.get
+
+/**
+ * Tests for [HeadersDsl]
+ *
+ * @author Eleftheria Stein
+ */
+class HeadersDslTests {
+    @Rule
+    @JvmField
+    var spring = SpringTestRule()
+
+    @Autowired
+    lateinit var mockMvc: MockMvc
+
+    @Test
+    fun `headers when defaults enabled then default headers in response`() {
+        this.spring.register(DefaultHeadersConfig::class.java).autowire()
+
+        this.mockMvc.get("/") {
+            secure = true
+        }.andExpect {
+            header { string(ContentTypeOptionsServerHttpHeadersWriter.X_CONTENT_OPTIONS, "nosniff") }
+            header { string(XFrameOptionsServerHttpHeadersWriter.X_FRAME_OPTIONS, XFrameOptionsHeaderWriter.XFrameOptionsMode.DENY.name) }
+            header { string(StrictTransportSecurityServerHttpHeadersWriter.STRICT_TRANSPORT_SECURITY, "max-age=31536000 ; includeSubDomains") }
+            header { string(HttpHeaders.CACHE_CONTROL, "no-cache, no-store, max-age=0, must-revalidate") }
+            header { string(HttpHeaders.EXPIRES, "0") }
+            header { string(HttpHeaders.PRAGMA, "no-cache") }
+            header { string(XXssProtectionServerHttpHeadersWriter.X_XSS_PROTECTION, "1; mode=block") }
+        }
+    }
+
+    @EnableWebSecurity
+    open class DefaultHeadersConfig : WebSecurityConfigurerAdapter() {
+        override fun configure(http: HttpSecurity) {
+            http {
+                headers { }
+            }
+        }
+    }
+
+    @Test
+    fun `headers when feature policy configured then header in response`() {
+        this.spring.register(FeaturePolicyConfig::class.java).autowire()
+
+        this.mockMvc.get("/")
+                .andExpect {
+                    header { string("Feature-Policy", "geolocation 'self'") }
+                }
+    }
+
+    @EnableWebSecurity
+    open class FeaturePolicyConfig : WebSecurityConfigurerAdapter() {
+        override fun configure(http: HttpSecurity) {
+            http {
+                headers {
+                    featurePolicy(policyDirectives = "geolocation 'self'")
+                }
+            }
+        }
+    }
+}

+ 205 - 0
config/src/test/kotlin/org/springframework/security/config/web/servlet/HttpBasicDslTests.kt

@@ -0,0 +1,205 @@
+/*
+ * 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
+
+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.annotation.Bean
+import org.springframework.context.annotation.Configuration
+import org.springframework.security.authentication.AuthenticationDetailsSource
+import org.springframework.security.config.annotation.web.builders.HttpSecurity
+import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity
+import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter
+import org.springframework.security.config.test.SpringTestRule
+import org.springframework.security.core.AuthenticationException
+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.httpBasic
+import org.springframework.security.web.AuthenticationEntryPoint
+import org.springframework.test.web.servlet.MockMvc
+import org.springframework.test.web.servlet.get
+import org.springframework.web.bind.annotation.GetMapping
+import org.springframework.web.bind.annotation.RestController
+import javax.servlet.http.HttpServletRequest
+import javax.servlet.http.HttpServletResponse
+
+/**
+ * Tests for [HttpBasicDsl]
+ *
+ * @author Eleftheria Stein
+ */
+class HttpBasicDslTests {
+    @Rule
+    @JvmField
+    val spring = SpringTestRule()
+
+    @Autowired
+    lateinit var mockMvc: MockMvc
+
+    @Test
+    fun `http basic when configured then insecure request cannot access`() {
+        this.spring.register(HttpBasicConfig::class.java).autowire()
+
+        this.mockMvc.get("/")
+                .andExpect {
+                    status { isUnauthorized }
+                }
+    }
+
+    @Test
+    fun `http basic when configured then response includes basic challenge`() {
+        this.spring.register(HttpBasicConfig::class.java).autowire()
+
+        this.mockMvc.get("/")
+                .andExpect {
+                    header { string("WWW-Authenticate", "Basic realm=\"Realm\"") }
+                }
+    }
+
+    @Test
+    fun `http basic when valid user then permitted`() {
+        this.spring.register(HttpBasicConfig::class.java, UserConfig::class.java, MainController::class.java).autowire()
+
+        this.mockMvc.get("/") {
+            with(httpBasic("user", "password"))
+        }.andExpect {
+            status { isOk }
+        }
+    }
+
+    @EnableWebSecurity
+    open class HttpBasicConfig : WebSecurityConfigurerAdapter() {
+        override fun configure(http: HttpSecurity) {
+            http {
+                httpBasic {}
+                authorizeRequests {
+                    authorize(anyRequest, authenticated)
+                }
+            }
+        }
+    }
+
+    @Test
+    fun httpBasicWhenCustomRealmThenUsed() {
+        this.spring.register(CustomRealmConfig::class.java).autowire()
+
+        this.mockMvc.get("/")
+                .andExpect {
+                    header { string("WWW-Authenticate", "Basic realm=\"Custom Realm\"") }
+                }
+    }
+
+    @EnableWebSecurity
+    open class CustomRealmConfig : WebSecurityConfigurerAdapter() {
+        override fun configure(http: HttpSecurity) {
+            http {
+                httpBasic {
+                    realmName = "Custom Realm"
+                }
+                authorizeRequests {
+                    authorize(anyRequest, authenticated)
+                }
+            }
+        }
+    }
+
+    @Test
+    fun `http basic when custom authentication entry point then used`() {
+        this.spring.register(CustomAuthenticationEntryPointConfig::class.java).autowire()
+
+        this.mockMvc.get("/")
+
+        verify<AuthenticationEntryPoint>(CustomAuthenticationEntryPointConfig.ENTRY_POINT)
+                .commence(any(HttpServletRequest::class.java),
+                        any(HttpServletResponse::class.java),
+                        any(AuthenticationException::class.java))
+    }
+
+    @EnableWebSecurity
+    open class CustomAuthenticationEntryPointConfig : WebSecurityConfigurerAdapter() {
+        companion object {
+            var ENTRY_POINT: AuthenticationEntryPoint = mock(AuthenticationEntryPoint::class.java)
+        }
+
+        override fun configure(http: HttpSecurity) {
+            http {
+                httpBasic {
+                    authenticationEntryPoint = ENTRY_POINT
+                }
+                authorizeRequests {
+                    authorize(anyRequest, authenticated)
+                }
+            }
+        }
+    }
+
+    @Test
+    fun `http basic when custom authentication details source then used`() {
+        this.spring.register(CustomAuthenticationDetailsSourceConfig::class.java,
+                UserConfig::class.java, MainController::class.java).autowire()
+
+        this.mockMvc.get("/") {
+            with(httpBasic("username", "password"))
+        }
+
+        verify(CustomAuthenticationDetailsSourceConfig.AUTHENTICATION_DETAILS_SOURCE)
+                .buildDetails(any(HttpServletRequest::class.java))
+    }
+
+    @EnableWebSecurity
+    open class CustomAuthenticationDetailsSourceConfig : WebSecurityConfigurerAdapter() {
+        companion object {
+            var AUTHENTICATION_DETAILS_SOURCE = mock(AuthenticationDetailsSource::class.java) as AuthenticationDetailsSource<HttpServletRequest, *>
+        }
+
+        override fun configure(http: HttpSecurity) {
+            http {
+                httpBasic {
+                    authenticationDetailsSource = AUTHENTICATION_DETAILS_SOURCE
+                }
+                authorizeRequests {
+                    authorize(anyRequest, authenticated)
+                }
+            }
+        }
+    }
+
+    @Configuration
+    open class UserConfig {
+        @Bean
+        open fun userDetailsService(): UserDetailsService {
+            val userDetails = User.withDefaultPasswordEncoder()
+                    .username("user")
+                    .password("password")
+                    .roles("USER")
+                    .build()
+            return InMemoryUserDetailsManager(userDetails)
+        }
+    }
+
+    @RestController
+    class MainController {
+        @GetMapping("/")
+        fun main() {
+        }
+    }
+}

+ 215 - 0
config/src/test/kotlin/org/springframework/security/config/web/servlet/HttpSecurityDslTests.kt

@@ -0,0 +1,215 @@
+/*
+ * 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
+
+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.HttpHeaders
+import org.springframework.security.config.annotation.web.builders.HttpSecurity
+import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity
+import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter
+import org.springframework.security.config.test.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.web.header.writers.frameoptions.XFrameOptionsHeaderWriter
+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.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.web.servlet.config.annotation.EnableWebMvc
+
+/**
+ * Tests for [HttpSecurityDsl]
+ *
+ * @author Eleftheria Stein
+ */
+class HttpSecurityDslTests {
+    @Rule
+    @JvmField
+    val spring = SpringTestRule()
+
+    @Autowired
+    lateinit var mockMvc: MockMvc
+
+    @Test
+    fun `post when default security configured then CSRF prevents the request`() {
+        this.spring.register(DefaultSecurityConfig::class.java).autowire()
+
+        this.mockMvc.post("/")
+                .andExpect {
+                    status { isForbidden }
+                }
+    }
+
+    @Test
+    fun `when default security configured then default headers are in the response`() {
+        this.spring.register(DefaultSecurityConfig::class.java).autowire()
+
+        this.mockMvc.get("/") {
+            secure = true
+        }.andExpect {
+            header {
+                string(ContentTypeOptionsServerHttpHeadersWriter.X_CONTENT_OPTIONS, "nosniff")
+            }
+            header {
+                string(XFrameOptionsServerHttpHeadersWriter.X_FRAME_OPTIONS, XFrameOptionsHeaderWriter.XFrameOptionsMode.DENY.name)
+            }
+            header {
+                string(StrictTransportSecurityServerHttpHeadersWriter.STRICT_TRANSPORT_SECURITY, "max-age=31536000 ; includeSubDomains")
+            }
+            header {
+                string(HttpHeaders.CACHE_CONTROL, "no-cache, no-store, max-age=0, must-revalidate")
+            }
+            header {
+                string(HttpHeaders.EXPIRES, "0")
+            }
+            header {
+                string(HttpHeaders.PRAGMA, "no-cache")
+            }
+            header {
+                string(XXssProtectionServerHttpHeadersWriter.X_XSS_PROTECTION, "1; mode=block")
+            }
+        }
+    }
+
+    @EnableWebSecurity
+    open class DefaultSecurityConfig : WebSecurityConfigurerAdapter() {
+        override fun configure(http: HttpSecurity) {
+            http {}
+        }
+
+        @Configuration
+        open class UserConfig {
+            @Bean
+            open fun userDetailsService(): UserDetailsService {
+                val userDetails = User.withDefaultPasswordEncoder()
+                        .username("user")
+                        .password("password")
+                        .roles("USER")
+                        .build()
+                return InMemoryUserDetailsManager(userDetails)
+            }
+        }
+    }
+
+    @Test
+    fun `request when it does not match the security request matcher then the security rules do not apply`() {
+        this.spring.register(SecurityRequestMatcherConfig::class.java).autowire()
+
+        this.mockMvc.get("/")
+                .andExpect {
+                    status { isNotFound }
+                }
+    }
+
+    @Test
+    fun `request when it matches the security request matcher then the security rules apply`() {
+        this.spring.register(SecurityRequestMatcherConfig::class.java).autowire()
+
+        this.mockMvc.get("/path")
+                .andExpect {
+                    status { isForbidden }
+                }
+    }
+
+    @EnableWebSecurity
+    open class SecurityRequestMatcherConfig : WebSecurityConfigurerAdapter() {
+        override fun configure(http: HttpSecurity) {
+            http {
+                securityMatcher(RegexRequestMatcher("/path", null))
+                authorizeRequests {
+                    authorize(anyRequest, authenticated)
+                }
+            }
+        }
+    }
+
+    @Test
+    fun `request when it does not match the security pattern matcher then the security rules do not apply`() {
+        this.spring.register(SecurityPatternMatcherConfig::class.java).autowire()
+
+        this.mockMvc.get("/")
+                .andExpect {
+                    status { isNotFound }
+                }
+    }
+
+    @Test
+    fun `request when it matches the security pattern matcher then the security rules apply`() {
+        this.spring.register(SecurityPatternMatcherConfig::class.java).autowire()
+
+        this.mockMvc.get("/path")
+                .andExpect {
+                    status { isForbidden }
+                }
+    }
+
+    @EnableWebSecurity
+    @EnableWebMvc
+    open class SecurityPatternMatcherConfig : WebSecurityConfigurerAdapter() {
+        override fun configure(http: HttpSecurity) {
+            http {
+                securityMatcher("/path")
+                authorizeRequests {
+                    authorize(anyRequest, authenticated)
+                }
+            }
+        }
+    }
+
+    @Test
+    fun `security pattern matcher when used with security request matcher then both apply`() {
+        this.spring.register(MultiMatcherConfig::class.java).autowire()
+
+        this.mockMvc.get("/path1")
+                .andExpect {
+                    status { isForbidden }
+                }
+
+        this.mockMvc.get("/path2")
+                .andExpect {
+                    status { isForbidden }
+                }
+
+        this.mockMvc.get("/path3")
+                .andExpect {
+                    status { isNotFound }
+                }
+    }
+
+    @EnableWebSecurity
+    @EnableWebMvc
+    open class MultiMatcherConfig : WebSecurityConfigurerAdapter() {
+        override fun configure(http: HttpSecurity) {
+            http {
+                securityMatcher("/path1")
+                securityMatcher(RegexRequestMatcher("/path2", null))
+                authorizeRequests {
+                    authorize(anyRequest, authenticated)
+                }
+            }
+        }
+    }
+}

+ 310 - 0
config/src/test/kotlin/org/springframework/security/config/web/servlet/LogoutDslTests.kt

@@ -0,0 +1,310 @@
+/*
+ * 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
+
+import org.assertj.core.api.Assertions.assertThat
+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.mock.web.MockHttpSession
+import org.springframework.security.authentication.TestingAuthenticationToken
+import org.springframework.security.config.annotation.web.builders.HttpSecurity
+import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity
+import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter
+import org.springframework.security.config.test.SpringTestRule
+import org.springframework.security.core.context.SecurityContextHolder
+import org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.csrf
+import org.springframework.security.web.authentication.logout.LogoutHandler
+import org.springframework.security.web.authentication.logout.SimpleUrlLogoutSuccessHandler
+import org.springframework.security.web.context.HttpSessionSecurityContextRepository
+import org.springframework.security.web.util.matcher.AntPathRequestMatcher
+import org.springframework.test.web.servlet.MockMvc
+import org.springframework.test.web.servlet.post
+
+/**
+ * Tests for [LogoutDsl]
+ *
+ * @author Eleftheria Stein
+ */
+class LogoutDslTests {
+    @Rule
+    @JvmField
+    val spring = SpringTestRule()
+
+    @Autowired
+    lateinit var mockMvc: MockMvc
+
+    @Test
+    fun `logout when custom logout url then custom url used`() {
+        this.spring.register(CustomLogoutUrlConfig::class.java).autowire()
+
+        this.mockMvc.post("/custom/logout") {
+            with(csrf())
+        }.andExpect {
+            status { isFound }
+            redirectedUrl("/login?logout")
+        }
+    }
+
+    @EnableWebSecurity
+    open class CustomLogoutUrlConfig : WebSecurityConfigurerAdapter() {
+        override fun configure(http: HttpSecurity) {
+            http {
+                logout {
+                    logoutUrl = "/custom/logout"
+                }
+            }
+        }
+    }
+
+    @Test
+    fun `logout when custom logout request matcher then custom request matcher used`() {
+        this.spring.register(CustomLogoutRequestMatcherConfig::class.java).autowire()
+
+        this.mockMvc.post("/custom/logout") {
+            with(csrf())
+        }.andExpect {
+            status { isFound }
+            redirectedUrl("/login?logout")
+        }
+    }
+
+    @EnableWebSecurity
+    open class CustomLogoutRequestMatcherConfig : WebSecurityConfigurerAdapter() {
+        override fun configure(http: HttpSecurity) {
+            http {
+                logout {
+                    logoutRequestMatcher = AntPathRequestMatcher("/custom/logout")
+                }
+            }
+        }
+    }
+
+    @Test
+    fun `logout when custom success url then redirects to success url`() {
+        this.spring.register(SuccessUrlConfig::class.java).autowire()
+
+        this.mockMvc.post("/logout") {
+            with(csrf())
+        }.andExpect {
+            status { isFound }
+            redirectedUrl("/login")
+        }
+    }
+
+    @EnableWebSecurity
+    open class SuccessUrlConfig : WebSecurityConfigurerAdapter() {
+        override fun configure(http: HttpSecurity) {
+            http {
+                logout {
+                    logoutSuccessUrl = "/login"
+                }
+            }
+        }
+    }
+
+    @Test
+    fun `logout when custom success handler then redirects to success url`() {
+        this.spring.register(SuccessHandlerConfig::class.java).autowire()
+
+        this.mockMvc.post("/logout") {
+            with(csrf())
+        }.andExpect {
+            status { isFound }
+            redirectedUrl("/")
+        }
+    }
+
+    @EnableWebSecurity
+    open class SuccessHandlerConfig : WebSecurityConfigurerAdapter() {
+        override fun configure(http: HttpSecurity) {
+            http {
+                logout {
+                    logoutSuccessHandler = SimpleUrlLogoutSuccessHandler()
+                }
+            }
+        }
+    }
+
+    @Test
+    fun `logout when permit all then logout allowed`() {
+        this.spring.register(PermitAllConfig::class.java).autowire()
+
+        this.mockMvc.post("/custom/logout") {
+            with(csrf())
+        }.andExpect {
+            status { isFound }
+            redirectedUrl("/login?logout")
+        }
+    }
+
+    @EnableWebSecurity
+    open class PermitAllConfig : WebSecurityConfigurerAdapter() {
+        override fun configure(http: HttpSecurity) {
+            http {
+                authorizeRequests {
+                    authorize(anyRequest, authenticated)
+                }
+                logout {
+                    logoutUrl = "/custom/logout"
+                    permitAll()
+                }
+            }
+        }
+    }
+
+    @Test
+    fun `logout when clear authentication false then authentication not cleared`() {
+        this.spring.register(ClearAuthenticationFalseConfig::class.java).autowire()
+        val currentContext = SecurityContextHolder.createEmptyContext()
+        currentContext.authentication = TestingAuthenticationToken("user", "password", "ROLE_USER")
+        val currentSession = MockHttpSession()
+        currentSession.setAttribute(HttpSessionSecurityContextRepository.SPRING_SECURITY_CONTEXT_KEY, currentContext)
+
+        this.mockMvc.post("/logout") {
+            with(csrf())
+            session = currentSession
+        }.andExpect {
+            status { isFound }
+            redirectedUrl("/login?logout")
+        }
+
+        assertThat(currentContext.authentication).isNotNull
+    }
+
+    @EnableWebSecurity
+    open class ClearAuthenticationFalseConfig : WebSecurityConfigurerAdapter() {
+        override fun configure(http: HttpSecurity) {
+            http {
+                logout {
+                    clearAuthentication = false
+                }
+            }
+        }
+    }
+
+    @Test
+    fun `logout when invalidate http session false then session not invalidated`() {
+        this.spring.register(InvalidateHttpSessionFalseConfig::class.java).autowire()
+        val currentSession = MockHttpSession()
+
+        this.mockMvc.post("/logout") {
+            with(csrf())
+            session = currentSession
+        }.andExpect {
+            status { isFound }
+            redirectedUrl("/login?logout")
+        }
+
+        assertThat(currentSession.isInvalid).isFalse()
+    }
+
+    @EnableWebSecurity
+    open class InvalidateHttpSessionFalseConfig : WebSecurityConfigurerAdapter() {
+        override fun configure(http: HttpSecurity) {
+            http {
+                logout {
+                    invalidateHttpSession = false
+                }
+            }
+        }
+    }
+
+    @Test
+    fun `logout when delete cookies then cookies are cleared`() {
+        this.spring.register(DeleteCookiesConfig::class.java).autowire()
+
+        this.mockMvc.post("/logout") {
+            with(csrf())
+        }.andExpect {
+            status { isFound }
+            redirectedUrl("/login?logout")
+            cookie { maxAge("remove", 0) }
+        }
+    }
+
+    @EnableWebSecurity
+    open class DeleteCookiesConfig : WebSecurityConfigurerAdapter() {
+        override fun configure(http: HttpSecurity) {
+            http {
+                logout {
+                    deleteCookies("remove")
+                }
+            }
+        }
+    }
+
+    @Test
+    fun `logout when default logout success handler for request then custom handler used`() {
+        this.spring.register(DefaultLogoutSuccessHandlerForConfig::class.java).autowire()
+
+        this.mockMvc.post("/logout/default") {
+            with(csrf())
+        }.andExpect {
+            status { isFound }
+            redirectedUrl("/login?logout")
+        }
+
+        this.mockMvc.post("/logout/custom") {
+            with(csrf())
+        }.andExpect {
+            status { isFound }
+            redirectedUrl("/")
+        }
+    }
+
+    @EnableWebSecurity
+    open class DefaultLogoutSuccessHandlerForConfig : WebSecurityConfigurerAdapter() {
+        override fun configure(http: HttpSecurity) {
+            http {
+                logout {
+                    logoutRequestMatcher = AntPathRequestMatcher("/logout/**")
+                    defaultLogoutSuccessHandlerFor(SimpleUrlLogoutSuccessHandler(), AntPathRequestMatcher("/logout/custom"))
+                }
+            }
+        }
+    }
+
+    @Test
+    fun `logout when custom logout handler then custom handler used`() {
+        this.spring.register(CustomLogoutHandlerConfig::class.java).autowire()
+
+        this.mockMvc.post("/logout") {
+            with(csrf())
+        }
+
+        verify(CustomLogoutHandlerConfig.HANDLER).logout(any(), any(), any())
+    }
+
+    @EnableWebSecurity
+    open class CustomLogoutHandlerConfig : WebSecurityConfigurerAdapter() {
+        companion object {
+            var HANDLER: LogoutHandler = mock(LogoutHandler::class.java)
+        }
+
+        override fun configure(http: HttpSecurity) {
+            http {
+                logout {
+                    addLogoutHandler(HANDLER)
+                }
+            }
+        }
+    }
+}

+ 144 - 0
config/src/test/kotlin/org/springframework/security/config/web/servlet/OAuth2ClientDslTests.kt

@@ -0,0 +1,144 @@
+/*
+ * 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
+
+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.annotation.Bean
+import org.springframework.context.annotation.Configuration
+import org.springframework.security.config.annotation.web.builders.HttpSecurity
+import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity
+import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter
+import org.springframework.security.config.oauth2.client.CommonOAuth2Provider
+import org.springframework.security.config.test.SpringTestRule
+import org.springframework.security.oauth2.client.endpoint.OAuth2AccessTokenResponseClient
+import org.springframework.security.oauth2.client.endpoint.OAuth2AuthorizationCodeGrantRequest
+import org.springframework.security.oauth2.client.registration.ClientRegistrationRepository
+import org.springframework.security.oauth2.client.registration.InMemoryClientRegistrationRepository
+import org.springframework.security.oauth2.client.web.AuthorizationRequestRepository
+import org.springframework.security.oauth2.client.web.OAuth2AuthorizedClientRepository
+import org.springframework.security.oauth2.core.OAuth2AccessToken
+import org.springframework.security.oauth2.core.endpoint.OAuth2AccessTokenResponse
+import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationRequest
+import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames
+import org.springframework.test.web.servlet.MockMvc
+import org.springframework.test.web.servlet.get
+
+/**
+ * Tests for [OAuth2ClientDsl]
+ *
+ * @author Eleftheria Stein
+ */
+class OAuth2ClientDslTests {
+    @Rule
+    @JvmField
+    val spring = SpringTestRule()
+
+    @Autowired
+    lateinit var mockMvc: MockMvc
+
+    @Test
+    fun `oauth2Client when custom client registration repository then bean is not required`() {
+        this.spring.register(ClientRepoConfig::class.java).autowire()
+    }
+
+    @EnableWebSecurity
+    open class ClientRepoConfig : WebSecurityConfigurerAdapter() {
+        override fun configure(http: HttpSecurity) {
+            http {
+                oauth2Client {
+                    clientRegistrationRepository = InMemoryClientRegistrationRepository(
+                            CommonOAuth2Provider.GOOGLE
+                                    .getBuilder("google").clientId("clientId").clientSecret("clientSecret")
+                                    .build()
+                    )
+                }
+            }
+        }
+    }
+
+    @Test
+    fun `oauth2Client when custom authorized client repository then repository used`() {
+        this.spring.register(ClientRepositoryConfig::class.java, ClientConfig::class.java).autowire()
+        val authorizationRequest = OAuth2AuthorizationRequest
+                .authorizationCode()
+                .state("test")
+                .clientId("clientId")
+                .authorizationUri("https://test")
+                .redirectUri("http://localhost/callback")
+                .attributes(mapOf(Pair(OAuth2ParameterNames.REGISTRATION_ID, "registrationId")))
+                .build()
+        `when`(ClientRepositoryConfig.REQUEST_REPOSITORY.loadAuthorizationRequest(any()))
+                .thenReturn(authorizationRequest)
+        `when`(ClientRepositoryConfig.REQUEST_REPOSITORY.removeAuthorizationRequest(any(), any()))
+                .thenReturn(authorizationRequest)
+        `when`(ClientRepositoryConfig.CLIENT.getTokenResponse(any()))
+                .thenReturn(OAuth2AccessTokenResponse
+                        .withToken("token")
+                        .tokenType(OAuth2AccessToken.TokenType.BEARER)
+                        .build())
+
+        this.mockMvc.get("/callback") {
+            param("state", "test")
+            param("code", "123")
+        }
+
+        verify(ClientRepositoryConfig.CLIENT_REPOSITORY).saveAuthorizedClient(any(), any(), any(), any())
+    }
+
+    @EnableWebSecurity
+    open class ClientRepositoryConfig : WebSecurityConfigurerAdapter() {
+        companion object {
+            var REQUEST_REPOSITORY: AuthorizationRequestRepository<OAuth2AuthorizationRequest> = mock(AuthorizationRequestRepository::class.java) as AuthorizationRequestRepository<OAuth2AuthorizationRequest>
+            var CLIENT: OAuth2AccessTokenResponseClient<OAuth2AuthorizationCodeGrantRequest> = mock(OAuth2AccessTokenResponseClient::class.java) as OAuth2AccessTokenResponseClient<OAuth2AuthorizationCodeGrantRequest>
+            var CLIENT_REPOSITORY: OAuth2AuthorizedClientRepository = mock(OAuth2AuthorizedClientRepository::class.java)
+        }
+
+        override fun configure(http: HttpSecurity) {
+            http {
+                oauth2Client {
+                    authorizedClientRepository = CLIENT_REPOSITORY
+                    authorizationCodeGrant {
+                        authorizationRequestRepository = REQUEST_REPOSITORY
+                        accessTokenResponseClient = CLIENT
+                    }
+                }
+                authorizeRequests {
+                    authorize(anyRequest, authenticated)
+                }
+            }
+        }
+    }
+
+    @Configuration
+    open class ClientConfig {
+        @Bean
+        open fun clientRegistrationRepository(): ClientRegistrationRepository {
+            return InMemoryClientRegistrationRepository(
+                    CommonOAuth2Provider.GOOGLE
+                            .getBuilder("google")
+                            .registrationId("registrationId")
+                            .clientId("clientId")
+                            .clientSecret("clientSecret")
+                            .build()
+            )
+        }
+    }
+}

+ 126 - 0
config/src/test/kotlin/org/springframework/security/config/web/servlet/OAuth2LoginDslTests.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.servlet
+
+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.security.config.annotation.web.builders.HttpSecurity
+import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity
+import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter
+import org.springframework.security.config.oauth2.client.CommonOAuth2Provider
+import org.springframework.security.config.test.SpringTestRule
+import org.springframework.security.oauth2.client.registration.ClientRegistrationRepository
+import org.springframework.security.oauth2.client.registration.InMemoryClientRegistrationRepository
+import org.springframework.test.web.servlet.MockMvc
+import org.springframework.test.web.servlet.get
+import org.springframework.web.bind.annotation.GetMapping
+import org.springframework.web.bind.annotation.RestController
+
+/**
+ * Tests for [OAuth2LoginDsl]
+ *
+ * @author Eleftheria Stein
+ */
+class OAuth2LoginDslTests {
+    @Rule
+    @JvmField
+    val spring = SpringTestRule()
+
+    @Autowired
+    lateinit var mockMvc: MockMvc
+
+    @Test
+    fun `oauth2Login when custom client registration repository then bean is not required`() {
+        this.spring.register(ClientRepoConfig::class.java).autowire()
+    }
+
+    @EnableWebSecurity
+    open class ClientRepoConfig : WebSecurityConfigurerAdapter() {
+        override fun configure(http: HttpSecurity) {
+            http {
+                oauth2Login {
+                    clientRegistrationRepository = InMemoryClientRegistrationRepository(
+                            CommonOAuth2Provider.GOOGLE
+                                    .getBuilder("google").clientId("clientId").clientSecret("clientSecret")
+                                    .build()
+                    )
+                }
+            }
+        }
+    }
+
+    @Test
+    fun `login page when oAuth2Login configured then default login page created`() {
+        this.spring.register(OAuth2LoginConfig::class.java, ClientConfig::class.java).autowire()
+
+        this.mockMvc.get("/login")
+                .andExpect {
+                    status { isOk }
+                }
+    }
+
+    @EnableWebSecurity
+    open class OAuth2LoginConfig : WebSecurityConfigurerAdapter() {
+        override fun configure(http: HttpSecurity) {
+            http {
+                oauth2Login { }
+            }
+        }
+    }
+
+    @Test
+    fun `login page when custom login page then redirected to custom page`() {
+        this.spring.register(LoginPageConfig::class.java, ClientConfig::class.java).autowire()
+
+        this.mockMvc.get("/custom-login")
+                .andExpect {
+                    status { isOk }
+                }
+    }
+
+    @EnableWebSecurity
+    open class LoginPageConfig : WebSecurityConfigurerAdapter() {
+        override fun configure(http: HttpSecurity) {
+            http {
+                oauth2Login {
+                    loginPage = "/custom-login"
+                }
+            }
+        }
+
+        @RestController
+        class LoginController {
+            @GetMapping("/custom-login")
+            fun loginPage() { }
+        }
+    }
+
+    @Configuration
+    open class ClientConfig {
+        @Bean
+        open fun clientRegistrationRepository(): ClientRegistrationRepository {
+            return InMemoryClientRegistrationRepository(
+                    CommonOAuth2Provider.GOOGLE
+                            .getBuilder("google").clientId("clientId").clientSecret("clientSecret")
+                            .build()
+            )
+        }
+    }
+}

+ 156 - 0
config/src/test/kotlin/org/springframework/security/config/web/servlet/OAuth2ResourceServerDslTests.kt

@@ -0,0 +1,156 @@
+/*
+ * 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
+
+import org.junit.Rule
+import org.junit.Test
+import org.mockito.Mockito.*
+import org.springframework.beans.factory.annotation.Autowired
+import org.springframework.context.annotation.Bean
+import org.springframework.security.config.annotation.web.builders.HttpSecurity
+import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity
+import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter
+import org.springframework.security.config.test.SpringTestRule
+import org.springframework.security.oauth2.core.oidc.IdTokenClaimNames.SUB
+import org.springframework.security.oauth2.jwt.Jwt
+import org.springframework.security.oauth2.jwt.JwtDecoder
+import org.springframework.security.oauth2.server.resource.web.BearerTokenResolver
+import org.springframework.security.web.AuthenticationEntryPoint
+import org.springframework.security.web.access.AccessDeniedHandler
+import org.springframework.test.web.servlet.MockMvc
+import org.springframework.test.web.servlet.get
+
+/**
+ * Tests for [OAuth2ResourceServerDsl]
+ *
+ * @author Eleftheria Stein
+ */
+class OAuth2ResourceServerDslTests {
+    @Rule
+    @JvmField
+    val spring = SpringTestRule()
+
+    @Autowired
+    lateinit var mockMvc: MockMvc
+
+    @Test
+    fun `oauth2Resource server when custom entry point then entry point used`() {
+        this.spring.register(EntryPointConfig::class.java).autowire()
+
+        this.mockMvc.get("/")
+
+        verify(EntryPointConfig.ENTRY_POINT).commence(any(), any(), any())
+    }
+
+    @EnableWebSecurity
+    open class EntryPointConfig : WebSecurityConfigurerAdapter() {
+        companion object {
+            var ENTRY_POINT: AuthenticationEntryPoint = mock(AuthenticationEntryPoint::class.java)
+        }
+
+        override fun configure(http: HttpSecurity) {
+            http {
+                authorizeRequests {
+                    authorize(anyRequest, authenticated)
+                }
+                oauth2ResourceServer {
+                    authenticationEntryPoint = ENTRY_POINT
+                    jwt { }
+                }
+            }
+        }
+
+        @Bean
+        open fun jwtDecoder(): JwtDecoder {
+            return mock(JwtDecoder::class.java)
+        }
+    }
+
+    @Test
+    fun `oauth2Resource server when custom bearer token resolver then resolver used`() {
+        this.spring.register(BearerTokenResolverConfig::class.java).autowire()
+
+        this.mockMvc.get("/")
+
+        verify(BearerTokenResolverConfig.RESOLVER).resolve(any())
+    }
+
+    @EnableWebSecurity
+    open class BearerTokenResolverConfig : WebSecurityConfigurerAdapter() {
+        companion object {
+            var RESOLVER: BearerTokenResolver = mock(BearerTokenResolver::class.java)
+        }
+
+        override fun configure(http: HttpSecurity) {
+            http {
+                authorizeRequests {
+                    authorize(anyRequest, authenticated)
+                }
+                oauth2ResourceServer {
+                    bearerTokenResolver = RESOLVER
+                    jwt { }
+                }
+            }
+        }
+
+        @Bean
+        open fun jwtDecoder(): JwtDecoder {
+            return mock(JwtDecoder::class.java)
+        }
+    }
+
+    @Test
+    fun `oauth2Resource server when custom access denied handler then handler used`() {
+        this.spring.register(AccessDeniedHandlerConfig::class.java).autowire()
+        `when`(AccessDeniedHandlerConfig.DECODER.decode(anyString())).thenReturn(
+                Jwt.withTokenValue("token")
+                        .header("alg", "none")
+                        .claim(SUB, "user")
+                        .build())
+
+        this.mockMvc.get("/") {
+            header("Authorization", "Bearer token")
+        }
+
+        verify(AccessDeniedHandlerConfig.DENIED_HANDLER).handle(any(), any(), any())
+    }
+
+    @EnableWebSecurity
+    open class AccessDeniedHandlerConfig : WebSecurityConfigurerAdapter() {
+        companion object {
+            var DENIED_HANDLER: AccessDeniedHandler = mock(AccessDeniedHandler::class.java)
+            var DECODER: JwtDecoder = mock(JwtDecoder::class.java)
+        }
+
+        override fun configure(http: HttpSecurity) {
+            http {
+                authorizeRequests {
+                    authorize(anyRequest, denyAll)
+                }
+                oauth2ResourceServer {
+                    accessDeniedHandler = DENIED_HANDLER
+                    jwt { }
+                }
+            }
+        }
+
+        @Bean
+        open fun jwtDecoder(): JwtDecoder {
+            return DECODER
+        }
+    }
+}

+ 93 - 0
config/src/test/kotlin/org/springframework/security/config/web/servlet/PortMapperDslTests.kt

@@ -0,0 +1,93 @@
+/*
+ * 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
+
+import org.junit.Rule
+import org.junit.Test
+import org.springframework.beans.factory.annotation.Autowired
+import org.springframework.security.config.annotation.web.builders.HttpSecurity
+import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity
+import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter
+import org.springframework.security.config.test.SpringTestRule
+import org.springframework.security.web.PortMapperImpl
+import org.springframework.test.web.servlet.MockMvc
+import org.springframework.test.web.servlet.get
+import java.util.*
+
+/**
+ * Tests for [PortMapperDsl]
+ *
+ * @author Eleftheria Stein
+ */
+class PortMapperDslTests  {
+    @Rule
+    @JvmField
+    val spring = SpringTestRule()
+
+    @Autowired
+    lateinit var mockMvc: MockMvc
+
+    @Test
+    fun `port mapper when specifying map then redirects to https port`() {
+        this.spring.register(PortMapperMapConfig::class.java).autowire()
+
+        this.mockMvc.get("http://localhost:543")
+                .andExpect {
+                    redirectedUrl("https://localhost:123")
+                }
+    }
+
+    @EnableWebSecurity
+    open class PortMapperMapConfig : WebSecurityConfigurerAdapter() {
+        override fun configure(http: HttpSecurity) {
+            http {
+                requiresChannel {
+                    secure(anyRequest, requiresSecure)
+                }
+                portMapper {
+                    map(543, 123)
+                }
+            }
+        }
+    }
+
+    @Test
+    fun `port mapper when specifying custom mapper then redirects to https port`() {
+        this.spring.register(CustomPortMapperConfig::class.java).autowire()
+
+        this.mockMvc.get("http://localhost:543")
+                .andExpect {
+                    redirectedUrl("https://localhost:123")
+                }
+    }
+
+    @EnableWebSecurity
+    open class CustomPortMapperConfig : WebSecurityConfigurerAdapter() {
+        override fun configure(http: HttpSecurity) {
+            val customPortMapper = PortMapperImpl()
+            customPortMapper.setPortMappings(Collections.singletonMap("543", "123"))
+            http {
+                requiresChannel {
+                    secure(anyRequest, requiresSecure)
+                }
+                portMapper {
+                    portMapper = customPortMapper
+                }
+            }
+        }
+    }
+}

+ 90 - 0
config/src/test/kotlin/org/springframework/security/config/web/servlet/RequestCacheDslTests.kt

@@ -0,0 +1,90 @@
+/*
+ * 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
+
+import org.junit.Rule
+import org.junit.Test
+import org.springframework.beans.factory.annotation.Autowired
+import org.springframework.security.config.annotation.web.builders.HttpSecurity
+import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity
+import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter
+import org.springframework.security.config.test.SpringTestRule
+import org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestBuilders.formLogin
+import org.springframework.security.web.savedrequest.NullRequestCache
+import org.springframework.test.web.servlet.MockMvc
+import org.springframework.test.web.servlet.get
+import org.springframework.test.web.servlet.result.MockMvcResultMatchers.redirectedUrl
+
+/**
+ * Tests for [RequestCacheDsl]
+ *
+ * @author Eleftheria Stein
+ */
+class RequestCacheDslTests {
+    @Rule
+    @JvmField
+    val spring = SpringTestRule()
+
+    @Autowired
+    lateinit var mockMvc: MockMvc
+
+    @Test
+    fun `GET when request cache enabled then redirected to cached page`() {
+        this.spring.register(RequestCacheConfig::class.java).autowire()
+
+        this.mockMvc.get("/test")
+
+        this.mockMvc.perform(formLogin())
+                .andExpect {
+                    redirectedUrl("http://localhost/test")
+                }
+    }
+
+    @EnableWebSecurity
+    open class RequestCacheConfig : WebSecurityConfigurerAdapter() {
+        override fun configure(http: HttpSecurity) {
+            http {
+                requestCache { }
+                formLogin { }
+            }
+        }
+    }
+
+    @Test
+    fun `GET when custom request cache then custom request cache used`() {
+        this.spring.register(CustomRequestCacheConfig::class.java).autowire()
+
+        this.mockMvc.get("/test")
+
+        this.mockMvc.perform(formLogin())
+                .andExpect {
+                    redirectedUrl("/")
+                }
+    }
+
+    @EnableWebSecurity
+    open class CustomRequestCacheConfig : WebSecurityConfigurerAdapter() {
+        override fun configure(http: HttpSecurity) {
+            http {
+                requestCache {
+                    requestCache = NullRequestCache()
+                }
+                formLogin { }
+            }
+        }
+    }
+}

+ 138 - 0
config/src/test/kotlin/org/springframework/security/config/web/servlet/RequiresChannelDslTests.kt

@@ -0,0 +1,138 @@
+/*
+ * 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
+
+import org.junit.Rule
+import org.junit.Test
+import org.mockito.Mockito.*
+import org.springframework.beans.factory.annotation.Autowired
+import org.springframework.security.config.annotation.web.builders.HttpSecurity
+import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity
+import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter
+import org.springframework.security.config.test.SpringTestRule
+import org.springframework.security.web.access.channel.ChannelProcessor
+import org.springframework.test.web.servlet.MockMvc
+import org.springframework.test.web.servlet.get
+import org.springframework.test.web.servlet.request.MockMvcRequestBuilders
+import org.springframework.test.web.servlet.result.MockMvcResultMatchers
+import org.springframework.test.web.servlet.result.MockMvcResultMatchers.redirectedUrl
+import org.springframework.test.web.servlet.result.MockMvcResultMatchers.status
+import org.springframework.web.bind.annotation.RequestMapping
+import org.springframework.web.bind.annotation.RestController
+import org.springframework.web.servlet.config.annotation.EnableWebMvc
+
+/**
+ * Tests for [RequiresChannelDsl]
+ *
+ * @author Eleftheria Stein
+ */
+class RequiresChannelDslTests {
+    @Rule
+    @JvmField
+    val spring = SpringTestRule()
+
+    @Autowired
+    lateinit var mockMvc: MockMvc
+
+    @Test
+    fun `requires channel when requires secure then redirects to https`() {
+        this.spring.register(RequiresSecureConfig::class.java).autowire()
+
+        this.mockMvc.get("/")
+                .andExpect {
+                    redirectedUrl("https://localhost/")
+                }
+    }
+
+    @EnableWebSecurity
+    open class RequiresSecureConfig : WebSecurityConfigurerAdapter() {
+        override fun configure(http: HttpSecurity) {
+            http {
+                requiresChannel {
+                    secure(anyRequest, requiresSecure)
+                }
+            }
+        }
+    }
+
+    @Test
+    fun `request when channel matches mvc with servlet path then redirects based on servlet path`() {
+        this.spring.register(MvcMatcherServletPathConfig::class.java).autowire()
+
+        this.mockMvc.perform(MockMvcRequestBuilders.get("/spring/path")
+                .with { request ->
+                    request.servletPath = "/spring"
+                    request
+                })
+                .andExpect(status().isFound)
+                .andExpect(redirectedUrl("https://localhost/spring/path"))
+
+        this.mockMvc.perform(MockMvcRequestBuilders.get("/other/path")
+                .with { request ->
+                    request.servletPath = "/other"
+                    request
+                })
+                .andExpect(MockMvcResultMatchers.status().isOk)
+    }
+
+    @EnableWebSecurity
+    @EnableWebMvc
+    open class MvcMatcherServletPathConfig : WebSecurityConfigurerAdapter() {
+        override fun configure(http: HttpSecurity) {
+            http {
+                requiresChannel {
+                    secure("/path",
+                            "/spring",
+                            requiresSecure)
+                }
+            }
+        }
+
+        @RestController
+        internal class PathController {
+            @RequestMapping("/path")
+            fun path() {
+            }
+        }
+    }
+
+    @Test
+    fun `requires channel when channel processors configured then channel processors used`() {
+        `when`(ChannelProcessorsConfig.CHANNEL_PROCESSOR.supports(any())).thenReturn(true)
+        this.spring.register(ChannelProcessorsConfig::class.java).autowire()
+
+        this.mockMvc.get("/")
+
+        verify(ChannelProcessorsConfig.CHANNEL_PROCESSOR).supports(any())
+    }
+
+    @EnableWebSecurity
+    open class ChannelProcessorsConfig : WebSecurityConfigurerAdapter() {
+        companion object {
+            var CHANNEL_PROCESSOR: ChannelProcessor = mock(ChannelProcessor::class.java)
+        }
+
+        override fun configure(http: HttpSecurity) {
+            http {
+                requiresChannel {
+                    channelProcessors = listOf(CHANNEL_PROCESSOR)
+                    secure(anyRequest, requiresSecure)
+                }
+            }
+        }
+    }
+}

+ 105 - 0
config/src/test/kotlin/org/springframework/security/config/web/servlet/Saml2DslTests.kt

@@ -0,0 +1,105 @@
+/*
+ * 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
+
+import org.assertj.core.api.Assertions
+import org.junit.Rule
+import org.junit.Test
+import org.springframework.beans.factory.BeanCreationException
+import org.springframework.beans.factory.annotation.Autowired
+import org.springframework.core.io.ClassPathResource
+import org.springframework.security.config.annotation.web.builders.HttpSecurity
+import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity
+import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter
+import org.springframework.security.config.test.SpringTestRule
+import org.springframework.security.saml2.credentials.Saml2X509Credential
+import org.springframework.security.saml2.credentials.Saml2X509Credential.Saml2X509CredentialType.VERIFICATION
+import org.springframework.security.saml2.provider.service.registration.InMemoryRelyingPartyRegistrationRepository
+import org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistration
+import org.springframework.security.saml2.provider.service.servlet.filter.Saml2WebSsoAuthenticationFilter
+import org.springframework.test.web.servlet.MockMvc
+import org.springframework.test.web.servlet.get
+import java.security.cert.Certificate
+import java.security.cert.CertificateFactory
+
+/**
+ * Tests for [Saml2Dsl]
+ *
+ * @author Eleftheria Stein
+ */
+class Saml2DslTests {
+    @Rule
+    @JvmField
+    val spring = SpringTestRule()
+
+    @Autowired
+    lateinit var mockMvc: MockMvc
+
+    @Test
+    fun `saml2Login when no relying party registration repository then exception`() {
+        Assertions.assertThatThrownBy { this.spring.register(Saml2LoginNoRelyingPArtyRegistrationRepoConfig::class.java).autowire() }
+                .isInstanceOf(BeanCreationException::class.java)
+                .hasMessageContaining("relyingPartyRegistrationRepository cannot be null")
+
+    }
+
+    @EnableWebSecurity
+    open class Saml2LoginNoRelyingPArtyRegistrationRepoConfig : WebSecurityConfigurerAdapter() {
+        override fun configure(http: HttpSecurity) {
+            http {
+                saml2Login { }
+            }
+        }
+    }
+
+    @Test
+    fun `login page when saml2Configured then default login page created`() {
+        this.spring.register(Saml2LoginConfig::class.java).autowire()
+
+        this.mockMvc.get("/login")
+                .andExpect {
+                    status { isOk }
+                }
+    }
+
+    @EnableWebSecurity
+    open class Saml2LoginConfig : WebSecurityConfigurerAdapter() {
+
+        override fun configure(http: HttpSecurity) {
+            http {
+                saml2Login {
+                    relyingPartyRegistrationRepository =
+                            InMemoryRelyingPartyRegistrationRepository(
+                                    RelyingPartyRegistration.withRegistrationId("samlId")
+                                            .remoteIdpEntityId("entityId")
+                                            .assertionConsumerServiceUrlTemplate("{baseUrl}" + Saml2WebSsoAuthenticationFilter.DEFAULT_FILTER_PROCESSES_URI)
+                                            .credentials { c -> c.add(Saml2X509Credential(loadCert("rod.cer"), VERIFICATION)) }
+                                            .idpWebSsoUrl("ssoUrl")
+                                            .build()
+                            )
+                }
+            }
+        }
+
+        private fun <T : Certificate> loadCert(location: String): T {
+            ClassPathResource(location).inputStream.use { inputStream ->
+                val certFactory = CertificateFactory.getInstance("X.509")
+                return certFactory.generateCertificate(inputStream) as T
+            }
+        }
+    }
+}

+ 218 - 0
config/src/test/kotlin/org/springframework/security/config/web/servlet/SessionManagementDslTests.kt

@@ -0,0 +1,218 @@
+/*
+ * 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
+
+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.annotation.Bean
+import org.springframework.mock.web.MockHttpSession
+import org.springframework.security.config.annotation.web.builders.HttpSecurity
+import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity
+import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter
+import org.springframework.security.config.http.SessionCreationPolicy
+import org.springframework.security.config.test.SpringTestRule
+import org.springframework.security.core.Authentication
+import org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.authentication
+import org.springframework.security.web.authentication.SimpleUrlAuthenticationFailureHandler
+import org.springframework.security.web.authentication.session.SessionAuthenticationException
+import org.springframework.security.web.authentication.session.SessionAuthenticationStrategy
+import org.springframework.security.web.session.SimpleRedirectInvalidSessionStrategy
+import org.springframework.test.web.servlet.MockMvc
+import org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get
+import org.springframework.test.web.servlet.result.MockMvcResultMatchers.redirectedUrl
+import org.springframework.test.web.servlet.result.MockMvcResultMatchers.status
+import javax.servlet.http.HttpServletRequest
+import javax.servlet.http.HttpServletResponse
+
+/**
+ * Tests for [SessionManagementDsl]
+ *
+ * @author Eleftheria Stein
+ */
+class SessionManagementDslTests {
+    @Rule
+    @JvmField
+    val spring = SpringTestRule()
+
+    @Autowired
+    lateinit var mockMvc: MockMvc
+
+    @Test
+    fun `session management when invalid session url then redirected to url`() {
+        this.spring.register(InvalidSessionUrlConfig::class.java).autowire()
+
+        this.mockMvc.perform(get("/")
+                .with { request ->
+                    request.isRequestedSessionIdValid = false
+                    request.requestedSessionId = "id"
+                    request
+                })
+                .andExpect(status().isFound)
+                .andExpect(redirectedUrl("/invalid"))
+    }
+
+    @EnableWebSecurity
+    open class InvalidSessionUrlConfig : WebSecurityConfigurerAdapter() {
+        override fun configure(http: HttpSecurity) {
+            http {
+                sessionManagement {
+                    invalidSessionUrl = "/invalid"
+                }
+            }
+        }
+    }
+
+    @Test
+    fun `session management when invalid session strategy then strategy used`() {
+        this.spring.register(InvalidSessionStrategyConfig::class.java).autowire()
+
+        this.mockMvc.perform(get("/")
+                .with { request ->
+                    request.isRequestedSessionIdValid = false
+                    request.requestedSessionId = "id"
+                    request
+                })
+                .andExpect(status().isFound)
+                .andExpect(redirectedUrl("/invalid"))
+    }
+
+    @EnableWebSecurity
+    open class InvalidSessionStrategyConfig : WebSecurityConfigurerAdapter() {
+        override fun configure(http: HttpSecurity) {
+            http {
+                sessionManagement {
+                    invalidSessionStrategy = SimpleRedirectInvalidSessionStrategy("/invalid")
+                }
+            }
+        }
+    }
+
+    @Test
+    fun `session management when session authentication error url then redirected to url`() {
+        this.spring.register(SessionAuthenticationErrorUrlConfig::class.java).autowire()
+        val session = mock(MockHttpSession::class.java)
+        `when`(session.changeSessionId()).thenThrow(SessionAuthenticationException::class.java)
+
+        this.mockMvc.perform(get("/")
+                .with(authentication(mock(Authentication::class.java)))
+                .session(session))
+                .andExpect(status().isFound)
+                .andExpect(redirectedUrl("/session-auth-error"))
+    }
+
+    @EnableWebSecurity
+    open class SessionAuthenticationErrorUrlConfig : WebSecurityConfigurerAdapter() {
+        override fun configure(http: HttpSecurity) {
+            http {
+                authorizeRequests {
+                    authorize(anyRequest, authenticated)
+                }
+                sessionManagement {
+                    sessionAuthenticationErrorUrl = "/session-auth-error"
+                }
+            }
+        }
+    }
+
+    @Test
+    fun `session management when session authentication failure handler then handler used`() {
+        this.spring.register(SessionAuthenticationFailureHandlerConfig::class.java).autowire()
+        val session = mock(MockHttpSession::class.java)
+        `when`(session.changeSessionId()).thenThrow(SessionAuthenticationException::class.java)
+
+        this.mockMvc.perform(get("/")
+                .with(authentication(mock(Authentication::class.java)))
+                .session(session))
+                .andExpect(status().isFound)
+                .andExpect(redirectedUrl("/session-auth-error"))
+    }
+
+    @EnableWebSecurity
+    open class SessionAuthenticationFailureHandlerConfig : WebSecurityConfigurerAdapter() {
+        override fun configure(http: HttpSecurity) {
+            http {
+                authorizeRequests {
+                    authorize(anyRequest, authenticated)
+                }
+                sessionManagement {
+                    sessionAuthenticationFailureHandler = SimpleUrlAuthenticationFailureHandler("/session-auth-error")
+                }
+            }
+        }
+    }
+
+    @Test
+    fun `session management when stateless policy then does not store session`() {
+        this.spring.register(StatelessSessionManagementConfig::class.java).autowire()
+
+        val result = this.mockMvc.perform(get("/"))
+                .andReturn()
+
+        assertThat(result.request.getSession(false)).isNull()
+    }
+
+    @EnableWebSecurity
+    open class StatelessSessionManagementConfig : WebSecurityConfigurerAdapter() {
+        override fun configure(http: HttpSecurity) {
+            http {
+                authorizeRequests {
+                    authorize(anyRequest, authenticated)
+                }
+                sessionManagement {
+                    sessionCreationPolicy = SessionCreationPolicy.STATELESS
+                }
+            }
+        }
+    }
+
+    @Test
+    fun `session management when session authentication strategy then strategy used`() {
+        this.spring.register(SessionAuthenticationStrategyConfig::class.java).autowire()
+
+        this.mockMvc.perform(get("/")
+                .with(authentication(mock(Authentication::class.java)))
+                .session(mock(MockHttpSession::class.java)))
+
+        verify(this.spring.getContext().getBean(SessionAuthenticationStrategy::class.java))
+                .onAuthentication(any(Authentication::class.java),
+                        any(HttpServletRequest::class.java), any(HttpServletResponse::class.java))
+    }
+
+    @EnableWebSecurity
+    open class SessionAuthenticationStrategyConfig : WebSecurityConfigurerAdapter() {
+        var mockSessionAuthenticationStrategy: SessionAuthenticationStrategy = mock(SessionAuthenticationStrategy::class.java)
+
+        override fun configure(http: HttpSecurity) {
+            http {
+                authorizeRequests {
+                    authorize(anyRequest, authenticated)
+                }
+                sessionManagement {
+                    sessionAuthenticationStrategy = mockSessionAuthenticationStrategy
+                }
+            }
+        }
+
+        @Bean
+        open fun sessionAuthenticationStrategy(): SessionAuthenticationStrategy {
+            return this.mockSessionAuthenticationStrategy
+        }
+    }
+}

+ 221 - 0
config/src/test/kotlin/org/springframework/security/config/web/servlet/X509DslTests.kt

@@ -0,0 +1,221 @@
+/*
+ * 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
+
+import org.junit.Rule
+import org.junit.Test
+import org.mockito.Mockito.mock
+import org.springframework.beans.factory.annotation.Autowired
+import org.springframework.context.annotation.Bean
+import org.springframework.core.io.ClassPathResource
+import org.springframework.security.config.annotation.web.builders.HttpSecurity
+import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity
+import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter
+import org.springframework.security.config.test.SpringTestRule
+import org.springframework.security.core.userdetails.User
+import org.springframework.security.core.userdetails.UserDetailsByNameServiceWrapper
+import org.springframework.security.core.userdetails.UserDetailsService
+import org.springframework.security.provisioning.InMemoryUserDetailsManager
+import org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.x509
+import org.springframework.security.test.web.servlet.response.SecurityMockMvcResultMatchers.authenticated
+import org.springframework.security.web.authentication.preauth.PreAuthenticatedAuthenticationToken
+import org.springframework.security.web.authentication.preauth.x509.SubjectDnX509PrincipalExtractor
+import org.springframework.test.web.servlet.MockMvc
+import org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get
+import java.security.cert.Certificate
+import java.security.cert.CertificateFactory
+import java.security.cert.X509Certificate
+
+/**
+ * Tests for [X509Dsl]
+ *
+ * @author Eleftheria Stein
+ */
+class X509DslTests {
+    @Rule
+    @JvmField
+    val spring = SpringTestRule()
+
+    @Autowired
+    lateinit var mockMvc: MockMvc
+
+    @Test
+    fun `x509 when configured with defaults then user authenticated`() {
+        this.spring.register(X509Config::class.java).autowire()
+        val certificate = loadCert<X509Certificate>("rod.cer")
+
+        this.mockMvc.perform(get("/")
+                .with(x509(certificate)))
+                .andExpect(authenticated().withUsername("rod"))
+    }
+
+    @EnableWebSecurity
+    open class X509Config : WebSecurityConfigurerAdapter() {
+        override fun configure(http: HttpSecurity) {
+            http {
+                x509 { }
+            }
+        }
+
+        @Bean
+        override fun userDetailsService(): UserDetailsService {
+            val userDetails = User.withDefaultPasswordEncoder()
+                    .username("rod")
+                    .password("password")
+                    .roles("USER")
+                    .build()
+            return InMemoryUserDetailsManager(userDetails)
+        }
+    }
+
+    @Test
+    fun `x509 when configured with regex then user authenticated`() {
+        this.spring.register(X509RegexConfig::class.java).autowire()
+        val certificate = loadCert<X509Certificate>("rodatexampledotcom.cer")
+
+        this.mockMvc.perform(get("/")
+                .with(x509(certificate)))
+                .andExpect(authenticated().withUsername("rod"))
+    }
+
+    @EnableWebSecurity
+    open class X509RegexConfig : WebSecurityConfigurerAdapter() {
+        override fun configure(http: HttpSecurity) {
+            http {
+                x509 {
+                    subjectPrincipalRegex = "CN=(.*?)@example.com(?:,|$)"
+                }
+            }
+        }
+
+        @Bean
+        override fun userDetailsService(): UserDetailsService {
+            val userDetails = User.withDefaultPasswordEncoder()
+                    .username("rod")
+                    .password("password")
+                    .roles("USER")
+                    .build()
+            return InMemoryUserDetailsManager(userDetails)
+        }
+    }
+
+    @Test
+    fun `x509 when user details service configured then user details service used`() {
+        this.spring.register(UserDetailsServiceConfig::class.java).autowire()
+        val certificate = loadCert<X509Certificate>("rod.cer")
+
+        this.mockMvc.perform(get("/")
+                .with(x509(certificate)))
+                .andExpect(authenticated().withUsername("rod"))
+    }
+
+    @EnableWebSecurity
+    open class UserDetailsServiceConfig : WebSecurityConfigurerAdapter() {
+        override fun configure(http: HttpSecurity) {
+            val userDetails = User.withDefaultPasswordEncoder()
+                    .username("rod")
+                    .password("password")
+                    .roles("USER")
+                    .build()
+            val customUserDetailsService = InMemoryUserDetailsManager(userDetails)
+            http {
+                x509 {
+                    userDetailsService = customUserDetailsService
+                }
+            }
+        }
+
+        @Bean
+        override fun userDetailsService(): UserDetailsService {
+            return mock(UserDetailsService::class.java)
+        }
+    }
+
+    @Test
+    fun `x509 when authentication user details service configured then custom user details service used`() {
+        this.spring.register(AuthenticationUserDetailsServiceConfig::class.java).autowire()
+        val certificate = loadCert<X509Certificate>("rod.cer")
+
+        this.mockMvc.perform(get("/")
+                .with(x509(certificate)))
+                .andExpect(authenticated().withUsername("rod"))
+    }
+
+    @EnableWebSecurity
+    open class AuthenticationUserDetailsServiceConfig : WebSecurityConfigurerAdapter() {
+        override fun configure(http: HttpSecurity) {
+            val userDetails = User.withDefaultPasswordEncoder()
+                    .username("rod")
+                    .password("password")
+                    .roles("USER")
+                    .build()
+            val customUserDetailsService = InMemoryUserDetailsManager(userDetails)
+            val customSource = UserDetailsByNameServiceWrapper<PreAuthenticatedAuthenticationToken>()
+            customSource.setUserDetailsService(customUserDetailsService)
+            http {
+                x509 {
+                    authenticationUserDetailsService = customSource
+                }
+            }
+        }
+
+        @Bean
+        override fun userDetailsService(): UserDetailsService {
+            return mock(UserDetailsService::class.java)
+        }
+    }
+
+    @Test
+    fun `x509 when configured with principal extractor then principal extractor used`() {
+        this.spring.register(X509PrincipalExtractorConfig::class.java).autowire()
+        val certificate = loadCert<X509Certificate>("rodatexampledotcom.cer")
+
+        this.mockMvc.perform(get("/")
+                .with(x509(certificate)))
+                .andExpect(authenticated().withUsername("rod"))
+    }
+
+    @EnableWebSecurity
+    open class X509PrincipalExtractorConfig : WebSecurityConfigurerAdapter() {
+        override fun configure(http: HttpSecurity) {
+            val principalExtractor = SubjectDnX509PrincipalExtractor()
+            principalExtractor.setSubjectDnRegex("CN=(.*?)@example.com(?:,|$)")
+            http {
+                x509 {
+                    x509PrincipalExtractor = principalExtractor
+                }
+            }
+        }
+
+        @Bean
+        override fun userDetailsService(): UserDetailsService {
+            val userDetails = User.withDefaultPasswordEncoder()
+                    .username("rod")
+                    .password("password")
+                    .roles("USER")
+                    .build()
+            return InMemoryUserDetailsManager(userDetails)
+        }
+    }
+
+    private fun <T : Certificate> loadCert(location: String): T {
+        ClassPathResource(location).inputStream.use { inputStream ->
+            val certFactory = CertificateFactory.getInstance("X.509")
+            return certFactory.generateCertificate(inputStream) as T
+        }
+    }
+}

+ 92 - 0
config/src/test/kotlin/org/springframework/security/config/web/servlet/headers/CacheControlDslTests.kt

@@ -0,0 +1,92 @@
+/*
+ * 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
+
+import org.junit.Rule
+import org.junit.Test
+import org.springframework.beans.factory.annotation.Autowired
+import org.springframework.http.HttpHeaders
+import org.springframework.security.config.annotation.web.builders.HttpSecurity
+import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity
+import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter
+import org.springframework.security.config.web.servlet.invoke
+import org.springframework.security.config.test.SpringTestRule
+import org.springframework.test.web.servlet.MockMvc
+import org.springframework.test.web.servlet.get
+
+/**
+ * Tests for [CacheControlDsl]
+ *
+ * @author Eleftheria Stein
+ */
+class CacheControlDslTests {
+    @Rule
+    @JvmField
+    var spring = SpringTestRule()
+
+    @Autowired
+    lateinit var mockMvc: MockMvc
+
+    @Test
+    fun `headers when cache control configured then cache control headers in response`() {
+        this.spring.register(CacheControlConfig::class.java).autowire()
+
+        this.mockMvc.get("/")
+                .andExpect {
+                    header { string(HttpHeaders.CACHE_CONTROL, "no-cache, no-store, max-age=0, must-revalidate") }
+                    header { string(HttpHeaders.EXPIRES, "0") }
+                    header { string(HttpHeaders.PRAGMA, "no-cache") }
+                }
+    }
+
+    @EnableWebSecurity
+    open class CacheControlConfig : WebSecurityConfigurerAdapter() {
+        override fun configure(http: HttpSecurity) {
+            http {
+                headers {
+                    defaultsDisabled = true
+                    cacheControl { }
+                }
+            }
+        }
+    }
+
+    @Test
+    fun `headers when cache control disabled then no cache control headers in response`() {
+        this.spring.register(CacheControlDisabledConfig::class.java).autowire()
+
+        this.mockMvc.get("/")
+                .andExpect {
+                    header { doesNotExist(HttpHeaders.CACHE_CONTROL) }
+                    header { doesNotExist(HttpHeaders.EXPIRES) }
+                    header { doesNotExist(HttpHeaders.PRAGMA) }
+                }
+    }
+
+    @EnableWebSecurity
+    open class CacheControlDisabledConfig : WebSecurityConfigurerAdapter() {
+        override fun configure(http: HttpSecurity) {
+            http {
+                headers {
+                    cacheControl {
+                        disable()
+                    }
+                }
+            }
+        }
+    }
+}

+ 116 - 0
config/src/test/kotlin/org/springframework/security/config/web/servlet/headers/ContentSecurityPolicyDslTests.kt

@@ -0,0 +1,116 @@
+/*
+ * 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
+
+import org.junit.Rule
+import org.junit.Test
+import org.springframework.beans.factory.annotation.Autowired
+import org.springframework.security.config.annotation.web.builders.HttpSecurity
+import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity
+import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter
+import org.springframework.security.config.web.servlet.invoke
+import org.springframework.security.config.test.SpringTestRule
+import org.springframework.security.web.server.header.ContentSecurityPolicyServerHttpHeadersWriter
+import org.springframework.test.web.servlet.MockMvc
+import org.springframework.test.web.servlet.get
+
+/**
+ * Tests for [ContentSecurityPolicyDsl]
+ *
+ * @author Eleftheria Stein
+ */
+class ContentSecurityPolicyDslTests {
+    @Rule
+    @JvmField
+    var spring = SpringTestRule()
+
+    @Autowired
+    lateinit var mockMvc: MockMvc
+
+    @Test
+    fun `headers when content security policy configured then header in response`() {
+        this.spring.register(ContentSecurityPolicyConfig::class.java).autowire()
+
+        this.mockMvc.get("/") {
+            secure = true
+        }.andExpect {
+            header { string(ContentSecurityPolicyServerHttpHeadersWriter.CONTENT_SECURITY_POLICY, "default-src 'self'") }
+        }
+    }
+
+    @EnableWebSecurity
+    open class ContentSecurityPolicyConfig : WebSecurityConfigurerAdapter() {
+        override fun configure(http: HttpSecurity) {
+            http {
+                headers {
+                    defaultsDisabled = true
+                    contentSecurityPolicy { }
+                }
+            }
+        }
+    }
+
+    @Test
+    fun `headers when content security policy configured with custom policy directives then custom directives in header`() {
+        this.spring.register(CustomPolicyDirectivesConfig::class.java).autowire()
+
+        this.mockMvc.get("/") {
+            secure = true
+        }.andExpect {
+            header { string(ContentSecurityPolicyServerHttpHeadersWriter.CONTENT_SECURITY_POLICY, "default-src 'self'; script-src trustedscripts.example.com") }
+        }
+    }
+
+    @EnableWebSecurity
+    open class CustomPolicyDirectivesConfig : WebSecurityConfigurerAdapter() {
+        override fun configure(http: HttpSecurity) {
+            http {
+                headers {
+                    defaultsDisabled = true
+                    contentSecurityPolicy {
+                        policyDirectives = "default-src 'self'; script-src trustedscripts.example.com"
+                    }
+                }
+            }
+        }
+    }
+
+    @Test
+    fun `headers when report only content security policy report only header in response`() {
+        this.spring.register(ReportOnlyConfig::class.java).autowire()
+
+        this.mockMvc.get("/") {
+            secure = true
+        }.andExpect {
+            header { string(ContentSecurityPolicyServerHttpHeadersWriter.CONTENT_SECURITY_POLICY_REPORT_ONLY, "default-src 'self'") }
+        }
+    }
+
+    @EnableWebSecurity
+    open class ReportOnlyConfig : WebSecurityConfigurerAdapter() {
+        override fun configure(http: HttpSecurity) {
+            http {
+                headers {
+                    defaultsDisabled = true
+                    contentSecurityPolicy {
+                        reportOnly = true
+                    }
+                }
+            }
+        }
+    }
+}

+ 88 - 0
config/src/test/kotlin/org/springframework/security/config/web/servlet/headers/ContentTypeOptionsDslTests.kt

@@ -0,0 +1,88 @@
+/*
+ * 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
+
+import org.junit.Rule
+import org.junit.Test
+import org.springframework.beans.factory.annotation.Autowired
+import org.springframework.security.config.annotation.web.builders.HttpSecurity
+import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity
+import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter
+import org.springframework.security.config.web.servlet.invoke
+import org.springframework.security.config.test.SpringTestRule
+import org.springframework.security.web.server.header.ContentTypeOptionsServerHttpHeadersWriter
+import org.springframework.test.web.servlet.MockMvc
+import org.springframework.test.web.servlet.get
+
+/**
+ * Tests for [ContentTypeOptionsDsl]
+ *
+ * @author Eleftheria Stein
+ */
+class ContentTypeOptionsDslTests {
+    @Rule
+    @JvmField
+    var spring = SpringTestRule()
+
+    @Autowired
+    lateinit var mockMvc: MockMvc
+
+    @Test
+    fun `headers when content type options configured then X-Content-Type-Options header in response`() {
+        this.spring.register(ContentTypeOptionsConfig::class.java).autowire()
+
+        this.mockMvc.get("/")
+                .andExpect {
+                    header { string(ContentTypeOptionsServerHttpHeadersWriter.X_CONTENT_OPTIONS, "nosniff") }
+                }
+    }
+
+    @EnableWebSecurity
+    open class ContentTypeOptionsConfig : WebSecurityConfigurerAdapter() {
+        override fun configure(http: HttpSecurity) {
+            http {
+                headers {
+                    defaultsDisabled = true
+                    contentTypeOptions { }
+                }
+            }
+        }
+    }
+
+    @Test
+    fun `headers when content type options disabled then X-Content-Type-Options header not in response`() {
+        this.spring.register(ContentTypeOptionsDisabledConfig::class.java).autowire()
+
+        this.mockMvc.get("/")
+                .andExpect {
+                    header { doesNotExist(ContentTypeOptionsServerHttpHeadersWriter.X_CONTENT_OPTIONS) }
+                }
+    }
+
+    @EnableWebSecurity
+    open class ContentTypeOptionsDisabledConfig : WebSecurityConfigurerAdapter() {
+        override fun configure(http: HttpSecurity) {
+            http {
+                headers {
+                    contentTypeOptions {
+                        disable()
+                    }
+                }
+            }
+        }
+    }
+}

+ 167 - 0
config/src/test/kotlin/org/springframework/security/config/web/servlet/headers/FrameOptionsDslTests.kt

@@ -0,0 +1,167 @@
+/*
+ * 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
+
+import org.junit.Rule
+import org.junit.Test
+import org.springframework.beans.factory.annotation.Autowired
+import org.springframework.security.config.annotation.web.builders.HttpSecurity
+import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity
+import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter
+import org.springframework.security.config.web.servlet.invoke
+import org.springframework.security.config.test.SpringTestRule
+import org.springframework.security.web.header.writers.frameoptions.XFrameOptionsHeaderWriter
+import org.springframework.security.web.server.header.XFrameOptionsServerHttpHeadersWriter
+import org.springframework.test.web.servlet.MockMvc
+import org.springframework.test.web.servlet.get
+
+/**
+ * Tests for [FrameOptionsDsl]
+ *
+ * @author Eleftheria Stein
+ */
+class FrameOptionsDslTests {
+    @Rule
+    @JvmField
+    var spring = SpringTestRule()
+
+    @Autowired
+    lateinit var mockMvc: MockMvc
+
+    @Test
+    fun `headers when frame options configured then frame options deny header`() {
+        this.spring.register(FrameOptionsConfig::class.java).autowire()
+
+        this.mockMvc.get("/") {
+            secure = true
+        }.andExpect {
+            header { string(XFrameOptionsServerHttpHeadersWriter.X_FRAME_OPTIONS, XFrameOptionsHeaderWriter.XFrameOptionsMode.DENY.name) }
+        }
+    }
+
+    @EnableWebSecurity
+    open class FrameOptionsConfig : WebSecurityConfigurerAdapter() {
+        override fun configure(http: HttpSecurity) {
+            http {
+                headers {
+                    defaultsDisabled = true
+                    frameOptions { }
+                }
+            }
+        }
+    }
+
+    @Test
+    fun `headers when frame options deny configured then frame options deny header`() {
+        this.spring.register(FrameOptionsDenyConfig::class.java).autowire()
+
+        this.mockMvc.get("/") {
+            secure = true
+        }.andExpect {
+            header { string(XFrameOptionsServerHttpHeadersWriter.X_FRAME_OPTIONS, XFrameOptionsHeaderWriter.XFrameOptionsMode.DENY.name) }
+        }
+    }
+
+    @EnableWebSecurity
+    open class FrameOptionsDenyConfig : WebSecurityConfigurerAdapter() {
+        override fun configure(http: HttpSecurity) {
+            http {
+                headers {
+                    defaultsDisabled = true
+                    frameOptions {
+                        deny = true
+                    }
+                }
+            }
+        }
+    }
+
+    @Test
+    fun `headers when frame options same origin configured then frame options same origin header`() {
+        this.spring.register(FrameOptionsSameOriginConfig::class.java).autowire()
+
+        this.mockMvc.get("/") {
+            secure = true
+        }.andExpect {
+            header { string(XFrameOptionsServerHttpHeadersWriter.X_FRAME_OPTIONS, XFrameOptionsHeaderWriter.XFrameOptionsMode.SAMEORIGIN.name) }
+        }
+    }
+
+    @EnableWebSecurity
+    open class FrameOptionsSameOriginConfig : WebSecurityConfigurerAdapter() {
+        override fun configure(http: HttpSecurity) {
+            http {
+                headers {
+                    defaultsDisabled = true
+                    frameOptions {
+                        sameOrigin = true
+                    }
+                }
+            }
+        }
+    }
+
+    @Test
+    fun `headers when frame options same origin and deny configured then frame options deny header`() {
+        this.spring.register(FrameOptionsSameOriginAndDenyConfig::class.java).autowire()
+
+        this.mockMvc.get("/") {
+            secure = true
+        }.andExpect {
+            header { string(XFrameOptionsServerHttpHeadersWriter.X_FRAME_OPTIONS, XFrameOptionsHeaderWriter.XFrameOptionsMode.DENY.name) }
+        }
+    }
+
+    @EnableWebSecurity
+    open class FrameOptionsSameOriginAndDenyConfig : WebSecurityConfigurerAdapter() {
+        override fun configure(http: HttpSecurity) {
+            http {
+                headers {
+                    defaultsDisabled = true
+                    frameOptions {
+                        sameOrigin = true
+                        deny = true
+                    }
+                }
+            }
+        }
+    }
+
+    @Test
+    fun `headers when frame options disabled then no frame options header in response`() {
+        this.spring.register(FrameOptionsDisabledConfig::class.java).autowire()
+
+        this.mockMvc.get("/") {
+            secure = true
+        }.andExpect {
+            header { doesNotExist(XFrameOptionsServerHttpHeadersWriter.X_FRAME_OPTIONS) }
+        }
+    }
+
+    @EnableWebSecurity
+    open class FrameOptionsDisabledConfig : WebSecurityConfigurerAdapter() {
+        override fun configure(http: HttpSecurity) {
+            http {
+                headers {
+                    frameOptions {
+                        disable()
+                    }
+                }
+            }
+        }
+    }
+}

+ 230 - 0
config/src/test/kotlin/org/springframework/security/config/web/servlet/headers/HttpPublicKeyPinningDslTests.kt

@@ -0,0 +1,230 @@
+/*
+ * 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
+
+import org.assertj.core.api.Assertions
+import org.junit.Rule
+import org.junit.Test
+import org.springframework.beans.factory.annotation.Autowired
+import org.springframework.security.config.annotation.web.builders.HttpSecurity
+import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity
+import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter
+import org.springframework.security.config.test.SpringTestRule
+import org.springframework.security.config.web.servlet.invoke
+import org.springframework.test.web.servlet.MockMvc
+import org.springframework.test.web.servlet.get
+
+/**
+ * Tests for [HttpPublicKeyPinningDsl]
+ *
+ * @author Eleftheria Stein
+ */
+class HttpPublicKeyPinningDslTests {
+    @Rule
+    @JvmField
+    var spring = SpringTestRule()
+
+    @Autowired
+    lateinit var mockMvc: MockMvc
+
+    private val HPKP_RO_HEADER_NAME = "Public-Key-Pins-Report-Only"
+    private val HPKP_HEADER_NAME = "Public-Key-Pins"
+
+    @Test
+    fun `headers when HPKP configured and no pin then no headers in response`() {
+        this.spring.register(HpkpNoPinConfig::class.java).autowire()
+
+        val result = this.mockMvc.get("/") {
+            secure = true
+        }.andReturn()
+
+        Assertions.assertThat(result.response.headerNames).isEmpty()
+    }
+
+    @EnableWebSecurity
+    open class HpkpNoPinConfig : WebSecurityConfigurerAdapter() {
+        override fun configure(http: HttpSecurity) {
+            http {
+                headers {
+                    defaultsDisabled = true
+                    httpPublicKeyPinning { }
+                }
+            }
+        }
+    }
+
+    @Test
+    fun `headers when HPKP configured with pin then header in response`() {
+        this.spring.register(HpkpPinConfig::class.java).autowire()
+
+        this.mockMvc.get("/") {
+            secure = true
+        }.andExpect {
+            header { string(HPKP_RO_HEADER_NAME, "max-age=5184000 ; pin-sha256=\"d6qzRu9zOECb90Uez27xWltNsj0e1Md7GkYYkVoZWmM=\"") }
+        }
+    }
+
+    @EnableWebSecurity
+    open class HpkpPinConfig : WebSecurityConfigurerAdapter() {
+        override fun configure(http: HttpSecurity) {
+            http {
+                headers {
+                    defaultsDisabled = true
+                    httpPublicKeyPinning {
+                        pins = mapOf(Pair("d6qzRu9zOECb90Uez27xWltNsj0e1Md7GkYYkVoZWmM=", "sha256"))
+                    }
+                }
+            }
+        }
+    }
+
+    @Test
+    fun `headers when HPKP configured with maximum age then maximum age in header`() {
+        this.spring.register(HpkpMaxAgeConfig::class.java).autowire()
+
+        this.mockMvc.get("/") {
+            secure = true
+        }.andExpect {
+            header { string(HPKP_RO_HEADER_NAME, "max-age=604800 ; pin-sha256=\"d6qzRu9zOECb90Uez27xWltNsj0e1Md7GkYYkVoZWmM=\"") }
+        }
+    }
+
+    @EnableWebSecurity
+    open class HpkpMaxAgeConfig : WebSecurityConfigurerAdapter() {
+        override fun configure(http: HttpSecurity) {
+            http {
+                headers {
+                    defaultsDisabled = true
+                    httpPublicKeyPinning {
+                        pins = mapOf(Pair("d6qzRu9zOECb90Uez27xWltNsj0e1Md7GkYYkVoZWmM=", "sha256"))
+                        maxAgeInSeconds = 604800
+                    }
+                }
+            }
+        }
+    }
+
+    @Test
+    fun `headers when HPKP configured with report only false then public key pins header in response`() {
+        this.spring.register(HpkpReportOnlyFalseConfig::class.java).autowire()
+
+        this.mockMvc.get("/") {
+            secure = true
+        }.andExpect {
+            header { string(HPKP_HEADER_NAME, "max-age=5184000 ; pin-sha256=\"d6qzRu9zOECb90Uez27xWltNsj0e1Md7GkYYkVoZWmM=\"") }
+        }
+    }
+
+    @EnableWebSecurity
+    open class HpkpReportOnlyFalseConfig : WebSecurityConfigurerAdapter() {
+        override fun configure(http: HttpSecurity) {
+            http {
+                headers {
+                    defaultsDisabled = true
+                    httpPublicKeyPinning {
+                        pins = mapOf(Pair("d6qzRu9zOECb90Uez27xWltNsj0e1Md7GkYYkVoZWmM=", "sha256"))
+                        reportOnly = false
+                    }
+                }
+            }
+        }
+    }
+
+    @Test
+    fun `headers when HPKP configured with include subdomains then include subdomains in header`() {
+        this.spring.register(HpkpIncludeSubdomainsConfig::class.java).autowire()
+
+        this.mockMvc.get("/") {
+            secure = true
+        }.andExpect {
+            header {
+                string(HPKP_RO_HEADER_NAME,
+                        "max-age=5184000 ; pin-sha256=\"d6qzRu9zOECb90Uez27xWltNsj0e1Md7GkYYkVoZWmM=\" ; includeSubDomains")
+            }
+        }
+    }
+
+    @EnableWebSecurity
+    open class HpkpIncludeSubdomainsConfig : WebSecurityConfigurerAdapter() {
+        override fun configure(http: HttpSecurity) {
+            http {
+                headers {
+                    defaultsDisabled = true
+                    httpPublicKeyPinning {
+                        pins = mapOf(Pair("d6qzRu9zOECb90Uez27xWltNsj0e1Md7GkYYkVoZWmM=", "sha256"))
+                        includeSubDomains = true
+                    }
+                }
+            }
+        }
+    }
+
+    @Test
+    fun `headers when HPKP configured with report uri then report uri in header`() {
+        this.spring.register(HpkpReportUriConfig::class.java).autowire()
+
+        this.mockMvc.get("/") {
+            secure = true
+        }.andExpect {
+            header {
+                string(HPKP_RO_HEADER_NAME,
+                        "max-age=5184000 ; pin-sha256=\"d6qzRu9zOECb90Uez27xWltNsj0e1Md7GkYYkVoZWmM=\" ; report-uri=\"https://example.com\"")
+            }
+        }
+    }
+
+    @EnableWebSecurity
+    open class HpkpReportUriConfig : WebSecurityConfigurerAdapter() {
+        override fun configure(http: HttpSecurity) {
+            http {
+                headers {
+                    defaultsDisabled = true
+                    httpPublicKeyPinning {
+                        pins = mapOf(Pair("d6qzRu9zOECb90Uez27xWltNsj0e1Md7GkYYkVoZWmM=", "sha256"))
+                        reportUri = "https://example.com"
+                    }
+                }
+            }
+        }
+    }
+
+    @Test
+    fun `headers when HPKP disabled then no HPKP header in response`() {
+        this.spring.register(HpkpDisabledConfig::class.java).autowire()
+
+        this.mockMvc.get("/") {
+            secure = true
+        }.andExpect {
+            header {
+                doesNotExist(HPKP_RO_HEADER_NAME)
+            }
+        }
+    }
+
+    @EnableWebSecurity
+    open class HpkpDisabledConfig : WebSecurityConfigurerAdapter() {
+        override fun configure(http: HttpSecurity) {
+            http {
+                headers {
+                    httpPublicKeyPinning {
+                        disable()
+                    }
+                }
+            }
+        }
+    }
+}

+ 167 - 0
config/src/test/kotlin/org/springframework/security/config/web/servlet/headers/HttpStrictTransportSecurityDslTests.kt

@@ -0,0 +1,167 @@
+/*
+ * 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
+
+import org.assertj.core.api.Assertions
+import org.junit.Rule
+import org.junit.Test
+import org.springframework.beans.factory.annotation.Autowired
+import org.springframework.security.config.annotation.web.builders.HttpSecurity
+import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity
+import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter
+import org.springframework.security.config.web.servlet.invoke
+import org.springframework.security.config.test.SpringTestRule
+import org.springframework.security.web.server.header.StrictTransportSecurityServerHttpHeadersWriter
+import org.springframework.security.web.util.matcher.AntPathRequestMatcher
+import org.springframework.test.web.servlet.MockMvc
+import org.springframework.test.web.servlet.get
+
+/**
+ * Tests for [HttpStrictTransportSecurityDsl]
+ *
+ * @author Eleftheria Stein
+ */
+class HttpStrictTransportSecurityDslTests {
+    @Rule
+    @JvmField
+    var spring = SpringTestRule()
+
+    @Autowired
+    lateinit var mockMvc: MockMvc
+
+    @Test
+    fun `headers when hsts configured then headers in response`() {
+        this.spring.register(HstsConfig::class.java).autowire()
+
+        this.mockMvc.get("/") {
+            secure = true
+        }.andExpect {
+            header { string(StrictTransportSecurityServerHttpHeadersWriter.STRICT_TRANSPORT_SECURITY, "max-age=31536000 ; includeSubDomains") }
+        }
+    }
+
+    @EnableWebSecurity
+    open class HstsConfig : WebSecurityConfigurerAdapter() {
+        override fun configure(http: HttpSecurity) {
+            http {
+                headers {
+                    defaultsDisabled = true
+                    httpStrictTransportSecurity { }
+                }
+            }
+        }
+    }
+
+    @Test
+    fun `headers when hsts configured with preload then preload in header`() {
+        this.spring.register(HstsPreloadConfig::class.java).autowire()
+
+        this.mockMvc.get("/") {
+            secure = true
+        }.andExpect {
+            header { string(StrictTransportSecurityServerHttpHeadersWriter.STRICT_TRANSPORT_SECURITY, "max-age=31536000 ; includeSubDomains ; preload") }
+        }
+    }
+
+    @EnableWebSecurity
+    open class HstsPreloadConfig : WebSecurityConfigurerAdapter() {
+        override fun configure(http: HttpSecurity) {
+            http {
+                headers {
+                    defaultsDisabled = true
+                    httpStrictTransportSecurity {
+                        preload = true
+                    }
+                }
+            }
+        }
+    }
+
+    @Test
+    fun `headers when hsts configured with max age then max age in header`() {
+        this.spring.register(HstsMaxAgeConfig::class.java).autowire()
+
+        this.mockMvc.get("/") {
+            secure = true
+        }.andExpect {
+            header { string(StrictTransportSecurityServerHttpHeadersWriter.STRICT_TRANSPORT_SECURITY, "max-age=1 ; includeSubDomains") }
+        }
+    }
+
+    @EnableWebSecurity
+    open class HstsMaxAgeConfig : WebSecurityConfigurerAdapter() {
+        override fun configure(http: HttpSecurity) {
+            http {
+                headers {
+                    defaultsDisabled = true
+                    httpStrictTransportSecurity {
+                        maxAgeInSeconds = 1
+                    }
+                }
+            }
+        }
+    }
+
+    @Test
+    fun `headers when hsts configured and does not match then hsts header not in response`() {
+        this.spring.register(HstsCustomMatcherConfig::class.java).autowire()
+
+        val result = this.mockMvc.get("/") {
+            secure = true
+        }.andReturn()
+
+        Assertions.assertThat(result.response.headerNames).isEmpty()
+    }
+
+    @EnableWebSecurity
+    open class HstsCustomMatcherConfig : WebSecurityConfigurerAdapter() {
+        override fun configure(http: HttpSecurity) {
+            http {
+                headers {
+                    defaultsDisabled = true
+                    httpStrictTransportSecurity {
+                        requestMatcher = AntPathRequestMatcher("/secure/**")
+                    }
+                }
+            }
+        }
+    }
+
+    @Test
+    fun `request when hsts disabled then hsts header not in response`() {
+        this.spring.register(HstsDisabledConfig::class.java).autowire()
+
+        this.mockMvc.get("/") {
+            secure = true
+        }.andExpect {
+            header { doesNotExist(StrictTransportSecurityServerHttpHeadersWriter.STRICT_TRANSPORT_SECURITY) }
+        }
+    }
+
+    @EnableWebSecurity
+    open class HstsDisabledConfig : WebSecurityConfigurerAdapter() {
+        override fun configure(http: HttpSecurity) {
+            http {
+                headers {
+                    httpStrictTransportSecurity {
+                        disable()
+                    }
+                }
+            }
+        }
+    }
+}

+ 89 - 0
config/src/test/kotlin/org/springframework/security/config/web/servlet/headers/ReferrerPolicyDslTests.kt

@@ -0,0 +1,89 @@
+/*
+ * 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
+
+import org.junit.Rule
+import org.junit.Test
+import org.springframework.beans.factory.annotation.Autowired
+import org.springframework.security.config.annotation.web.builders.HttpSecurity
+import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity
+import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter
+import org.springframework.security.config.web.servlet.invoke
+import org.springframework.security.config.test.SpringTestRule
+import org.springframework.security.web.header.writers.ReferrerPolicyHeaderWriter
+import org.springframework.test.web.servlet.MockMvc
+import org.springframework.test.web.servlet.get
+
+/**
+ * Tests for [ReferrerPolicyDsl]
+ *
+ * @author Eleftheria Stein
+ */
+class ReferrerPolicyDslTests {
+    @Rule
+    @JvmField
+    var spring = SpringTestRule()
+
+    @Autowired
+    lateinit var mockMvc: MockMvc
+
+    @Test
+    fun `headers when referrer policy configured then header in response`() {
+        this.spring.register(ReferrerPolicyConfig::class.java).autowire()
+
+        this.mockMvc.get("/")
+                .andExpect {
+                    header { string("Referrer-Policy", ReferrerPolicyHeaderWriter.ReferrerPolicy.NO_REFERRER.policy) }
+                }
+    }
+
+    @EnableWebSecurity
+    open class ReferrerPolicyConfig : WebSecurityConfigurerAdapter() {
+        override fun configure(http: HttpSecurity) {
+            http {
+                headers {
+                    defaultsDisabled = true
+                    referrerPolicy { }
+                }
+            }
+        }
+    }
+
+    @Test
+    fun `headers when referrer policy configured with custom policy then custom policy in header`() {
+        this.spring.register(ReferrerPolicyCustomPolicyConfig::class.java).autowire()
+
+        this.mockMvc.get("/")
+                .andExpect {
+                    header { string("Referrer-Policy", ReferrerPolicyHeaderWriter.ReferrerPolicy.SAME_ORIGIN.policy) }
+                }
+    }
+
+    @EnableWebSecurity
+    open class ReferrerPolicyCustomPolicyConfig : WebSecurityConfigurerAdapter() {
+        override fun configure(http: HttpSecurity) {
+            http {
+                headers {
+                    defaultsDisabled = true
+                    referrerPolicy {
+                        policy = ReferrerPolicyHeaderWriter.ReferrerPolicy.SAME_ORIGIN
+                    }
+                }
+            }
+        }
+    }
+}

+ 140 - 0
config/src/test/kotlin/org/springframework/security/config/web/servlet/headers/XssProtectionConfigDslTests.kt

@@ -0,0 +1,140 @@
+/*
+ * 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
+
+import org.junit.Rule
+import org.junit.Test
+import org.springframework.beans.factory.annotation.Autowired
+import org.springframework.security.config.annotation.web.builders.HttpSecurity
+import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity
+import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter
+import org.springframework.security.config.web.servlet.invoke
+import org.springframework.security.config.test.SpringTestRule
+import org.springframework.security.web.server.header.XXssProtectionServerHttpHeadersWriter
+import org.springframework.test.web.servlet.MockMvc
+import org.springframework.test.web.servlet.get
+
+/**
+ * Tests for [XssProtectionConfigDsl]
+ *
+ * @author Eleftheria Stein
+ */
+class XssProtectionConfigDslTests {
+    @Rule
+    @JvmField
+    var spring = SpringTestRule()
+
+    @Autowired
+    lateinit var mockMvc: MockMvc
+
+    @Test
+    fun `headers when XSS protection configured then header in response`() {
+        this.spring.register(XssProtectionConfig::class.java).autowire()
+
+        this.mockMvc.get("/") {
+            secure = true
+        }.andExpect {
+            header { string(XXssProtectionServerHttpHeadersWriter.X_XSS_PROTECTION, "1; mode=block") }
+        }
+    }
+
+    @EnableWebSecurity
+    open class XssProtectionConfig : WebSecurityConfigurerAdapter() {
+        override fun configure(http: HttpSecurity) {
+            http {
+                headers {
+                    defaultsDisabled = true
+                    xssProtection { }
+                }
+            }
+        }
+    }
+
+    @Test
+    fun `headers when XSS protection with block false then mode is not block in header`() {
+        this.spring.register(XssProtectionBlockFalseConfig::class.java).autowire()
+
+        this.mockMvc.get("/") {
+            secure = true
+        }.andExpect {
+            header { string(XXssProtectionServerHttpHeadersWriter.X_XSS_PROTECTION, "1") }
+        }
+    }
+
+    @EnableWebSecurity
+    open class XssProtectionBlockFalseConfig : WebSecurityConfigurerAdapter() {
+        override fun configure(http: HttpSecurity) {
+            http {
+                headers {
+                    defaultsDisabled = true
+                    xssProtection {
+                        block = false
+                    }
+                }
+            }
+        }
+    }
+
+    @Test
+    fun `headers when XSS protection disabled then X-XSS-Protection header is 0`() {
+        this.spring.register(XssProtectionDisabledConfig::class.java).autowire()
+
+        this.mockMvc.get("/") {
+            secure = true
+        }.andExpect {
+            header { string(XXssProtectionServerHttpHeadersWriter.X_XSS_PROTECTION, "0") }
+        }
+    }
+
+    @EnableWebSecurity
+    open class XssProtectionDisabledConfig : WebSecurityConfigurerAdapter() {
+        override fun configure(http: HttpSecurity) {
+            http {
+                headers {
+                    defaultsDisabled = true
+                    xssProtection {
+                        xssProtectionEnabled = false
+                    }
+                }
+            }
+        }
+    }
+
+    @Test
+    fun `headers when XSS protection disabled then X-XSS-Protection header not in response`() {
+        this.spring.register(XssProtectionDisabledFunctionConfig::class.java).autowire()
+
+        this.mockMvc.get("/") {
+            secure = true
+        }.andExpect {
+            header { doesNotExist(XXssProtectionServerHttpHeadersWriter.X_XSS_PROTECTION) }
+        }
+    }
+
+    @EnableWebSecurity
+    open class XssProtectionDisabledFunctionConfig : WebSecurityConfigurerAdapter() {
+        override fun configure(http: HttpSecurity) {
+            http {
+                headers {
+                    xssProtection {
+                        disable()
+                    }
+                }
+            }
+        }
+    }
+}

+ 192 - 0
config/src/test/kotlin/org/springframework/security/config/web/servlet/oauth2/client/AuthorizationCodeGrantDslTests.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.servlet.oauth2.client
+
+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.annotation.Bean
+import org.springframework.context.annotation.Configuration
+import org.springframework.security.config.annotation.web.builders.HttpSecurity
+import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity
+import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter
+import org.springframework.security.config.oauth2.client.CommonOAuth2Provider
+import org.springframework.security.config.test.SpringTestRule
+import org.springframework.security.config.web.servlet.invoke
+import org.springframework.security.oauth2.client.endpoint.OAuth2AccessTokenResponseClient
+import org.springframework.security.oauth2.client.endpoint.OAuth2AuthorizationCodeGrantRequest
+import org.springframework.security.oauth2.client.registration.ClientRegistrationRepository
+import org.springframework.security.oauth2.client.registration.InMemoryClientRegistrationRepository
+import org.springframework.security.oauth2.client.web.AuthorizationRequestRepository
+import org.springframework.security.oauth2.client.web.OAuth2AuthorizationRequestResolver
+import org.springframework.security.oauth2.core.OAuth2AccessToken
+import org.springframework.security.oauth2.core.endpoint.OAuth2AccessTokenResponse
+import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationRequest
+import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames
+import org.springframework.test.web.servlet.MockMvc
+import org.springframework.test.web.servlet.get
+
+/**
+ * Tests for [AuthorizationCodeGrantDsl]
+ *
+ * @author Eleftheria Stein
+ */
+class AuthorizationCodeGrantDslTests {
+    @Rule
+    @JvmField
+    var spring = SpringTestRule()
+
+    @Autowired
+    lateinit var mockMvc: MockMvc
+
+    @Test
+    fun `oauth2Client when custom authorization request repository then repository used`() {
+        this.spring.register(RequestRepositoryConfig::class.java, ClientConfig::class.java).autowire()
+
+        this.mockMvc.get("/callback") {
+            param("state", "test")
+            param("code", "123")
+        }
+
+        verify(RequestRepositoryConfig.REQUEST_REPOSITORY).loadAuthorizationRequest(any())
+    }
+
+    @EnableWebSecurity
+    open class RequestRepositoryConfig : WebSecurityConfigurerAdapter() {
+        companion object {
+            var REQUEST_REPOSITORY: AuthorizationRequestRepository<OAuth2AuthorizationRequest> = Mockito.mock(AuthorizationRequestRepository::class.java) as AuthorizationRequestRepository<OAuth2AuthorizationRequest>
+        }
+
+        override fun configure(http: HttpSecurity) {
+            http {
+                oauth2Client {
+                    authorizationCodeGrant {
+                        authorizationRequestRepository = REQUEST_REPOSITORY
+                    }
+                }
+                authorizeRequests {
+                    authorize(anyRequest, authenticated)
+                }
+            }
+        }
+    }
+
+    @Test
+    fun `oauth2Client when custom access token response client then client used`() {
+        this.spring.register(AuthorizedClientConfig::class.java, ClientConfig::class.java).autowire()
+        val authorizationRequest = getOAuth2AuthorizationRequest()
+        Mockito.`when`(AuthorizedClientConfig.REQUEST_REPOSITORY.loadAuthorizationRequest(any()))
+                .thenReturn(authorizationRequest)
+        Mockito.`when`(AuthorizedClientConfig.REQUEST_REPOSITORY.removeAuthorizationRequest(any(), any()))
+                .thenReturn(authorizationRequest)
+        Mockito.`when`(AuthorizedClientConfig.CLIENT.getTokenResponse(any()))
+                .thenReturn(OAuth2AccessTokenResponse
+                        .withToken("token")
+                        .tokenType(OAuth2AccessToken.TokenType.BEARER)
+                        .build())
+
+        this.mockMvc.get("/callback") {
+            param("state", "test")
+            param("code", "123")
+        }
+
+        verify(AuthorizedClientConfig.CLIENT).getTokenResponse(any())
+    }
+
+    @EnableWebSecurity
+    open class AuthorizedClientConfig : WebSecurityConfigurerAdapter() {
+        companion object {
+            var REQUEST_REPOSITORY: AuthorizationRequestRepository<OAuth2AuthorizationRequest> = Mockito.mock(AuthorizationRequestRepository::class.java) as AuthorizationRequestRepository<OAuth2AuthorizationRequest>
+            var CLIENT: OAuth2AccessTokenResponseClient<OAuth2AuthorizationCodeGrantRequest> = Mockito.mock(OAuth2AccessTokenResponseClient::class.java) as OAuth2AccessTokenResponseClient<OAuth2AuthorizationCodeGrantRequest>
+        }
+
+        override fun configure(http: HttpSecurity) {
+            http {
+                oauth2Client {
+                    authorizationCodeGrant {
+                        authorizationRequestRepository = REQUEST_REPOSITORY
+                        accessTokenResponseClient = CLIENT
+                    }
+                }
+                authorizeRequests {
+                    authorize(anyRequest, authenticated)
+                }
+            }
+        }
+    }
+
+    @Test
+    fun `oauth2Client when custom authorization request resolver then request resolver used`() {
+        this.spring.register(RequestResolverConfig::class.java, ClientConfig::class.java).autowire()
+
+        this.mockMvc.get("/callback") {
+            param("state", "test")
+            param("code", "123")
+        }
+
+        verify(RequestResolverConfig.REQUEST_RESOLVER).resolve(any())
+    }
+
+    @EnableWebSecurity
+    open class RequestResolverConfig : WebSecurityConfigurerAdapter() {
+        companion object {
+            var REQUEST_RESOLVER: OAuth2AuthorizationRequestResolver = Mockito.mock(OAuth2AuthorizationRequestResolver::class.java)
+        }
+
+        override fun configure(http: HttpSecurity) {
+            http {
+                oauth2Client {
+                    authorizationCodeGrant {
+                        authorizationRequestResolver = REQUEST_RESOLVER
+                    }
+                }
+                authorizeRequests {
+                    authorize(anyRequest, authenticated)
+                }
+            }
+        }
+    }
+
+    @Configuration
+    open class ClientConfig {
+        @Bean
+        open fun clientRegistrationRepository(): ClientRegistrationRepository {
+            return InMemoryClientRegistrationRepository(
+                    CommonOAuth2Provider.GOOGLE
+                            .getBuilder("google")
+                            .registrationId("registrationId")
+                            .clientId("clientId")
+                            .clientSecret("clientSecret")
+                            .build()
+            )
+        }
+    }
+
+    private fun getOAuth2AuthorizationRequest(): OAuth2AuthorizationRequest? {
+        return OAuth2AuthorizationRequest
+                .authorizationCode()
+                .state("test")
+                .clientId("clientId")
+                .authorizationUri("https://test")
+                .redirectUri("http://localhost/callback")
+                .attributes(mapOf(Pair(OAuth2ParameterNames.REGISTRATION_ID, "registrationId")))
+                .build()
+    }
+}

+ 144 - 0
config/src/test/kotlin/org/springframework/security/config/web/servlet/oauth2/login/AuthorizationEndpointDslTests.kt

@@ -0,0 +1,144 @@
+/*
+ * 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
+
+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.annotation.Bean
+import org.springframework.context.annotation.Configuration
+import org.springframework.security.config.annotation.web.builders.HttpSecurity
+import org.springframework.security.config.web.servlet.invoke
+import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity
+import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter
+import org.springframework.security.config.oauth2.client.CommonOAuth2Provider
+import org.springframework.security.config.test.SpringTestRule
+import org.springframework.security.oauth2.client.registration.ClientRegistrationRepository
+import org.springframework.security.oauth2.client.registration.InMemoryClientRegistrationRepository
+import org.springframework.security.oauth2.client.web.AuthorizationRequestRepository
+import org.springframework.security.oauth2.client.web.OAuth2AuthorizationRequestResolver
+import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationRequest
+import org.springframework.test.web.servlet.MockMvc
+import org.springframework.test.web.servlet.get
+
+/**
+ * Tests for [AuthorizationEndpointDsl]
+ *
+ * @author Eleftheria Stein
+ */
+class AuthorizationEndpointDslTests {
+    @Rule
+    @JvmField
+    var spring = SpringTestRule()
+
+    @Autowired
+    lateinit var mockMvc: MockMvc
+
+    @Test
+    fun `oauth2Login when custom client registration repository then repository used`() {
+        this.spring.register(ResolverConfig::class.java, ClientConfig::class.java).autowire()
+
+        this.mockMvc.get("/oauth2/authorization/google")
+
+        verify(ResolverConfig.RESOLVER).resolve(any())
+    }
+
+    @EnableWebSecurity
+    open class ResolverConfig : WebSecurityConfigurerAdapter() {
+        companion object {
+            var RESOLVER: OAuth2AuthorizationRequestResolver = Mockito.mock(OAuth2AuthorizationRequestResolver::class.java)
+        }
+
+        override fun configure(http: HttpSecurity) {
+            http {
+                oauth2Login {
+                    authorizationEndpoint {
+                        authorizationRequestResolver = RESOLVER
+                    }
+                }
+            }
+        }
+    }
+
+    @Test
+    fun `oauth2Login when custom authorization request repository then repository used`() {
+        this.spring.register(RequestRepoConfig::class.java, ClientConfig::class.java).autowire()
+
+        this.mockMvc.get("/oauth2/authorization/google")
+
+        verify(RequestRepoConfig.REPOSITORY).saveAuthorizationRequest(any(), any(), any())
+    }
+
+    @EnableWebSecurity
+    open class RequestRepoConfig : WebSecurityConfigurerAdapter() {
+        companion object {
+            var REPOSITORY: AuthorizationRequestRepository<OAuth2AuthorizationRequest> = Mockito.mock(AuthorizationRequestRepository::class.java) as AuthorizationRequestRepository<OAuth2AuthorizationRequest>
+        }
+
+        override fun configure(http: HttpSecurity) {
+            http {
+                oauth2Login {
+                    authorizationEndpoint {
+                        authorizationRequestRepository = REPOSITORY
+                    }
+                }
+            }
+        }
+    }
+
+    @Test
+    fun `oauth2Login when custom authorization uri repository then uri used`() {
+        this.spring.register(AuthorizationUriConfig::class.java, ClientConfig::class.java).autowire()
+
+        this.mockMvc.get("/connect/google")
+
+        verify(AuthorizationUriConfig.REPOSITORY).saveAuthorizationRequest(any(), any(), any())
+    }
+
+    @EnableWebSecurity
+    open class AuthorizationUriConfig : WebSecurityConfigurerAdapter() {
+        companion object {
+            var REPOSITORY: AuthorizationRequestRepository<OAuth2AuthorizationRequest> = Mockito.mock(AuthorizationRequestRepository::class.java) as AuthorizationRequestRepository<OAuth2AuthorizationRequest>
+        }
+
+        override fun configure(http: HttpSecurity) {
+            http {
+                oauth2Login {
+                    authorizationEndpoint {
+                        authorizationRequestRepository = REPOSITORY
+                        baseUri = "/connect"
+                    }
+                }
+            }
+        }
+    }
+
+    @Configuration
+    open class ClientConfig {
+        @Bean
+        open fun clientRegistrationRepository(): ClientRegistrationRepository {
+            return InMemoryClientRegistrationRepository(
+                    CommonOAuth2Provider.GOOGLE
+                            .getBuilder("google").clientId("clientId").clientSecret("clientSecret")
+                            .build()
+            )
+        }
+    }
+}

+ 142 - 0
config/src/test/kotlin/org/springframework/security/config/web/servlet/oauth2/login/RedirectionEndpointDslTests.kt

@@ -0,0 +1,142 @@
+/*
+ * 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
+
+import org.junit.Rule
+import org.junit.Test
+import org.mockito.ArgumentMatchers
+import org.mockito.Mockito
+import org.mockito.Mockito.mock
+import org.springframework.beans.factory.annotation.Autowired
+import org.springframework.context.annotation.Bean
+import org.springframework.context.annotation.Configuration
+import org.springframework.security.config.annotation.web.builders.HttpSecurity
+import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity
+import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter
+import org.springframework.security.config.oauth2.client.CommonOAuth2Provider
+import org.springframework.security.config.test.SpringTestRule
+import org.springframework.security.core.authority.SimpleGrantedAuthority
+import org.springframework.security.config.web.servlet.invoke
+import org.springframework.security.oauth2.client.endpoint.OAuth2AccessTokenResponseClient
+import org.springframework.security.oauth2.client.endpoint.OAuth2AuthorizationCodeGrantRequest
+import org.springframework.security.oauth2.client.registration.ClientRegistrationRepository
+import org.springframework.security.oauth2.client.registration.InMemoryClientRegistrationRepository
+import org.springframework.security.oauth2.client.userinfo.OAuth2UserRequest
+import org.springframework.security.oauth2.client.userinfo.OAuth2UserService
+import org.springframework.security.oauth2.client.web.AuthorizationRequestRepository
+import org.springframework.security.oauth2.core.OAuth2AccessToken
+import org.springframework.security.oauth2.core.endpoint.OAuth2AccessTokenResponse
+import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationRequest
+import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames
+import org.springframework.security.oauth2.core.user.DefaultOAuth2User
+import org.springframework.security.oauth2.core.user.OAuth2User
+import org.springframework.test.web.servlet.MockMvc
+import org.springframework.test.web.servlet.get
+import java.util.*
+
+/**
+ * Tests for [RedirectionEndpointDsl]
+ *
+ * @author Eleftheria Stein
+ */
+class RedirectionEndpointDslTests {
+    @Rule
+    @JvmField
+    var spring = SpringTestRule()
+
+    @Autowired
+    lateinit var mockMvc: MockMvc
+
+    @Test
+    fun `oauth2Login when redirection endpoint configured then custom redirection endpoing used`() {
+        this.spring.register(UserServiceConfig::class.java, ClientConfig::class.java).autowire()
+
+        val registrationId = "registrationId"
+        val attributes = HashMap<String, Any>()
+        attributes[OAuth2ParameterNames.REGISTRATION_ID] = registrationId
+        val authorizationRequest = OAuth2AuthorizationRequest
+                .authorizationCode()
+                .state("test")
+                .clientId("clientId")
+                .authorizationUri("https://test")
+                .redirectUri("http://localhost/callback")
+                .attributes(attributes)
+                .build()
+        Mockito.`when`(UserServiceConfig.REPOSITORY.removeAuthorizationRequest(ArgumentMatchers.any(), ArgumentMatchers.any()))
+                .thenReturn(authorizationRequest)
+        Mockito.`when`(UserServiceConfig.CLIENT.getTokenResponse(ArgumentMatchers.any()))
+                .thenReturn(OAuth2AccessTokenResponse
+                        .withToken("token")
+                        .tokenType(OAuth2AccessToken.TokenType.BEARER)
+                        .build())
+        Mockito.`when`(UserServiceConfig.USER_SERVICE.loadUser(ArgumentMatchers.any()))
+                .thenReturn(DefaultOAuth2User(listOf(SimpleGrantedAuthority("ROLE_USER")), mapOf(Pair("user", "user")), "user"))
+
+        this.mockMvc.get("/callback") {
+            param("code", "auth-code")
+            param("state", "test")
+        }.andExpect {
+            redirectedUrl("/")
+        }
+    }
+
+    @EnableWebSecurity
+    open class UserServiceConfig : WebSecurityConfigurerAdapter() {
+        companion object {
+            var USER_SERVICE: OAuth2UserService<OAuth2UserRequest, OAuth2User> = mock(OAuth2UserService::class.java) as OAuth2UserService<OAuth2UserRequest, OAuth2User>
+            var CLIENT: OAuth2AccessTokenResponseClient<OAuth2AuthorizationCodeGrantRequest> = mock(OAuth2AccessTokenResponseClient::class.java) as OAuth2AccessTokenResponseClient<OAuth2AuthorizationCodeGrantRequest>
+            var REPOSITORY: AuthorizationRequestRepository<OAuth2AuthorizationRequest> = mock(AuthorizationRequestRepository::class.java) as AuthorizationRequestRepository<OAuth2AuthorizationRequest>
+        }
+
+        override fun configure(http: HttpSecurity) {
+            http {
+                authorizeRequests {
+                    authorize(anyRequest, authenticated)
+                }
+                oauth2Login {
+                    userInfoEndpoint {
+                        userService = USER_SERVICE
+                    }
+                    tokenEndpoint {
+                        accessTokenResponseClient = CLIENT
+                    }
+                    authorizationEndpoint {
+                        authorizationRequestRepository = REPOSITORY
+                    }
+                    redirectionEndpoint {
+                        baseUri = "/callback"
+                    }
+                }
+            }
+        }
+    }
+
+    @Configuration
+    open class ClientConfig {
+        @Bean
+        open fun clientRegistrationRepository(): ClientRegistrationRepository {
+            return InMemoryClientRegistrationRepository(
+                    CommonOAuth2Provider.GOOGLE
+                            .getBuilder("google")
+                            .registrationId("registrationId")
+                            .clientId("clientId")
+                            .clientSecret("clientSecret")
+                            .build()
+            )
+        }
+    }
+}

+ 128 - 0
config/src/test/kotlin/org/springframework/security/config/web/servlet/oauth2/login/TokenEndpointDslTests.kt

@@ -0,0 +1,128 @@
+/*
+ * 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
+
+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.annotation.Bean
+import org.springframework.context.annotation.Configuration
+import org.springframework.security.config.annotation.web.builders.HttpSecurity
+import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity
+import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter
+import org.springframework.security.config.oauth2.client.CommonOAuth2Provider
+import org.springframework.security.config.test.SpringTestRule
+import org.springframework.security.config.web.servlet.invoke
+import org.springframework.security.oauth2.client.endpoint.OAuth2AccessTokenResponseClient
+import org.springframework.security.oauth2.client.endpoint.OAuth2AuthorizationCodeGrantRequest
+import org.springframework.security.oauth2.client.registration.ClientRegistrationRepository
+import org.springframework.security.oauth2.client.registration.InMemoryClientRegistrationRepository
+import org.springframework.security.oauth2.client.web.AuthorizationRequestRepository
+import org.springframework.security.oauth2.core.OAuth2AccessToken
+import org.springframework.security.oauth2.core.endpoint.OAuth2AccessTokenResponse
+import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationRequest
+import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames
+import org.springframework.test.web.servlet.MockMvc
+import org.springframework.test.web.servlet.get
+import java.util.*
+
+/**
+ * Tests for [TokenEndpointDsl]
+ *
+ * @author Eleftheria Stein
+ */
+class TokenEndpointDslTests {
+    @Rule
+    @JvmField
+    var spring = SpringTestRule()
+
+    @Autowired
+    lateinit var mockMvc: MockMvc
+
+    @Test
+    fun `oauth2Login when custom access token response client then client used`() {
+        this.spring.register(TokenConfig::class.java, ClientConfig::class.java).autowire()
+
+        val registrationId = "registrationId"
+        val attributes = HashMap<String, Any>()
+        attributes[OAuth2ParameterNames.REGISTRATION_ID] = registrationId
+        val authorizationRequest = OAuth2AuthorizationRequest
+                .authorizationCode()
+                .state("test")
+                .clientId("clientId")
+                .authorizationUri("https://test")
+                .redirectUri("http://localhost/login/oauth2/code/google")
+                .attributes(attributes)
+                .build()
+        `when`(TokenConfig.REPOSITORY.removeAuthorizationRequest(any(), any()))
+                .thenReturn(authorizationRequest)
+        `when`(TokenConfig.CLIENT.getTokenResponse(any())).thenReturn(OAuth2AccessTokenResponse
+                .withToken("token")
+                .tokenType(OAuth2AccessToken.TokenType.BEARER)
+                .build())
+
+        this.mockMvc.get("/login/oauth2/code/google") {
+            param("code", "auth-code")
+            param("state", "test")
+        }
+
+        Mockito.verify(TokenConfig.CLIENT).getTokenResponse(any())
+    }
+
+    @EnableWebSecurity
+    open class TokenConfig : WebSecurityConfigurerAdapter() {
+        companion object {
+            var CLIENT: OAuth2AccessTokenResponseClient<OAuth2AuthorizationCodeGrantRequest> = mock(OAuth2AccessTokenResponseClient::class.java) as OAuth2AccessTokenResponseClient<OAuth2AuthorizationCodeGrantRequest>
+            var REPOSITORY: AuthorizationRequestRepository<OAuth2AuthorizationRequest> = mock(AuthorizationRequestRepository::class.java) as AuthorizationRequestRepository<OAuth2AuthorizationRequest>
+        }
+
+        override fun configure(http: HttpSecurity) {
+            http {
+                authorizeRequests {
+                    authorize(anyRequest, authenticated)
+                }
+                oauth2Login {
+                    tokenEndpoint {
+                        accessTokenResponseClient = CLIENT
+                    }
+                    authorizationEndpoint {
+                        authorizationRequestRepository = REPOSITORY
+                    }
+                }
+            }
+        }
+    }
+
+    @Configuration
+    open class ClientConfig {
+        @Bean
+        open fun clientRegistrationRepository(): ClientRegistrationRepository {
+            return InMemoryClientRegistrationRepository(
+                    CommonOAuth2Provider.GOOGLE
+                            .getBuilder("google")
+                            .registrationId("registrationId")
+                            .clientId("clientId")
+                            .clientSecret("clientSecret")
+                            .build()
+            )
+        }
+    }
+}

+ 139 - 0
config/src/test/kotlin/org/springframework/security/config/web/servlet/oauth2/login/UserInfoEndpointDslTests.kt

@@ -0,0 +1,139 @@
+/*
+ * 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
+
+import org.junit.Rule
+import org.junit.Test
+import org.mockito.ArgumentMatchers.any
+import org.mockito.Mockito
+import org.mockito.Mockito.`when`
+import org.springframework.beans.factory.annotation.Autowired
+import org.springframework.context.annotation.Bean
+import org.springframework.context.annotation.Configuration
+import org.springframework.security.config.annotation.web.builders.HttpSecurity
+import org.springframework.security.config.web.servlet.invoke
+import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity
+import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter
+import org.springframework.security.config.oauth2.client.CommonOAuth2Provider
+import org.springframework.security.core.authority.SimpleGrantedAuthority
+import org.springframework.security.config.test.SpringTestRule
+import org.springframework.security.oauth2.client.endpoint.OAuth2AccessTokenResponseClient
+import org.springframework.security.oauth2.client.endpoint.OAuth2AuthorizationCodeGrantRequest
+import org.springframework.security.oauth2.client.registration.ClientRegistrationRepository
+import org.springframework.security.oauth2.client.registration.InMemoryClientRegistrationRepository
+import org.springframework.security.oauth2.client.userinfo.OAuth2UserRequest
+import org.springframework.security.oauth2.client.userinfo.OAuth2UserService
+import org.springframework.security.oauth2.client.web.AuthorizationRequestRepository
+import org.springframework.security.oauth2.core.OAuth2AccessToken
+import org.springframework.security.oauth2.core.endpoint.OAuth2AccessTokenResponse
+import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationRequest
+import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames
+import org.springframework.security.oauth2.core.user.DefaultOAuth2User
+import org.springframework.security.oauth2.core.user.OAuth2User
+import org.springframework.test.web.servlet.MockMvc
+import org.springframework.test.web.servlet.get
+import java.util.*
+
+/**
+ * Tests for [UserInfoEndpointDsl]
+ *
+ * @author Eleftheria Stein
+ */
+class UserInfoEndpointDslTests {
+    @Rule
+    @JvmField
+    var spring = SpringTestRule()
+
+    @Autowired
+    lateinit var mockMvc: MockMvc
+
+    @Test
+    fun `oauth2Login when custom user service then user service used`() {
+        this.spring.register(UserServiceConfig::class.java, ClientConfig::class.java).autowire()
+
+        val registrationId = "registrationId"
+        val attributes = HashMap<String, Any>()
+        attributes[OAuth2ParameterNames.REGISTRATION_ID] = registrationId
+        val authorizationRequest = OAuth2AuthorizationRequest
+                .authorizationCode()
+                .state("test")
+                .clientId("clientId")
+                .authorizationUri("https://test")
+                .redirectUri("http://localhost/login/oauth2/code/google")
+                .attributes(attributes)
+                .build()
+        `when`(UserServiceConfig.REPOSITORY.removeAuthorizationRequest(any(), any()))
+                .thenReturn(authorizationRequest)
+        `when`(UserServiceConfig.CLIENT.getTokenResponse(any()))
+                .thenReturn(OAuth2AccessTokenResponse
+                        .withToken("token")
+                        .tokenType(OAuth2AccessToken.TokenType.BEARER)
+                        .build())
+        `when`(UserServiceConfig.USER_SERVICE.loadUser(any()))
+                .thenReturn(DefaultOAuth2User(listOf(SimpleGrantedAuthority("ROLE_USER")), mapOf(Pair("user", "user")), "user"))
+
+        this.mockMvc.get("/login/oauth2/code/google") {
+            param("code", "auth-code")
+            param("state", "test")
+        }
+
+        Mockito.verify(UserServiceConfig.USER_SERVICE).loadUser(any())
+    }
+
+    @EnableWebSecurity
+    open class UserServiceConfig : WebSecurityConfigurerAdapter() {
+        companion object {
+            var USER_SERVICE: OAuth2UserService<OAuth2UserRequest, OAuth2User> = Mockito.mock(OAuth2UserService::class.java) as OAuth2UserService<OAuth2UserRequest, OAuth2User>
+            var CLIENT: OAuth2AccessTokenResponseClient<OAuth2AuthorizationCodeGrantRequest> = Mockito.mock(OAuth2AccessTokenResponseClient::class.java) as OAuth2AccessTokenResponseClient<OAuth2AuthorizationCodeGrantRequest>
+            var REPOSITORY: AuthorizationRequestRepository<OAuth2AuthorizationRequest> = Mockito.mock(AuthorizationRequestRepository::class.java) as AuthorizationRequestRepository<OAuth2AuthorizationRequest>
+        }
+
+        override fun configure(http: HttpSecurity) {
+            http {
+                authorizeRequests {
+                    authorize(anyRequest, authenticated)
+                }
+                oauth2Login {
+                    userInfoEndpoint {
+                        userService = USER_SERVICE
+                    }
+                    tokenEndpoint {
+                        accessTokenResponseClient = CLIENT
+                    }
+                    authorizationEndpoint {
+                        authorizationRequestRepository = REPOSITORY
+                    }
+                }
+            }
+        }
+    }
+
+    @Configuration
+    open class ClientConfig {
+        @Bean
+        open fun clientRegistrationRepository(): ClientRegistrationRepository {
+            return InMemoryClientRegistrationRepository(
+                    CommonOAuth2Provider.GOOGLE
+                            .getBuilder("google")
+                            .registrationId("registrationId")
+                            .clientId("clientId")
+                            .clientSecret("clientSecret")
+                            .build()
+            )
+        }
+    }
+}

+ 129 - 0
config/src/test/kotlin/org/springframework/security/config/web/servlet/oauth2/resourceserver/JwtDslTests.kt

@@ -0,0 +1,129 @@
+/*
+ * 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
+
+import org.junit.Rule
+import org.junit.Test
+import org.mockito.Mockito.*
+import org.springframework.beans.factory.annotation.Autowired
+import org.springframework.context.annotation.Bean
+import org.springframework.core.convert.converter.Converter
+import org.springframework.security.authentication.AbstractAuthenticationToken
+import org.springframework.security.authentication.TestingAuthenticationToken
+import org.springframework.security.config.annotation.web.builders.HttpSecurity
+import org.springframework.security.config.web.servlet.invoke
+import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity
+import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter
+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.JwtDecoder
+import org.springframework.test.web.servlet.MockMvc
+import org.springframework.test.web.servlet.get
+
+/**
+ * Tests for [JwtDsl]
+ *
+ * @author Eleftheria Stein
+ */
+class JwtDslTests {
+    @Rule
+    @JvmField
+    val spring = SpringTestRule()
+
+    @Autowired
+    lateinit var mockMvc: MockMvc
+
+    @Test
+    fun `JWT when custom JWT decoder then bean not required`() {
+        this.spring.register(CustomJwtDecoderConfig::class.java).autowire()
+    }
+
+    @EnableWebSecurity
+    open class CustomJwtDecoderConfig : WebSecurityConfigurerAdapter() {
+        override fun configure(http: HttpSecurity) {
+            http {
+                oauth2ResourceServer {
+                    jwt {
+                        jwtDecoder = mock(JwtDecoder::class.java)
+                    }
+                }
+            }
+        }
+    }
+
+    @Test
+    fun `JWT when custom jwkSetUri then bean not required`() {
+        this.spring.register(CustomJwkSetUriConfig::class.java).autowire()
+    }
+
+    @EnableWebSecurity
+    open class CustomJwkSetUriConfig : WebSecurityConfigurerAdapter() {
+        override fun configure(http: HttpSecurity) {
+            http {
+                oauth2ResourceServer {
+                    jwt {
+                        jwkSetUri = "https://jwk-uri"
+                    }
+                }
+            }
+        }
+    }
+
+    @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(
+                Jwt.withTokenValue("token")
+                        .header("alg", "none")
+                        .claim(IdTokenClaimNames.SUB, "user")
+                        .build())
+        `when`(CustomJwtAuthenticationConverterConfig.CONVERTER.convert(any()))
+                .thenReturn(TestingAuthenticationToken("test", "this", "ROLE"))
+        this.mockMvc.get("/") {
+            header("Authorization", "Bearer token")
+        }
+
+        verify(CustomJwtAuthenticationConverterConfig.CONVERTER).convert(any())
+    }
+
+    @EnableWebSecurity
+    open class CustomJwtAuthenticationConverterConfig : WebSecurityConfigurerAdapter() {
+        companion object {
+            var CONVERTER: Converter<Jwt, out AbstractAuthenticationToken> = mock(Converter::class.java) as Converter<Jwt, out AbstractAuthenticationToken>
+            var DECODER: JwtDecoder = mock(JwtDecoder::class.java)
+        }
+
+        override fun configure(http: HttpSecurity) {
+            http {
+                authorizeRequests {
+                    authorize(anyRequest, authenticated)
+                }
+                oauth2ResourceServer {
+                    jwt {
+                        jwtAuthenticationConverter = CONVERTER
+                    }
+                }
+            }
+        }
+
+        @Bean
+        open fun jwtDecoder(): JwtDecoder {
+            return DECODER
+        }
+    }
+}

+ 148 - 0
config/src/test/kotlin/org/springframework/security/config/web/servlet/oauth2/resourceserver/OpaqueTokenDslTests.kt

@@ -0,0 +1,148 @@
+/*
+ * 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
+
+import org.junit.Rule
+import org.junit.Test
+import org.mockito.ArgumentMatchers
+import org.mockito.ArgumentMatchers.any
+import org.mockito.ArgumentMatchers.eq
+import org.mockito.Mockito.*
+import org.springframework.beans.factory.annotation.Autowired
+import org.springframework.context.annotation.Bean
+import org.springframework.http.*
+import org.springframework.security.config.annotation.web.builders.HttpSecurity
+import org.springframework.security.config.web.servlet.invoke
+import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity
+import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter
+import org.springframework.security.config.test.SpringTestRule
+import org.springframework.security.core.Authentication
+import org.springframework.security.core.annotation.AuthenticationPrincipal
+import org.springframework.security.oauth2.core.DefaultOAuth2AuthenticatedPrincipal
+import org.springframework.security.oauth2.jwt.JwtClaimNames
+import org.springframework.security.oauth2.server.resource.introspection.NimbusOpaqueTokenIntrospector
+import org.springframework.security.oauth2.server.resource.introspection.OpaqueTokenIntrospector
+import org.springframework.test.web.servlet.MockMvc
+import org.springframework.test.web.servlet.get
+import org.springframework.web.bind.annotation.GetMapping
+import org.springframework.web.bind.annotation.RestController
+import org.springframework.web.client.RestOperations
+
+/**
+ * Tests for [OpaqueTokenDsl]
+ *
+ * @author Eleftheria Stein
+ */
+class OpaqueTokenDslTests {
+    @Rule
+    @JvmField
+    val spring = SpringTestRule()
+
+    @Autowired
+    lateinit var mockMvc: MockMvc
+
+    @Test
+    fun `opaque token when defaults then uses introspection`() {
+        this.spring.register(DefaultOpaqueConfig::class.java, AuthenticationController::class.java).autowire()
+        val headers = HttpHeaders()
+        headers.contentType = MediaType.APPLICATION_JSON
+        val entity = ResponseEntity("{\n" +
+                "  \"active\" : true,\n" +
+                "  \"sub\": \"test-subject\",\n" +
+                "  \"scope\": \"message:read\",\n" +
+                "  \"exp\": 4683883211\n" +
+                "}", headers, HttpStatus.OK)
+        `when`(DefaultOpaqueConfig.REST.exchange(any(RequestEntity::class.java), eq(String::class.java)))
+                .thenReturn(entity)
+
+        this.mockMvc.get("/authenticated") {
+            header("Authorization", "Bearer token")
+        }.andExpect {
+            status { isOk }
+            content { string("test-subject") }
+        }
+    }
+
+    @EnableWebSecurity
+    open class DefaultOpaqueConfig : WebSecurityConfigurerAdapter() {
+        companion object {
+            var REST: RestOperations = mock(RestOperations::class.java)
+        }
+
+        override fun configure(http: HttpSecurity) {
+            http {
+                authorizeRequests {
+                    authorize(anyRequest, authenticated)
+                }
+                oauth2ResourceServer {
+                    opaqueToken { }
+                }
+            }
+        }
+
+        @Bean
+        open fun rest(): RestOperations {
+            return REST
+        }
+
+        @Bean
+        open fun tokenIntrospectionClient(): NimbusOpaqueTokenIntrospector {
+            return NimbusOpaqueTokenIntrospector("https://example.org/introspect", REST)
+        }
+    }
+
+    @Test
+    fun `opaque token when custom introspector set then introspector used`() {
+        this.spring.register(CustomIntrospectorConfig::class.java, AuthenticationController::class.java).autowire()
+        `when`(CustomIntrospectorConfig.INTROSPECTOR.introspect(ArgumentMatchers.anyString()))
+                .thenReturn(DefaultOAuth2AuthenticatedPrincipal(mapOf(Pair(JwtClaimNames.SUB, "mock-subject")), emptyList()))
+
+        this.mockMvc.get("/authenticated") {
+            header("Authorization", "Bearer token")
+        }
+
+        verify(CustomIntrospectorConfig.INTROSPECTOR).introspect("token")
+    }
+
+    @EnableWebSecurity
+    open class CustomIntrospectorConfig : WebSecurityConfigurerAdapter() {
+        companion object {
+            var INTROSPECTOR: OpaqueTokenIntrospector = mock(OpaqueTokenIntrospector::class.java)
+        }
+
+        override fun configure(http: HttpSecurity) {
+            http {
+                authorizeRequests {
+                    authorize(anyRequest, authenticated)
+                }
+                oauth2ResourceServer {
+                    opaqueToken {
+                        introspector = INTROSPECTOR
+                    }
+                }
+            }
+        }
+    }
+
+    @RestController
+    class AuthenticationController {
+        @GetMapping("/authenticated")
+        fun authenticated(@AuthenticationPrincipal authentication: Authentication): String {
+            return authentication.name
+        }
+    }
+}

+ 176 - 0
config/src/test/kotlin/org/springframework/security/config/web/servlet/session/SessionConcurrencyDslTests.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.servlet.session
+
+import org.junit.Rule
+import org.junit.Test
+import org.mockito.Mockito.*
+import org.springframework.beans.factory.annotation.Autowired
+import org.springframework.context.annotation.Bean
+import org.springframework.context.annotation.Configuration
+import org.springframework.mock.web.MockHttpSession
+import org.springframework.security.config.annotation.web.builders.HttpSecurity
+import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity
+import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter
+import org.springframework.security.config.test.SpringTestRule
+import org.springframework.security.core.session.SessionInformation
+import org.springframework.security.core.session.SessionRegistry
+import org.springframework.security.core.userdetails.User
+import org.springframework.security.core.userdetails.UserDetailsService
+import org.springframework.security.config.web.servlet.invoke
+import org.springframework.security.provisioning.InMemoryUserDetailsManager
+import org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.csrf
+import org.springframework.security.web.session.SimpleRedirectSessionInformationExpiredStrategy
+import org.springframework.test.web.servlet.MockMvc
+import org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get
+import org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post
+import org.springframework.test.web.servlet.result.MockMvcResultMatchers.redirectedUrl
+import org.springframework.test.web.servlet.result.MockMvcResultMatchers.status
+import java.util.*
+
+/**
+ * Tests for [SessionConcurrencyDsl]
+ *
+ * @author Eleftheria Stein
+ */
+class SessionConcurrencyDslTests {
+    @Rule
+    @JvmField
+    var spring = SpringTestRule()
+
+    @Autowired
+    lateinit var mockMvc: MockMvc
+
+    @Test
+    fun `session concurrency when maximum sessions then no more sessions allowed`() {
+        this.spring.register(MaximumSessionsConfig::class.java, UserDetailsConfig::class.java).autowire()
+
+        this.mockMvc.perform(post("/login")
+                .with(csrf())
+                .param("username", "user")
+                .param("password", "password"))
+
+        this.mockMvc.perform(post("/login")
+                .with(csrf())
+                .param("username", "user")
+                .param("password", "password"))
+                .andExpect(status().isFound)
+                .andExpect(redirectedUrl("/login?error"))
+    }
+
+    @EnableWebSecurity
+    open class MaximumSessionsConfig : WebSecurityConfigurerAdapter() {
+        override fun configure(http: HttpSecurity) {
+            http {
+                sessionManagement {
+                    sessionConcurrency {
+                        maximumSessions = 1
+                        maxSessionsPreventsLogin = true
+                    }
+                }
+                formLogin { }
+            }
+        }
+    }
+
+    @Test
+    fun `session concurrency when expired url then redirects to url`() {
+        this.spring.register(ExpiredUrlConfig::class.java).autowire()
+
+        val session = MockHttpSession()
+        val sessionInformation = SessionInformation("", session.id, Date(0))
+        sessionInformation.expireNow()
+        `when`(ExpiredUrlConfig.sessionRegistry.getSessionInformation(any())).thenReturn(sessionInformation)
+
+        this.mockMvc.perform(get("/").session(session))
+                .andExpect(redirectedUrl("/expired-session"))
+    }
+
+    @EnableWebSecurity
+    open class ExpiredUrlConfig : WebSecurityConfigurerAdapter() {
+        companion object {
+            val sessionRegistry: SessionRegistry = mock(SessionRegistry::class.java)
+        }
+
+        override fun configure(http: HttpSecurity) {
+            http {
+                sessionManagement {
+                    sessionConcurrency {
+                        maximumSessions = 1
+                        expiredUrl = "/expired-session"
+                        sessionRegistry = sessionRegistry()
+                    }
+                }
+            }
+        }
+
+        @Bean
+        open fun sessionRegistry(): SessionRegistry {
+            return sessionRegistry
+        }
+    }
+
+    @Test
+    fun `session concurrency when expired session strategy then strategy used`() {
+        this.spring.register(ExpiredSessionStrategyConfig::class.java).autowire()
+
+        val session = MockHttpSession()
+        val sessionInformation = SessionInformation("", session.id, Date(0))
+        sessionInformation.expireNow()
+        `when`(ExpiredSessionStrategyConfig.sessionRegistry.getSessionInformation(any())).thenReturn(sessionInformation)
+
+        this.mockMvc.perform(get("/").session(session))
+                .andExpect(redirectedUrl("/expired-session"))
+    }
+
+    @EnableWebSecurity
+    open class ExpiredSessionStrategyConfig : WebSecurityConfigurerAdapter() {
+        companion object {
+            val sessionRegistry: SessionRegistry = mock(SessionRegistry::class.java)
+        }
+
+        override fun configure(http: HttpSecurity) {
+            http {
+                sessionManagement {
+                    sessionConcurrency {
+                        maximumSessions = 1
+                        expiredSessionStrategy = SimpleRedirectSessionInformationExpiredStrategy("/expired-session")
+                        sessionRegistry = sessionRegistry()
+                    }
+                }
+            }
+        }
+
+        @Bean
+        open fun sessionRegistry(): SessionRegistry {
+            return sessionRegistry
+        }
+    }
+
+    @Configuration
+    open class UserDetailsConfig {
+        @Bean
+        open fun userDetailsService(): UserDetailsService {
+            val userDetails = User.withDefaultPasswordEncoder()
+                    .username("user")
+                    .password("password")
+                    .roles("USER")
+                    .build()
+            return InMemoryUserDetailsManager(userDetails)
+        }
+    }
+}

+ 195 - 0
config/src/test/kotlin/org/springframework/security/config/web/servlet/session/SessionFixationDslTest.kt

@@ -0,0 +1,195 @@
+/*
+ * 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
+
+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.annotation.Bean
+import org.springframework.context.annotation.Configuration
+import org.springframework.mock.web.MockHttpSession
+import org.springframework.security.config.annotation.web.builders.HttpSecurity
+import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity
+import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter
+import org.springframework.security.core.userdetails.User
+import org.springframework.security.core.userdetails.UserDetailsService
+import org.springframework.security.config.web.servlet.invoke
+import org.springframework.security.config.test.SpringTestRule
+import org.springframework.security.provisioning.InMemoryUserDetailsManager
+import org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.httpBasic
+import org.springframework.test.web.servlet.MockMvc
+import org.springframework.test.web.servlet.request.MockMvcRequestBuilders
+
+/**
+ * Tests for [SessionFixationDsl]
+ *
+ * @author Eleftheria Stein
+ */
+class SessionFixationDslTest {
+    @Rule
+    @JvmField
+    var spring = SpringTestRule()
+
+    @Autowired
+    lateinit var mockMvc: MockMvc
+
+    @Test
+    fun `session fixation when strategy is new session then new session created and attributes are not preserved`() {
+        this.spring.register(NewSessionConfig::class.java, UserDetailsConfig::class.java).autowire()
+        val givenSession = MockHttpSession()
+        val givenSessionId = givenSession.id
+        givenSession.clearAttributes()
+        givenSession.setAttribute("name", "value")
+
+        val result = this.mockMvc.perform(MockMvcRequestBuilders.get("/")
+                .with(httpBasic("user", "password"))
+                .session(givenSession))
+                .andReturn()
+
+        val resultingSession = result.request.getSession(false)
+        assertThat(resultingSession).isNotEqualTo(givenSession)
+        assertThat(resultingSession!!.id).isNotEqualTo(givenSessionId)
+        assertThat(resultingSession.getAttribute("name")).isNull()
+    }
+
+    @EnableWebSecurity
+    open class NewSessionConfig : WebSecurityConfigurerAdapter() {
+        override fun configure(http: HttpSecurity) {
+            http {
+                sessionManagement {
+                    sessionFixation {
+                        newSession()
+                    }
+                }
+                httpBasic { }
+            }
+        }
+    }
+
+    @Test
+    fun `session fixation when strategy is migrate session then new session created and attributes are preserved`() {
+        this.spring.register(MigrateSessionConfig::class.java, UserDetailsConfig::class.java).autowire()
+        val givenSession = MockHttpSession()
+        val givenSessionId = givenSession.id
+        givenSession.clearAttributes()
+        givenSession.setAttribute("name", "value")
+
+        val result = this.mockMvc.perform(MockMvcRequestBuilders.get("/")
+                .with(httpBasic("user", "password"))
+                .session(givenSession))
+                .andReturn()
+
+        val resultingSession = result.request.getSession(false)
+        assertThat(resultingSession).isNotEqualTo(givenSession)
+        assertThat(resultingSession!!.id).isNotEqualTo(givenSessionId)
+        assertThat(resultingSession.getAttribute("name")).isEqualTo("value")
+    }
+
+    @EnableWebSecurity
+    open class MigrateSessionConfig : WebSecurityConfigurerAdapter() {
+        override fun configure(http: HttpSecurity) {
+            http {
+                sessionManagement {
+                    sessionFixation {
+                        migrateSession()
+                    }
+                }
+                httpBasic { }
+            }
+        }
+    }
+
+    @Test
+    fun `session fixation when strategy is change session id then session id changes and attributes preserved`() {
+        this.spring.register(ChangeSessionIdConfig::class.java, UserDetailsConfig::class.java).autowire()
+        val givenSession = MockHttpSession()
+        val givenSessionId = givenSession.id
+        givenSession.clearAttributes()
+        givenSession.setAttribute("name", "value")
+
+        val result = this.mockMvc.perform(MockMvcRequestBuilders.get("/")
+                .with(httpBasic("user", "password"))
+                .session(givenSession))
+                .andReturn()
+
+        val resultingSession = result.request.getSession(false)
+        assertThat(resultingSession).isEqualTo(givenSession)
+        assertThat(resultingSession!!.id).isNotEqualTo(givenSessionId)
+        assertThat(resultingSession.getAttribute("name")).isEqualTo("value")
+    }
+
+    @EnableWebSecurity
+    open class ChangeSessionIdConfig : WebSecurityConfigurerAdapter() {
+        override fun configure(http: HttpSecurity) {
+            http {
+                sessionManagement {
+                    sessionFixation {
+                        changeSessionId()
+                    }
+                }
+                httpBasic { }
+            }
+        }
+    }
+
+    @Test
+    fun `session fixation when strategy is none then session does not change`() {
+        this.spring.register(NoneConfig::class.java, UserDetailsConfig::class.java).autowire()
+        val givenSession = MockHttpSession()
+        val givenSessionId = givenSession.id
+        givenSession.clearAttributes()
+        givenSession.setAttribute("name", "value")
+
+        val result = this.mockMvc.perform(MockMvcRequestBuilders.get("/")
+                .with(httpBasic("user", "password"))
+                .session(givenSession))
+                .andReturn()
+
+        val resultingSession = result.request.getSession(false)
+        assertThat(resultingSession).isEqualTo(givenSession)
+        assertThat(resultingSession!!.id).isEqualTo(givenSessionId)
+        assertThat(resultingSession.getAttribute("name")).isEqualTo("value")
+    }
+
+    @EnableWebSecurity
+    open class NoneConfig : WebSecurityConfigurerAdapter() {
+        override fun configure(http: HttpSecurity) {
+            http {
+                sessionManagement {
+                    sessionFixation {
+                        none()
+                    }
+                }
+                httpBasic { }
+            }
+        }
+    }
+
+    @Configuration
+    open class UserDetailsConfig {
+        @Bean
+        open fun userDetailsService(): UserDetailsService {
+            val userDetails = User.withDefaultPasswordEncoder()
+                    .username("user")
+                    .password("password")
+                    .roles("USER")
+                    .build()
+            return InMemoryUserDetailsManager(userDetails)
+        }
+    }
+}

+ 1 - 0
gradle.properties

@@ -3,3 +3,4 @@ gaeVersion=1.9.76
 springBootVersion=2.2.0.RELEASE
 version=5.3.0.BUILD-SNAPSHOT
 org.gradle.jvmargs=-Xmx3g -XX:MaxPermSize=2048m -XX:+HeapDumpOnOutOfMemoryError
+kotlinVersion=1.3.61

+ 4 - 0
gradle/dependency-management.gradle

@@ -9,6 +9,9 @@ if (!project.hasProperty("springVersion")) {
 if (!project.hasProperty("springDataVersion")) {
 	ext.springDataVersion = "Moore-SR+"
 }
+if (!project.hasProperty("kotlinVersion")) {
+	ext.kotlinVersion = "1.3.61"
+}
 ext.rsocketVersion = "1.+"
 ext.openSamlVersion = "3.+"
 
@@ -29,6 +32,7 @@ dependencies {
 	management platform("org.springframework:spring-framework-bom:$springVersion")
 	management platform("io.projectreactor:reactor-bom:$reactorVersion")
 	management platform("org.springframework.data:spring-data-releasetrain:$springDataVersion")
+	management platform("org.jetbrains.kotlin:kotlin-bom:$kotlinVersion")
 	constraints {
 		management "ch.qos.logback:logback-classic:1.+"
 		management "com.fasterxml.jackson.core:jackson-databind:2.+"

+ 31 - 0
samples/boot/kotlin/spring-security-samples-boot-kotlin.gradle.kts

@@ -0,0 +1,31 @@
+import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
+
+plugins {
+    id("io.spring.convention.spring-sample-boot")
+    kotlin("jvm")
+    kotlin("plugin.spring") version "1.3.61"
+}
+
+repositories {
+    mavenCentral()
+}
+
+dependencies {
+    implementation(project(":spring-security-core"))
+    implementation(project(":spring-security-config"))
+    implementation(project(":spring-security-web"))
+    implementation("org.springframework.boot:spring-boot-starter-web")
+    implementation("org.springframework.boot:spring-boot-starter-thymeleaf")
+    implementation("org.thymeleaf.extras:thymeleaf-extras-springsecurity5")
+    implementation("org.jetbrains.kotlin:kotlin-reflect")
+    implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8")
+    testImplementation(project(":spring-security-test"))
+    testImplementation("org.springframework.boot:spring-boot-starter-test")
+}
+
+tasks.withType<KotlinCompile> {
+    kotlinOptions {
+        freeCompilerArgs = listOf("-Xjsr305=strict")
+        jvmTarget = "1.8"
+    }
+}

+ 29 - 0
samples/boot/kotlin/src/main/kotlin/org/springframework/security/samples/KotlinApplication.kt

@@ -0,0 +1,29 @@
+/*
+ * 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.samples
+
+import org.springframework.boot.autoconfigure.SpringBootApplication
+import org.springframework.boot.runApplication
+
+/**
+ * @author Eleftheria Stein
+ */
+@SpringBootApplication
+class KotlinApplication
+
+fun main(args: Array<String>) {
+	runApplication<KotlinApplication>(*args)
+}

+ 55 - 0
samples/boot/kotlin/src/main/kotlin/org/springframework/security/samples/config/SecurityConfig.kt

@@ -0,0 +1,55 @@
+/*
+ * 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.samples.config
+
+import org.springframework.context.annotation.Bean
+import org.springframework.security.config.annotation.web.builders.HttpSecurity
+import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity
+import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter
+import org.springframework.security.config.web.servlet.invoke
+import org.springframework.security.core.userdetails.User
+import org.springframework.security.core.userdetails.UserDetailsService
+import org.springframework.security.provisioning.InMemoryUserDetailsManager
+
+/**
+ * @author Eleftheria Stein
+ */
+@EnableWebSecurity
+class SecurityConfig : WebSecurityConfigurerAdapter() {
+
+    override fun configure(http: HttpSecurity) {
+       http {
+            authorizeRequests {
+                authorize("/css/**", permitAll)
+                authorize("/user/**", hasAuthority("ROLE_USER"))
+            }
+           formLogin {
+               loginPage = "/log-in"
+           }
+        }
+    }
+
+    @Bean
+    public override fun userDetailsService(): UserDetailsService {
+        val userDetails = User.withDefaultPasswordEncoder()
+                .username("user")
+                .password("password")
+                .roles("USER")
+                .build()
+        return InMemoryUserDetailsManager(userDetails)
+    }
+}

+ 42 - 0
samples/boot/kotlin/src/main/kotlin/org/springframework/security/samples/web/MainController.kt

@@ -0,0 +1,42 @@
+/*
+ * 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.samples.web
+
+import org.springframework.stereotype.Controller
+import org.springframework.web.bind.annotation.GetMapping
+
+/**
+ * @author Eleftheria Stein
+ */
+@Controller
+class MainController {
+
+    @GetMapping("/")
+    fun index(): String {
+        return "index"
+    }
+
+    @GetMapping("/user/index")
+    fun userIndex(): String {
+        return "user/index"
+    }
+
+    @GetMapping("/log-in")
+    fun login(): String {
+        return "login"
+    }
+}

+ 6 - 0
samples/boot/kotlin/src/main/resources/application.yml

@@ -0,0 +1,6 @@
+server:
+  port: 8080
+
+spring:
+  thymeleaf:
+    cache: false

+ 8 - 0
samples/boot/kotlin/src/main/resources/static/css/main.css

@@ -0,0 +1,8 @@
+body {
+    font-family: sans;
+    font-size: 1em;
+}
+
+div.logout {
+    float: right;
+}

+ 40 - 0
samples/boot/kotlin/src/main/resources/templates/index.html

@@ -0,0 +1,40 @@
+<!--
+  ~ 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.
+  -->
+
+<!DOCTYPE html>
+<html xmlns="http://www.w3.org/1999/xhtml" xmlns:th="https://www.thymeleaf.org" xmlns:sec="https://www.thymeleaf.org/thymeleaf-extras-springsecurity5">
+    <head>
+        <title>Hello Spring Security</title>
+        <meta charset="utf-8" />
+        <link rel="stylesheet" href="/static/css/main.css" th:href="@{/css/main.css}" />
+    </head>
+    <body>
+        <div th:fragment="logout" class="logout" sec:authorize="isAuthenticated()">
+            Logged in user: <span sec:authentication="name"></span> |
+            Roles: <span sec:authentication="principal.authorities"></span>
+            <div>
+                <form action="#" th:action="@{/logout}" method="post">
+                    <input type="submit" value="Logout" />
+                </form>
+            </div>
+        </div>
+        <h1>Hello Spring Security</h1>
+        <p>This is an unsecured page, but you can access the secured pages after authenticating.</p>
+        <ul>
+            <li>Go to the <a href="/user/index" th:href="@{/user/index}">secured pages</a></li>
+        </ul>
+    </body>
+</html>

+ 20 - 0
samples/boot/kotlin/src/main/resources/templates/login.html

@@ -0,0 +1,20 @@
+<!DOCTYPE html>
+<html xmlns="http://www.w3.org/1999/xhtml" xmlns:th="https://www.thymeleaf.org">
+    <head>
+        <title>Login page</title>
+        <meta charset="utf-8" />
+        <link rel="stylesheet" href="/static/css/main.css" th:href="@{/css/main.css}" />
+	</head>
+    <body>
+        <h1>Login page</h1>
+        <p>Example user: user / password</p>
+        <form th:action="@{/log-in}" method="post">
+            <label for="username">Username</label>:
+            <input type="text" id="username" name="username" autofocus="autofocus" /> <br />
+            <label for="password">Password</label>:
+            <input type="password" id="password" name="password" /> <br />
+            <input type="submit" value="Log in" />
+        </form>
+        <p><a href="/" th:href="@{/}">Back to home page</a></p>
+    </body>
+</html>

+ 29 - 0
samples/boot/kotlin/src/main/resources/templates/user/index.html

@@ -0,0 +1,29 @@
+<!--
+  ~ 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.
+  -->
+
+<!DOCTYPE html>
+<html xmlns="http://www.w3.org/1999/xhtml" xmlns:th="https://www.thymeleaf.org">
+    <head>
+        <title>Hello Spring Security</title>
+        <meta charset="utf-8" />
+        <link rel="stylesheet" href="/static/css/main.css" th:href="@{/css/main.css}" />
+    </head>
+    <body>
+        <div th:substituteby="index::logout"></div>
+        <h1>This is a secured page!</h1>
+        <p><a href="/" th:href="@{/}">Back to home page</a></p>
+    </body>
+</html>

+ 85 - 0
samples/boot/kotlin/src/test/kotlin/org/springframework/security/samples/KotlinApplicationTests.kt

@@ -0,0 +1,85 @@
+/*
+ * 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.samples
+
+import org.assertj.core.api.Assertions.assertThat
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.springframework.beans.factory.annotation.Autowired
+import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc
+import org.springframework.boot.test.context.SpringBootTest
+import org.springframework.mock.web.MockHttpSession
+import org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestBuilders.formLogin
+import org.springframework.security.test.web.servlet.response.SecurityMockMvcResultMatchers.authenticated
+import org.springframework.security.test.web.servlet.response.SecurityMockMvcResultMatchers.unauthenticated
+import org.springframework.test.context.junit4.SpringRunner
+import org.springframework.test.web.servlet.MockMvc
+import org.springframework.test.web.servlet.get
+import org.springframework.test.web.servlet.result.MockMvcResultMatchers.status
+
+@RunWith(SpringRunner::class)
+@SpringBootTest
+@AutoConfigureMockMvc
+class KotlinApplicationTests {
+
+    @Autowired
+    private lateinit var mockMvc: MockMvc
+
+    @Test
+    fun `index page is not protected`() {
+        this.mockMvc.get("/")
+                .andExpect {
+                    status { isOk }
+                }
+    }
+
+    @Test
+    fun `protected page redirects to login`() {
+        val mvcResult = this.mockMvc.get("/user/index")
+                .andExpect { status { is3xxRedirection } }
+                .andReturn()
+
+        assertThat(mvcResult.response.redirectedUrl).endsWith("/log-in")
+    }
+
+    @Test
+    fun `valid user permitted to log in`() {
+        this.mockMvc.perform(formLogin("/log-in").user("user").password("password"))
+                .andExpect(authenticated())
+    }
+
+    @Test
+    fun `invalid user not permitted to log in`() {
+        this.mockMvc.perform(formLogin("/log-in").user("invalid").password("invalid"))
+                .andExpect(unauthenticated())
+                .andExpect(status().is3xxRedirection)
+    }
+
+    @Test
+    fun `logged in user can access protected page`() {
+        val mvcResult = this.mockMvc.perform(formLogin("/log-in").user("user").password("password"))
+                .andExpect(authenticated()).andReturn()
+
+        val httpSession = mvcResult.request.getSession(false) as MockHttpSession
+
+        this.mockMvc.get("/user/index") {
+            session = httpSession
+        }.andExpect {
+            status { isOk }
+        }
+    }
+}

+ 8 - 2
settings.gradle

@@ -2,7 +2,7 @@ rootProject.name = 'spring-security'
 
 FileTree buildFiles = fileTree(rootDir) {
 	List excludes = gradle.startParameter.projectProperties.get("excludeProjects")?.split(",")
-	include '**/*.gradle'
+	include '**/*.gradle', '**/*.gradle.kts'
 	exclude 'build', '**/gradle', 'settings.gradle', 'buildSrc', '/build.gradle', '.*', 'out'
 	exclude '**/grails3'
 	if(excludes) {
@@ -14,12 +14,18 @@ String rootDirPath = rootDir.absolutePath + File.separator
 buildFiles.each { File buildFile ->
 
 	boolean isDefaultName = 'build.gradle'.equals(buildFile.name)
+	boolean isKotlin = buildFile.name.endsWith(".kts")
 	if(isDefaultName) {
 		String buildFilePath = buildFile.parentFile.absolutePath
 		String projectPath = buildFilePath.replace(rootDirPath, '').replace(File.separator, ':')
 		include projectPath
 	} else {
-		String projectName = buildFile.name.replace('.gradle', '');
+		String projectName
+		if (isKotlin) {
+			projectName = buildFile.name.replace('.gradle.kts', '')
+		} else {
+		 	projectName = buildFile.name.replace('.gradle', '')
+		}
 		String projectPath = ':' + projectName;
 		include projectPath
 		def project = findProject("${projectPath}")