浏览代码

Support nested builder in DSL for reactive apps

Fixes: gh-7107
Eleftheria Stein 6 年之前
父节点
当前提交
a288ce4b00
共有 27 个文件被更改,包括 1991 次插入205 次删除
  1. 678 0
      config/src/main/java/org/springframework/security/config/web/server/ServerHttpSecurity.java
  2. 44 1
      config/src/test/java/org/springframework/security/config/web/server/AuthorizeExchangeSpecTests.java
  3. 9 1
      config/src/test/java/org/springframework/security/config/web/server/CorsSpecTests.java
  4. 102 1
      config/src/test/java/org/springframework/security/config/web/server/ExceptionHandlingSpecTests.java
  5. 78 0
      config/src/test/java/org/springframework/security/config/web/server/FormLoginTests.java
  6. 202 17
      config/src/test/java/org/springframework/security/config/web/server/HeaderSpecTests.java
  7. 98 1
      config/src/test/java/org/springframework/security/config/web/server/HttpsRedirectSpecTests.java
  8. 48 1
      config/src/test/java/org/springframework/security/config/web/server/LogoutSpecTests.java
  9. 44 0
      config/src/test/java/org/springframework/security/config/web/server/OAuth2ClientSpecTests.java
  10. 81 0
      config/src/test/java/org/springframework/security/config/web/server/OAuth2LoginTests.java
  11. 179 0
      config/src/test/java/org/springframework/security/config/web/server/OAuth2ResourceServerSpecTests.java
  12. 35 0
      config/src/test/java/org/springframework/security/config/web/server/RequestCacheTests.java
  13. 123 0
      config/src/test/java/org/springframework/security/config/web/server/ServerHttpSecurityTests.java
  14. 2 2
      docs/manual/src/docs/asciidoc/_includes/reactive/cors.adoc
  15. 80 40
      docs/manual/src/docs/asciidoc/_includes/reactive/headers.adoc
  16. 5 4
      docs/manual/src/docs/asciidoc/_includes/reactive/method.adoc
  17. 1 1
      docs/manual/src/docs/asciidoc/_includes/reactive/oauth2/access-token.adoc
  18. 10 8
      docs/manual/src/docs/asciidoc/_includes/reactive/oauth2/login.adoc
  19. 74 50
      docs/manual/src/docs/asciidoc/_includes/reactive/oauth2/resource-server.adoc
  20. 7 5
      docs/manual/src/docs/asciidoc/_includes/reactive/redirect-https.adoc
  21. 7 6
      docs/manual/src/docs/asciidoc/_includes/reactive/webflux.adoc
  22. 30 30
      docs/manual/src/docs/asciidoc/_includes/reactive/x509.adoc
  23. 8 5
      samples/boot/hellowebflux-method/src/main/java/sample/SecurityConfig.java
  24. 12 7
      samples/boot/oauth2resourceserver-webflux/src/main/java/sample/SecurityConfig.java
  25. 11 10
      samples/boot/oauth2webclient-webflux/src/main/java/sample/config/SecurityConfig.java
  26. 14 9
      samples/boot/webflux-form/src/main/java/sample/WebfluxFormSecurityConfig.java
  27. 9 6
      samples/boot/webflux-x509/src/main/java/sample/WebfluxX509Application.java

+ 678 - 0
config/src/main/java/org/springframework/security/config/web/server/ServerHttpSecurity.java

@@ -32,6 +32,7 @@ import java.util.UUID;
 import java.util.function.Function;
 import java.util.function.Supplier;
 
+import org.springframework.security.config.Customizer;
 import reactor.core.publisher.Mono;
 import reactor.util.context.Context;
 
@@ -394,6 +395,48 @@ public class ServerHttpSecurity {
 		return this.httpsRedirectSpec;
 	}
 
+	/**
+	 * Configures HTTPS redirection rules. If the default is used:
+	 *
+	 * <pre class="code">
+	 *  &#064;Bean
+	 * 	public SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) {
+	 * 	    http
+	 * 	        // ...
+	 * 	        .redirectToHttps(withDefaults());
+	 * 	    return http.build();
+	 * 	}
+	 * </pre>
+	 *
+	 * Then all non-HTTPS requests will be redirected to HTTPS.
+	 *
+	 * Typically, all requests should be HTTPS; however, the focus for redirection can also be narrowed:
+	 *
+	 * <pre class="code">
+	 *  &#064;Bean
+	 * 	public SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) throws Exception {
+	 * 	    http
+	 * 	        // ...
+	 * 	        .redirectToHttps(redirectToHttps ->
+	 * 	        	redirectToHttps
+	 * 	            	.httpsRedirectWhen(serverWebExchange ->
+	 * 	            		serverWebExchange.getRequest().getHeaders().containsKey("X-Requires-Https"))
+	 * 	            );
+	 * 	    return http.build();
+	 * 	}
+	 * </pre>
+	 *
+	 * @param httpsRedirectCustomizer the {@link Customizer} to provide more options for
+	 * the {@link HttpsRedirectSpec}
+	 * @return the {@link ServerHttpSecurity} to customize
+	 * @throws Exception
+	 */
+	public ServerHttpSecurity redirectToHttps(Customizer<HttpsRedirectSpec> httpsRedirectCustomizer) throws Exception {
+		this.httpsRedirectSpec = new HttpsRedirectSpec();
+		httpsRedirectCustomizer.customize(this.httpsRedirectSpec);
+		return this;
+	}
+
 	/**
 	 * Configures <a href="https://www.owasp.org/index.php/Cross-Site_Request_Forgery_(CSRF)_Prevention_Cheat_Sheet">CSRF Protection</a>
 	 * which is enabled by default. You can disable it using:
@@ -436,6 +479,56 @@ public class ServerHttpSecurity {
 		return this.csrf;
 	}
 
+	/**
+	 * Configures <a href="https://www.owasp.org/index.php/Cross-Site_Request_Forgery_(CSRF)_Prevention_Cheat_Sheet">CSRF Protection</a>
+	 * which is enabled by default. You can disable it using:
+	 *
+	 * <pre class="code">
+	 *  &#064;Bean
+	 *  public SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) throws Exception {
+	 *      http
+	 *          // ...
+	 *          .csrf(csrf ->
+	 *              csrf.disabled()
+	 *          );
+	 *      return http.build();
+	 *  }
+	 * </pre>
+	 *
+	 * Additional configuration options can be seen below:
+	 *
+	 *
+	 * <pre class="code">
+	 *  &#064;Bean
+	 *  public SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) throws Exception {
+	 *      http
+	 *          // ...
+	 *          .csrf(csrf ->
+	 *              csrf
+	 *                  // Handle CSRF failures
+	 *                  .accessDeniedHandler(accessDeniedHandler)
+	 *                  // Custom persistence of CSRF Token
+	 *                  .csrfTokenRepository(csrfTokenRepository)
+	 *                  // custom matching when CSRF protection is enabled
+	 *                  .requireCsrfProtectionMatcher(matcher)
+	 *          );
+	 *      return http.build();
+	 *  }
+	 * </pre>
+	 *
+	 * @param csrfCustomizer the {@link Customizer} to provide more options for
+	 * the {@link CsrfSpec}
+	 * @return the {@link ServerHttpSecurity} to customize
+	 * @throws Exception
+	 */
+	public ServerHttpSecurity csrf(Customizer<CsrfSpec> csrfCustomizer) throws Exception {
+		if (this.csrf == null) {
+			this.csrf = new CsrfSpec();
+		}
+		csrfCustomizer.customize(this.csrf);
+		return this;
+	}
+
 	/**
 	 * Configures CORS headers. By default if a {@link CorsConfigurationSource} Bean is found, it will be used
 	 * to create a {@link CorsWebFilter}. If {@link CorsSpec#configurationSource(CorsConfigurationSource)} is invoked
@@ -449,6 +542,24 @@ public class ServerHttpSecurity {
 		return this.cors;
 	}
 
+	/**
+	 * Configures CORS headers. By default if a {@link CorsConfigurationSource} Bean is found, it will be used
+	 * to create a {@link CorsWebFilter}. If {@link CorsSpec#configurationSource(CorsConfigurationSource)} is invoked
+	 * it will be used instead. If neither has been configured, the Cors configuration will do nothing.
+	 *
+	 * @param corsCustomizer the {@link Customizer} to provide more options for
+	 * the {@link CorsSpec}
+	 * @return the {@link ServerHttpSecurity} to customize
+	 * @throws Exception
+	 */
+	public ServerHttpSecurity cors(Customizer<CorsSpec> corsCustomizer) throws Exception {
+		if (this.cors == null) {
+			this.cors = new CorsSpec();
+		}
+		corsCustomizer.customize(this.cors);
+		return this;
+	}
+
 	/**
 	 * Enables and Configures anonymous authentication. Anonymous Authentication is disabled by default.
 	 *
@@ -473,6 +584,36 @@ public class ServerHttpSecurity {
 		return this.anonymous;
 	}
 
+	/**
+	 * Enables and Configures anonymous authentication. Anonymous Authentication is disabled by default.
+	 *
+	 * <pre class="code">
+	 *  &#064;Bean
+	 *  public SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) throws Exception {
+	 *      http
+	 *          // ...
+	 *          .anonymous(anonymous ->
+	 *              anonymous
+	 *                  .key("key")
+	 *                  .authorities("ROLE_ANONYMOUS")
+	 *          );
+	 *      return http.build();
+	 *  }
+	 * </pre>
+	 *
+	 * @param anonymousCustomizer the {@link Customizer} to provide more options for
+	 * the {@link AnonymousSpec}
+	 * @return the {@link ServerHttpSecurity} to customize
+	 * @throws Exception
+	 */
+	public ServerHttpSecurity anonymous(Customizer<AnonymousSpec> anonymousCustomizer) throws Exception {
+		if (this.anonymous == null) {
+			this.anonymous = new AnonymousSpec();
+		}
+		anonymousCustomizer.customize(this.anonymous);
+		return this;
+	}
+
 	/**
 	 * Configures CORS support within Spring Security. This ensures that the {@link CorsWebFilter} is place in the
 	 * correct order.
@@ -560,6 +701,38 @@ public class ServerHttpSecurity {
 		return this.httpBasic;
 	}
 
+	/**
+	 * Configures HTTP Basic authentication. An example configuration is provided below:
+	 *
+	 * <pre class="code">
+	 *  &#064;Bean
+	 *  public SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) throws Exception {
+	 *      http
+	 *          // ...
+	 *          .httpBasic(httpBasic ->
+	 *              httpBasic
+	 *                  // used for authenticating the credentials
+	 *                  .authenticationManager(authenticationManager)
+	 *                  // Custom persistence of the authentication
+	 *                  .securityContextRepository(securityContextRepository)
+	 *              );
+	 *      return http.build();
+	 *  }
+	 * </pre>
+	 *
+	 * @param httpBasicCustomizer the {@link Customizer} to provide more options for
+	 * the {@link HttpBasicSpec}
+	 * @return the {@link ServerHttpSecurity} to customize
+	 * @throws Exception
+	 */
+	public ServerHttpSecurity httpBasic(Customizer<HttpBasicSpec> httpBasicCustomizer) throws Exception {
+		if (this.httpBasic == null) {
+			this.httpBasic = new HttpBasicSpec();
+		}
+		httpBasicCustomizer.customize(this.httpBasic);
+		return this;
+	}
+
 	/**
 	 * Configures form based authentication. An example configuration is provided below:
 	 *
@@ -590,6 +763,42 @@ public class ServerHttpSecurity {
 		return this.formLogin;
 	}
 
+	/**
+	 * Configures form based authentication. An example configuration is provided below:
+	 *
+	 * <pre class="code">
+	 *  &#064;Bean
+	 *  public SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) throws Exception {
+	 *      http
+	 *          // ...
+	 *          .formLogin(formLogin ->
+	 *              formLogin
+	 *              	// used for authenticating the credentials
+	 *              	.authenticationManager(authenticationManager)
+	 *              	// Custom persistence of the authentication
+	 *              	.securityContextRepository(securityContextRepository)
+	 *              	// expect a log in page at "/authenticate"
+	 *              	// a POST "/authenticate" is where authentication occurs
+	 *              	// error page at "/authenticate?error"
+	 *              	.loginPage("/authenticate")
+	 *          );
+	 *      return http.build();
+	 *  }
+	 * </pre>
+	 *
+	 * @param formLoginCustomizer the {@link Customizer} to provide more options for
+	 * the {@link FormLoginSpec}
+	 * @return the {@link ServerHttpSecurity} to customize
+	 * @throws Exception
+	 */
+	public ServerHttpSecurity formLogin(Customizer<FormLoginSpec> formLoginCustomizer) throws Exception {
+		if (this.formLogin == null) {
+			this.formLogin = new FormLoginSpec();
+		}
+		formLoginCustomizer.customize(this.formLogin);
+		return this;
+	}
+
 	/**
 	 * Configures x509 authentication using a certificate provided by a client.
 	 *
@@ -619,6 +828,39 @@ public class ServerHttpSecurity {
 		return this.x509;
 	}
 
+	/**
+	 * Configures x509 authentication using a certificate provided by a client.
+	 *
+	 * <pre class="code">
+	 *  &#064;Bean
+	 *  public SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) throws Exception {
+	 *      http
+	 *          .x509(x509 ->
+	 *              x509
+	 *          	    .authenticationManager(authenticationManager)
+	 *                  .principalExtractor(principalExtractor)
+	 *          );
+	 *      return http.build();
+	 *  }
+	 * </pre>
+	 *
+	 * Note that if extractor is not specified, {@link SubjectDnX509PrincipalExtractor} will be used.
+	 * If authenticationManager is not specified, {@link ReactivePreAuthenticatedAuthenticationManager} will be used.
+	 *
+	 * @since 5.2
+	 * @param x509Customizer the {@link Customizer} to provide more options for
+	 * the {@link X509Spec}
+	 * @return the {@link ServerHttpSecurity} to customize
+	 * @throws Exception
+	 */
+	public ServerHttpSecurity x509(Customizer<X509Spec> x509Customizer) throws Exception {
+		if (this.x509 == null) {
+			this.x509 = new X509Spec();
+		}
+		x509Customizer.customize(this.x509);
+		return this;
+	}
+
 	/**
 	 * Configures X509 authentication
 	 *
@@ -702,6 +944,36 @@ public class ServerHttpSecurity {
 		return this.oauth2Login;
 	}
 
+	/**
+	 * Configures authentication support using an OAuth 2.0 and/or OpenID Connect 1.0 Provider.
+	 *
+	 * <pre class="code">
+	 *  &#064;Bean
+	 *  public SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) throws Exception {
+	 *      http
+	 *          // ...
+	 *          .oauth2Login(oauth2Login ->
+	 *              oauth2Login
+	 *                  .authenticationConverter(authenticationConverter)
+	 *                  .authenticationManager(manager)
+	 *          );
+	 *      return http.build();
+	 *  }
+	 * </pre>
+	 *
+	 * @param oauth2LoginCustomizer the {@link Customizer} to provide more options for
+	 * the {@link OAuth2LoginSpec}
+	 * @return the {@link ServerHttpSecurity} to customize
+	 * @throws Exception
+	 */
+	public ServerHttpSecurity oauth2Login(Customizer<OAuth2LoginSpec> oauth2LoginCustomizer) throws Exception {
+		if (this.oauth2Login == null) {
+			this.oauth2Login = new OAuth2LoginSpec();
+		}
+		oauth2LoginCustomizer.customize(this.oauth2Login);
+		return this;
+	}
+
 	public class OAuth2LoginSpec {
 		private ReactiveClientRegistrationRepository clientRegistrationRepository;
 
@@ -995,6 +1267,36 @@ public class ServerHttpSecurity {
 		return this.client;
 	}
 
+	/**
+	 * Configures the OAuth2 client.
+	 *
+	 * <pre class="code">
+	 *  &#064;Bean
+	 *  public SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) throws Exception {
+	 *      http
+	 *          // ...
+	 *          .oauth2Client(oauth2Client ->
+	 *              oauth2Client
+	 *                  .clientRegistrationRepository(clientRegistrationRepository)
+	 *                  .authorizedClientRepository(authorizedClientRepository)
+	 *          );
+	 *      return http.build();
+	 *  }
+	 * </pre>
+	 *
+	 * @param oauth2ClientCustomizer the {@link Customizer} to provide more options for
+	 * the {@link OAuth2ClientSpec}
+	 * @return the {@link ServerHttpSecurity} to customize
+	 * @throws Exception
+	 */
+	public ServerHttpSecurity oauth2Client(Customizer<OAuth2ClientSpec> oauth2ClientCustomizer) throws Exception {
+		if (this.client == null) {
+			this.client = new OAuth2ClientSpec();
+		}
+		oauth2ClientCustomizer.customize(this.client);
+		return this;
+	}
+
 	public class OAuth2ClientSpec {
 		private ReactiveClientRegistrationRepository clientRegistrationRepository;
 
@@ -1145,6 +1447,39 @@ public class ServerHttpSecurity {
 		return this.resourceServer;
 	}
 
+	/**
+	 * Configures OAuth 2.0 Resource Server support.
+	 *
+	 * <pre class="code">
+	 *  &#064;Bean
+	 *  public SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) throws Exception {
+	 *      http
+	 *          // ...
+	 *          .oauth2ResourceServer(oauth2ResourceServer ->
+	 *              oauth2ResourceServer
+	 *                  .jwt(jwt ->
+	 *                      jwt
+	 *                          .publicKey(publicKey())
+	 *                  )
+	 *          );
+	 *      return http.build();
+	 *  }
+	 * </pre>
+	 *
+	 * @param oauth2ResourceServerCustomizer the {@link Customizer} to provide more options for
+	 * the {@link OAuth2ResourceServerSpec}
+	 * @return the {@link ServerHttpSecurity} to customize
+	 * @throws Exception
+	 */
+	public ServerHttpSecurity oauth2ResourceServer(Customizer<OAuth2ResourceServerSpec> oauth2ResourceServerCustomizer)
+			throws Exception {
+		if (this.resourceServer == null) {
+			this.resourceServer = new OAuth2ResourceServerSpec();
+		}
+		oauth2ResourceServerCustomizer.customize(this.resourceServer);
+		return this;
+	}
+
 	/**
 	 * Configures OAuth2 Resource Server Support
 	 */
@@ -1228,6 +1563,22 @@ public class ServerHttpSecurity {
 			return this.jwt;
 		}
 
+		/**
+		 * Enables JWT Resource Server support.
+		 *
+		 * @param jwtCustomizer the {@link Customizer} to provide more options for
+		 * the {@link JwtSpec}
+		 * @return the {@link OAuth2ResourceServerSpec} to customize
+		 * @throws Exception
+		 */
+		public OAuth2ResourceServerSpec jwt(Customizer<JwtSpec> jwtCustomizer) throws Exception {
+			if (this.jwt == null) {
+				this.jwt = new JwtSpec();
+			}
+			jwtCustomizer.customize(this.jwt);
+			return this;
+		}
+
 		/**
 		 * Enables Opaque Token Resource Server support.
 		 *
@@ -1240,6 +1591,22 @@ public class ServerHttpSecurity {
 			return this.opaqueToken;
 		}
 
+		/**
+		 * Enables Opaque Token Resource Server support.
+		 *
+		 * @param opaqueTokenCustomizer the {@link Customizer} to provide more options for
+		 * the {@link OpaqueTokenSpec}
+		 * @return the {@link OAuth2ResourceServerSpec} to customize
+		 * @throws Exception
+		 */
+		public OAuth2ResourceServerSpec opaqueToken(Customizer<OpaqueTokenSpec> opaqueTokenCustomizer) throws Exception {
+			if (this.opaqueToken == null) {
+				this.opaqueToken = new OpaqueTokenSpec();
+			}
+			opaqueTokenCustomizer.customize(this.opaqueToken);
+			return this;
+		}
+
 		protected void configure(ServerHttpSecurity http) {
 			this.bearerTokenServerWebExchangeMatcher
 					.setBearerTokenConverter(this.bearerTokenConverter);
@@ -1561,6 +1928,58 @@ public class ServerHttpSecurity {
 		return this.headers;
 	}
 
+	/**
+	 * Configures HTTP Response Headers. The default headers are:
+	 *
+	 * <pre>
+	 * Cache-Control: no-cache, no-store, max-age=0, must-revalidate
+	 * Pragma: no-cache
+	 * Expires: 0
+	 * X-Content-Type-Options: nosniff
+	 * Strict-Transport-Security: max-age=31536000 ; includeSubDomains
+	 * X-Frame-Options: DENY
+	 * X-XSS-Protection: 1; mode=block
+	 * </pre>
+	 *
+	 * such that "Strict-Transport-Security" is only added on secure requests.
+	 *
+	 * An example configuration is provided below:
+	 *
+	 * <pre class="code">
+	 *  &#064;Bean
+	 *  public SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) throws Exception {
+	 *      http
+	 *          // ...
+	 *          .headers(headers ->
+	 *              headers
+	 *                  // customize frame options to be same origin
+	 *                  .frameOptions(frameOptions ->
+	 *                      frameOptions
+	 *                          .mode(XFrameOptionsServerHttpHeadersWriter.Mode.SAMEORIGIN)
+	 *                   )
+	 *                  // disable cache control
+	 *                  .cache(cache ->
+	 *                      cache
+	 *                          .disable()
+	 *                  )
+	 *          );
+	 *      return http.build();
+	 *  }
+	 * </pre>
+	 *
+	 * @param headerCustomizer the {@link Customizer} to provide more options for
+	 * the {@link HeaderSpec}
+	 * @return the {@link ServerHttpSecurity} to customize
+	 * @throws Exception
+	 */
+	public ServerHttpSecurity headers(Customizer<HeaderSpec> headerCustomizer) throws Exception {
+		if (this.headers == null) {
+			this.headers = new HeaderSpec();
+		}
+		headerCustomizer.customize(this.headers);
+		return this;
+	}
+
 	/**
 	 * Configures exception handling (i.e. handles when authentication is requested). An example configuration can
 	 * be found below:
@@ -1586,6 +2005,38 @@ public class ServerHttpSecurity {
 		return this.exceptionHandling;
 	}
 
+	/**
+	 * Configures exception handling (i.e. handles when authentication is requested). An example configuration can
+	 * be found below:
+	 *
+	 * <pre class="code">
+	 *  &#064;Bean
+	 *  public SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) throws Exception {
+	 *      http
+	 *          // ...
+	 *          .exceptionHandling(exceptionHandling ->
+	 *              exceptionHandling
+	 *                  // customize how to request for authentication
+	 *                  .authenticationEntryPoint(entryPoint)
+	 *          );
+	 *      return http.build();
+	 *  }
+	 * </pre>
+	 *
+	 * @param exceptionHandlingCustomizer the {@link Customizer} to provide more options for
+	 * the {@link ExceptionHandlingSpec}
+	 * @return the {@link ServerHttpSecurity} to customize
+	 * @throws Exception
+	 */
+	public ServerHttpSecurity exceptionHandling(Customizer<ExceptionHandlingSpec> exceptionHandlingCustomizer)
+			throws Exception {
+		if (this.exceptionHandling == null) {
+			this.exceptionHandling = new ExceptionHandlingSpec();
+		}
+		exceptionHandlingCustomizer.customize(this.exceptionHandling);
+		return this;
+	}
+
 	/**
 	 * Configures authorization. An example configuration can be found below:
 	 *
@@ -1624,6 +2075,51 @@ public class ServerHttpSecurity {
 		return this.authorizeExchange;
 	}
 
+	/**
+	 * Configures authorization. An example configuration can be found below:
+	 *
+	 * <pre class="code">
+	 *  &#064;Bean
+	 *  public SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) throws Exception {
+	 *      http
+	 *          // ...
+	 *          .authorizeExchange(exchanges ->
+	 *              exchanges
+	 *                  // any URL that starts with /admin/ requires the role "ROLE_ADMIN"
+	 *                  .pathMatchers("/admin/**").hasRole("ADMIN")
+	 *                  // a POST to /users requires the role "USER_POST"
+	 *                  .pathMatchers(HttpMethod.POST, "/users").hasAuthority("USER_POST")
+	 *                  // a request to /users/{username} requires the current authentication's username
+	 *                  // to be equal to the {username}
+	 *                  .pathMatchers("/users/{username}").access((authentication, context) ->
+	 *                      authentication
+	 *                          .map(Authentication::getName)
+	 *                          .map(username -> username.equals(context.getVariables().get("username")))
+	 *                          .map(AuthorizationDecision::new)
+	 *                  )
+	 *                  // allows providing a custom matching strategy that requires the role "ROLE_CUSTOM"
+	 *                  .matchers(customMatcher).hasRole("CUSTOM")
+	 *                  // any other request requires the user to be authenticated
+	 *                  .anyExchange().authenticated()
+	 *          );
+	 *      return http.build();
+	 *  }
+	 * </pre>
+	 *
+	 * @param authorizeExchangeCustomizer the {@link Customizer} to provide more options for
+	 * the {@link AuthorizeExchangeSpec}
+	 * @return the {@link ServerHttpSecurity} to customize
+	 * @throws Exception
+	 */
+	public ServerHttpSecurity authorizeExchange(Customizer<AuthorizeExchangeSpec> authorizeExchangeCustomizer)
+			throws Exception {
+		if (this.authorizeExchange == null) {
+			this.authorizeExchange = new AuthorizeExchangeSpec();
+		}
+		authorizeExchangeCustomizer.customize(this.authorizeExchange);
+		return this;
+	}
+
 	/**
 	 * Configures log out. An example configuration can be found below:
 	 *
@@ -1651,6 +2147,40 @@ public class ServerHttpSecurity {
 		return this.logout;
 	}
 
+	/**
+	 * Configures log out. An example configuration can be found below:
+	 *
+	 * <pre class="code">
+	 *  &#064;Bean
+	 *  public SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) throws Exception {
+	 *      http
+	 *          // ...
+	 *          .logout(logout ->
+	 *              logout
+	 *                  // configures how log out is done
+	 *                  .logoutHandler(logoutHandler)
+	 *                  // log out will be performed on POST /signout
+	 *                  .logoutUrl("/signout")
+	 *                  // configure what is done on logout success
+	 *                  .logoutSuccessHandler(successHandler)
+	 *          );
+	 *      return http.build();
+	 *  }
+	 * </pre>
+	 *
+	 * @param logoutCustomizer the {@link Customizer} to provide more options for
+	 * the {@link LogoutSpec}
+	 * @return the {@link ServerHttpSecurity} to customize
+	 * @throws Exception
+	 */
+	public ServerHttpSecurity logout(Customizer<LogoutSpec> logoutCustomizer) throws Exception {
+		if (this.logout == null) {
+			this.logout = new LogoutSpec();
+		}
+		logoutCustomizer.customize(this.logout);
+		return this;
+	}
+
 	/**
 	 * Configures the request cache which is used when a flow is interrupted (i.e. due to requesting credentials) so
 	 * that the request can be replayed after authentication. An example configuration can be found below:
@@ -1673,6 +2203,34 @@ public class ServerHttpSecurity {
 		return this.requestCache;
 	}
 
+	/**
+	 * Configures the request cache which is used when a flow is interrupted (i.e. due to requesting credentials) so
+	 * that the request can be replayed after authentication. An example configuration can be found below:
+	 *
+	 * <pre class="code">
+	 *  &#064;Bean
+	 *  public SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) throws Exception {
+	 *      http
+	 *          // ...
+	 *          .requestCache(requestCache ->
+	 *              requestCache
+	 *                  // configures how the request is cached
+	 *                  .requestCache(customRequestCache)
+	 *          );
+	 *      return http.build();
+	 *  }
+	 * </pre>
+	 *
+	 * @param requestCacheCustomizer the {@link Customizer} to provide more options for
+	 * the {@link RequestCacheSpec}
+	 * @return the {@link ServerHttpSecurity} to customize
+	 * @throws Exception
+	 */
+	public ServerHttpSecurity requestCache(Customizer<RequestCacheSpec> requestCacheCustomizer) throws Exception {
+		requestCacheCustomizer.customize(this.requestCache);
+		return this;
+	}
+
 	/**
 	 * Configure the default authentication manager.
 	 * @param manager the authentication manager to use
@@ -2549,6 +3107,19 @@ public class ServerHttpSecurity {
 			return new CacheSpec();
 		}
 
+		/**
+		 * Configures cache control headers
+		 *
+		 * @param cacheCustomizer the {@link Customizer} to provide more options for
+		 * the {@link CacheSpec}
+		 * @return the {@link HeaderSpec} to customize
+		 * @throws Exception
+		 */
+		public HeaderSpec cache(Customizer<CacheSpec> cacheCustomizer) throws Exception {
+			cacheCustomizer.customize(new CacheSpec());
+			return this;
+		}
+
 		/**
 		 * Configures content type response headers
 		 * @return the {@link ContentTypeOptionsSpec} to configure
@@ -2557,6 +3128,20 @@ public class ServerHttpSecurity {
 			return new ContentTypeOptionsSpec();
 		}
 
+		/**
+		 * Configures content type response headers
+		 *
+		 * @param contentTypeOptionsCustomizer the {@link Customizer} to provide more options for
+		 * the {@link ContentTypeOptionsSpec}
+		 * @return the {@link HeaderSpec} to customize
+		 * @throws Exception
+		 */
+		public HeaderSpec contentTypeOptions(Customizer<ContentTypeOptionsSpec> contentTypeOptionsCustomizer)
+				throws Exception {
+			contentTypeOptionsCustomizer.customize(new ContentTypeOptionsSpec());
+			return this;
+		}
+
 		/**
 		 * Configures frame options response headers
 		 * @return the {@link FrameOptionsSpec} to configure
@@ -2565,6 +3150,19 @@ public class ServerHttpSecurity {
 			return new FrameOptionsSpec();
 		}
 
+		/**
+		 * Configures frame options response headers
+		 *
+		 * @param frameOptionsCustomizer the {@link Customizer} to provide more options for
+		 * the {@link FrameOptionsSpec}
+		 * @return the {@link HeaderSpec} to customize
+		 * @throws Exception
+		 */
+		public HeaderSpec frameOptions(Customizer<FrameOptionsSpec> frameOptionsCustomizer) throws Exception {
+			frameOptionsCustomizer.customize(new FrameOptionsSpec());
+			return this;
+		}
+
 		/**
 		 * Configures the Strict Transport Security response headers
 		 * @return the {@link HstsSpec} to configure
@@ -2573,6 +3171,19 @@ public class ServerHttpSecurity {
 			return new HstsSpec();
 		}
 
+		/**
+		 * Configures the Strict Transport Security response headers
+		 *
+		 * @param hstsCustomizer the {@link Customizer} to provide more options for
+		 * the {@link HstsSpec}
+		 * @return the {@link HeaderSpec} to customize
+		 * @throws Exception
+		 */
+		public HeaderSpec hsts(Customizer<HstsSpec> hstsCustomizer) throws Exception {
+			hstsCustomizer.customize(new HstsSpec());
+			return this;
+		}
+
 		protected void configure(ServerHttpSecurity http) {
 			ServerHttpHeadersWriter writer = new CompositeServerHttpHeadersWriter(this.writers);
 			HttpHeaderWriterWebFilter result = new HttpHeaderWriterWebFilter(writer);
@@ -2587,6 +3198,19 @@ public class ServerHttpSecurity {
 			return new XssProtectionSpec();
 		}
 
+		/**
+		 * Configures x-xss-protection response header.
+		 *
+		 * @param xssProtectionCustomizer the {@link Customizer} to provide more options for
+		 * the {@link XssProtectionSpec}
+		 * @return the {@link HeaderSpec} to customize
+		 * @throws Exception
+		 */
+		public HeaderSpec xssProtection(Customizer<XssProtectionSpec> xssProtectionCustomizer) throws Exception {
+			xssProtectionCustomizer.customize(new XssProtectionSpec());
+			return this;
+		}
+
 		/**
 		 * Configures {@code Content-Security-Policy} response header.
 		 * @param policyDirectives the policy directive(s)
@@ -2596,6 +3220,20 @@ public class ServerHttpSecurity {
 			return new ContentSecurityPolicySpec(policyDirectives);
 		}
 
+		/**
+		 * Configures {@code Content-Security-Policy} response header.
+		 *
+		 * @param contentSecurityPolicyCustomizer the {@link Customizer} to provide more options for
+		 * the {@link ContentSecurityPolicySpec}
+		 * @return the {@link HeaderSpec} to customize
+		 * @throws Exception
+		 */
+		public HeaderSpec contentSecurityPolicy(Customizer<ContentSecurityPolicySpec> contentSecurityPolicyCustomizer)
+				throws Exception {
+			contentSecurityPolicyCustomizer.customize(new ContentSecurityPolicySpec());
+			return this;
+		}
+
 		/**
 		 * Configures {@code Feature-Policy} response header.
 		 * @param policyDirectives the policy directive(s)
@@ -2622,6 +3260,20 @@ public class ServerHttpSecurity {
 			return new ReferrerPolicySpec();
 		}
 
+		/**
+		 * Configures {@code Referrer-Policy} response header.
+		 *
+		 * @param referrerPolicyCustomizer the {@link Customizer} to provide more options for
+		 * the {@link ReferrerPolicySpec}
+		 * @return the {@link HeaderSpec} to customize
+		 * @throws Exception
+		 */
+		public HeaderSpec referrerPolicy(Customizer<ReferrerPolicySpec> referrerPolicyCustomizer)
+				throws Exception {
+			referrerPolicyCustomizer.customize(new ReferrerPolicySpec());
+			return this;
+		}
+
 		/**
 		 * Configures cache control headers
 		 * @see #cache()
@@ -2781,6 +3433,7 @@ public class ServerHttpSecurity {
 		 * @since 5.1
 		 */
 		public class ContentSecurityPolicySpec {
+			private static final String DEFAULT_SRC_SELF_POLICY = "default-src 'self'";
 
 			/**
 			 * Whether to include the {@code Content-Security-Policy-Report-Only} header in
@@ -2793,6 +3446,17 @@ public class ServerHttpSecurity {
 				return HeaderSpec.this;
 			}
 
+			/**
+			 * Sets the security policy directive(s) to be used in the response header.
+			 *
+			 * @param policyDirectives the security policy directive(s)
+			 * @return the {@link HeaderSpec} to continue configuring
+			 */
+			public HeaderSpec policyDirectives(String policyDirectives) {
+				HeaderSpec.this.contentSecurityPolicy.setPolicyDirectives(policyDirectives);
+				return HeaderSpec.this;
+			}
+
 			/**
 			 * Allows method chaining to continue configuring the
 			 * {@link ServerHttpSecurity}.
@@ -2806,6 +3470,9 @@ public class ServerHttpSecurity {
 				HeaderSpec.this.contentSecurityPolicy.setPolicyDirectives(policyDirectives);
 			}
 
+			private ContentSecurityPolicySpec() {
+				HeaderSpec.this.contentSecurityPolicy.setPolicyDirectives(DEFAULT_SRC_SELF_POLICY);
+			}
 		}
 
 		/**
@@ -2840,6 +3507,17 @@ public class ServerHttpSecurity {
 		 */
 		public class ReferrerPolicySpec {
 
+			/**
+			 * Sets the policy to be used in the response header.
+			 *
+			 * @param referrerPolicy a referrer policy
+			 * @return the {@link ReferrerPolicySpec} to continue configuring
+			 */
+			public ReferrerPolicySpec policy(ReferrerPolicy referrerPolicy) {
+				HeaderSpec.this.referrerPolicy.setPolicy(referrerPolicy);
+				return this;
+			}
+
 			/**
 			 * Allows method chaining to continue configuring the
 			 * {@link ServerHttpSecurity}.

+ 44 - 1
config/src/test/java/org/springframework/security/config/web/server/AuthorizeExchangeSpecTests.java

@@ -1,5 +1,5 @@
 /*
- * Copyright 2002-2017 the original author or authors.
+ * 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.
@@ -92,6 +92,39 @@ public class AuthorizeExchangeSpecTests {
 			.expectStatus().isUnauthorized();
 	}
 
+	@Test
+	public void antMatchersWhenPatternsInLambdaThenAnyMethod() throws Exception {
+		this.http
+			.csrf(ServerHttpSecurity.CsrfSpec::disable)
+			.authorizeExchange(exchanges ->
+				exchanges
+					.pathMatchers("/a", "/b").denyAll()
+					.anyExchange().permitAll()
+			);
+
+		WebTestClient client = buildClient();
+
+		client.get()
+			.uri("/a")
+			.exchange()
+			.expectStatus().isUnauthorized();
+
+		client.get()
+			.uri("/b")
+			.exchange()
+			.expectStatus().isUnauthorized();
+
+		client.post()
+			.uri("/a")
+			.exchange()
+			.expectStatus().isUnauthorized();
+
+		client.post()
+			.uri("/b")
+			.exchange()
+			.expectStatus().isUnauthorized();
+	}
+
 	@Test(expected = IllegalStateException.class)
 	public void antMatchersWhenNoAccessAndAnotherMatcherThenThrowsException() {
 		this.http
@@ -117,6 +150,16 @@ public class AuthorizeExchangeSpecTests {
 		this.http.build();
 	}
 
+	@Test(expected = IllegalStateException.class)
+	public void buildWhenMatcherDefinedWithNoAccessInLambdaThenThrowsException() throws Exception {
+		this.http
+			.authorizeExchange(exchanges ->
+				exchanges
+					.pathMatchers("/incomplete")
+			);
+		this.http.build();
+	}
+
 	private WebTestClient buildClient() {
 		return WebTestClientBuilder.bindToWebFilters(this.http.build()).build();
 	}

+ 9 - 1
config/src/test/java/org/springframework/security/config/web/server/CorsSpecTests.java

@@ -1,5 +1,5 @@
 /*
- * Copyright 2002-2017 the original author or authors.
+ * 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.
@@ -74,6 +74,14 @@ public class CorsSpecTests {
 		assertHeaders();
 	}
 
+	@Test
+	public void corsWhenEnabledInLambdaThenAccessControlAllowOriginAndSecurityHeaders() throws Exception {
+		this.http.cors(cors -> cors.configurationSource(this.source));
+		this.expectedHeaders.set("Access-Control-Allow-Origin", "*");
+		this.expectedHeaders.set("X-Frame-Options", "DENY");
+		assertHeaders();
+	}
+
 	@Test
 	public void corsWhenCorsConfigurationSourceBeanThenAccessControlAllowOriginAndSecurityHeaders() {
 		when(this.context.getBeanNamesForType(any(ResolvableType.class))).thenReturn(new String[] {"source"}, new String[0]);

+ 102 - 1
config/src/test/java/org/springframework/security/config/web/server/ExceptionHandlingSpecTests.java

@@ -1,5 +1,5 @@
 /*
- * Copyright 2002-2018 the original author or authors.
+ * 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.
@@ -26,6 +26,8 @@ import org.springframework.security.web.server.authorization.HttpStatusServerAcc
 import org.springframework.security.web.server.authorization.ServerAccessDeniedHandler;
 import org.springframework.test.web.reactive.server.WebTestClient;
 
+import static org.springframework.security.config.Customizer.withDefaults;
+
 /**
  * @author Denys Ivano
  * @since 5.0.5
@@ -56,6 +58,29 @@ public class ExceptionHandlingSpecTests {
 			.expectHeader().valueMatches("WWW-Authenticate", "Basic.*");
 	}
 
+	@Test
+	public void requestWhenExceptionHandlingWithDefaultsInLambdaThenDefaultAuthenticationEntryPointUsed()
+			throws Exception {
+		SecurityWebFilterChain securityWebFilter = this.http
+			.authorizeExchange(exchanges ->
+				exchanges
+					.anyExchange().authenticated()
+			)
+			.exceptionHandling(withDefaults())
+			.build();
+
+		WebTestClient client = WebTestClientBuilder
+			.bindToWebFilters(securityWebFilter)
+			.build();
+
+		client
+			.get()
+			.uri("/test")
+			.exchange()
+			.expectStatus().isUnauthorized()
+			.expectHeader().valueMatches("WWW-Authenticate", "Basic.*");
+	}
+
 	@Test
 	public void customAuthenticationEntryPoint() {
 		SecurityWebFilterChain securityWebFilter = this.http
@@ -80,6 +105,31 @@ public class ExceptionHandlingSpecTests {
 			.expectHeader().valueMatches("Location", ".*");
 	}
 
+	@Test
+	public void requestWhenCustomAuthenticationEntryPointInLambdaThenCustomAuthenticationEntryPointUsed() throws Exception {
+		SecurityWebFilterChain securityWebFilter = this.http
+				.authorizeExchange(exchanges ->
+					exchanges
+						.anyExchange().authenticated()
+				)
+				.exceptionHandling(exceptionHandling ->
+					exceptionHandling
+						.authenticationEntryPoint(redirectServerAuthenticationEntryPoint("/auth"))
+				)
+				.build();
+
+		WebTestClient client = WebTestClientBuilder
+			.bindToWebFilters(securityWebFilter)
+			.build();
+
+		client
+			.get()
+			.uri("/test")
+			.exchange()
+			.expectStatus().isFound()
+			.expectHeader().valueMatches("Location", ".*");
+	}
+
 	@Test
 	public void defaultAccessDeniedHandler() {
 		SecurityWebFilterChain securityWebFilter = this.http
@@ -104,6 +154,30 @@ public class ExceptionHandlingSpecTests {
 			.expectStatus().isForbidden();
 	}
 
+	@Test
+	public void requestWhenExceptionHandlingWithDefaultsInLambdaThenDefaultAccessDeniedHandlerUsed()
+			throws Exception {
+		SecurityWebFilterChain securityWebFilter = this.http
+				.httpBasic(withDefaults())
+				.authorizeExchange(exchanges ->
+					exchanges
+						.anyExchange().hasRole("ADMIN")
+				)
+				.exceptionHandling(withDefaults())
+				.build();
+
+		WebTestClient client = WebTestClientBuilder
+				.bindToWebFilters(securityWebFilter)
+				.build();
+
+		client
+				.get()
+				.uri("/admin")
+				.headers(headers -> headers.setBasicAuth("user", "password"))
+				.exchange()
+				.expectStatus().isForbidden();
+	}
+
 	@Test
 	public void customAccessDeniedHandler() {
 		SecurityWebFilterChain securityWebFilter = this.http
@@ -129,6 +203,33 @@ public class ExceptionHandlingSpecTests {
 			.expectStatus().isBadRequest();
 	}
 
+	@Test
+	public void requestWhenCustomAccessDeniedHandlerInLambdaThenCustomAccessDeniedHandlerUsed()
+			throws Exception {
+		SecurityWebFilterChain securityWebFilter = this.http
+				.httpBasic(withDefaults())
+				.authorizeExchange(exchanges ->
+						exchanges
+								.anyExchange().hasRole("ADMIN")
+				)
+				.exceptionHandling(exceptionHandling ->
+					exceptionHandling
+						.accessDeniedHandler(httpStatusServerAccessDeniedHandler(HttpStatus.BAD_REQUEST))
+				)
+				.build();
+
+		WebTestClient client = WebTestClientBuilder
+				.bindToWebFilters(securityWebFilter)
+				.build();
+
+		client
+				.get()
+				.uri("/admin")
+				.headers(headers -> headers.setBasicAuth("user", "password"))
+				.exchange()
+				.expectStatus().isBadRequest();
+	}
+
 	private ServerAuthenticationEntryPoint redirectServerAuthenticationEntryPoint(String location) {
 		return new RedirectServerAuthenticationEntryPoint(location);
 	}

+ 78 - 0
config/src/test/java/org/springframework/security/config/web/server/FormLoginTests.java

@@ -46,6 +46,7 @@ import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.BDDMockito.given;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.verifyZeroInteractions;
+import static org.springframework.security.config.Customizer.withDefaults;
 
 /**
  * @author Rob Winch
@@ -96,6 +97,49 @@ public class FormLoginTests {
 			.assertLogout();
 	}
 
+	@Test
+	public void formLoginWhenDefaultsInLambdaThenCreatesDefaultLoginPage() throws Exception {
+		SecurityWebFilterChain securityWebFilter = this.http
+			.authorizeExchange(exchanges ->
+				exchanges
+					.anyExchange().authenticated()
+			)
+			.formLogin(withDefaults())
+			.build();
+
+		WebTestClient webTestClient = WebTestClientBuilder
+			.bindToWebFilters(securityWebFilter)
+			.build();
+
+		WebDriver driver = WebTestClientHtmlUnitDriverBuilder
+			.webTestClientSetup(webTestClient)
+			.build();
+
+		DefaultLoginPage loginPage = HomePage.to(driver, DefaultLoginPage.class)
+			.assertAt();
+
+		loginPage = loginPage.loginForm()
+			.username("user")
+			.password("invalid")
+			.submit(DefaultLoginPage.class)
+			.assertError();
+
+		HomePage homePage = loginPage.loginForm()
+			.username("user")
+			.password("password")
+			.submit(HomePage.class);
+
+		homePage.assertAt();
+
+		loginPage = DefaultLogoutPage.to(driver)
+			.assertAt()
+			.logout();
+
+		loginPage
+			.assertAt()
+			.assertLogout();
+	}
+
 	@Test
 	public void customLoginPage() {
 		SecurityWebFilterChain securityWebFilter = this.http
@@ -128,6 +172,40 @@ public class FormLoginTests {
 		homePage.assertAt();
 	}
 
+	@Test
+	public void formLoginWhenCustomLoginPageInLambdaThenUsed() throws Exception {
+		SecurityWebFilterChain securityWebFilter = this.http
+			.authorizeExchange(exchanges ->
+				exchanges
+					.pathMatchers("/login").permitAll()
+					.anyExchange().authenticated()
+			)
+			.formLogin(formLogin ->
+				formLogin
+					.loginPage("/login")
+			)
+			.build();
+
+		WebTestClient webTestClient = WebTestClient
+			.bindToController(new CustomLoginPageController(), new WebTestClientBuilder.Http200RestController())
+			.webFilter(new WebFilterChainProxy(securityWebFilter))
+			.build();
+
+		WebDriver driver = WebTestClientHtmlUnitDriverBuilder
+			.webTestClientSetup(webTestClient)
+			.build();
+
+		CustomLoginPage loginPage = HomePage.to(driver, CustomLoginPage.class)
+			.assertAt();
+
+		HomePage homePage = loginPage.loginForm()
+			.username("user")
+			.password("password")
+			.submit(HomePage.class);
+
+		homePage.assertAt();
+	}
+
 	@Test
 	public void authenticationSuccess() {
 		SecurityWebFilterChain securityWebFilter = this.http

+ 202 - 17
config/src/test/java/org/springframework/security/config/web/server/HeaderSpecTests.java

@@ -1,5 +1,5 @@
 /*
- * Copyright 2002-2018 the original author or authors.
+ * 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.
@@ -39,6 +39,7 @@ import org.springframework.test.web.reactive.server.FluxExchangeResult;
 import org.springframework.test.web.reactive.server.WebTestClient;
 
 import static org.assertj.core.api.AssertionsForInterfaceTypes.assertThat;
+import static org.springframework.security.config.Customizer.withDefaults;
 
 /**
  * Tests for {@link ServerHttpSecurity.HeaderSpec}.
@@ -49,7 +50,7 @@ import static org.assertj.core.api.AssertionsForInterfaceTypes.assertThat;
  */
 public class HeaderSpecTests {
 
-	private ServerHttpSecurity.HeaderSpec headers = ServerHttpSecurity.http().headers();
+	private ServerHttpSecurity http = ServerHttpSecurity.http();
 
 	private HttpHeaders expectedHeaders = new HttpHeaders();
 
@@ -72,14 +73,23 @@ public class HeaderSpecTests {
 	public void headersWhenDisableThenNoSecurityHeaders() {
 		new HashSet<>(this.expectedHeaders.keySet()).forEach(this::expectHeaderNamesNotPresent);
 
-		this.headers.disable();
+		this.http.headers().disable();
+
+		assertHeaders();
+	}
+
+	@Test
+	public void headersWhenDisableInLambdaThenNoSecurityHeaders() throws Exception {
+		new HashSet<>(this.expectedHeaders.keySet()).forEach(this::expectHeaderNamesNotPresent);
+
+		this.http.headers(headers -> headers.disable());
 
 		assertHeaders();
 	}
 
 	@Test
 	public void headersWhenDisableAndInvokedExplicitlyThenDefautsUsed() {
-		this.headers.disable()
+		this.http.headers().disable()
 			.headers();
 
 		assertHeaders();
@@ -87,13 +97,34 @@ public class HeaderSpecTests {
 
 	@Test
 	public void headersWhenDefaultsThenAllDefaultsWritten() {
+		this.http.headers();
+
+		assertHeaders();
+	}
+
+	@Test
+	public void headersWhenDefaultsInLambdaThenAllDefaultsWritten() throws Exception {
+		this.http.headers(withDefaults());
+
 		assertHeaders();
 	}
 
 	@Test
 	public void headersWhenCacheDisableThenCacheNotWritten() {
 		expectHeaderNamesNotPresent(HttpHeaders.CACHE_CONTROL, HttpHeaders.PRAGMA, HttpHeaders.EXPIRES);
-		this.headers.cache().disable();
+		this.http.headers().cache().disable();
+
+		assertHeaders();
+	}
+
+
+	@Test
+	public void headersWhenCacheDisableInLambdaThenCacheNotWritten() throws Exception {
+		expectHeaderNamesNotPresent(HttpHeaders.CACHE_CONTROL, HttpHeaders.PRAGMA, HttpHeaders.EXPIRES);
+		this.http
+				.headers(headers ->
+					headers.cache(cache -> cache.disable())
+				);
 
 		assertHeaders();
 	}
@@ -101,7 +132,18 @@ public class HeaderSpecTests {
 	@Test
 	public void headersWhenContentOptionsDisableThenContentTypeOptionsNotWritten() {
 		expectHeaderNamesNotPresent(ContentTypeOptionsServerHttpHeadersWriter.X_CONTENT_OPTIONS);
-		this.headers.contentTypeOptions().disable();
+		this.http.headers().contentTypeOptions().disable();
+
+		assertHeaders();
+	}
+
+	@Test
+	public void headersWhenContentOptionsDisableInLambdaThenContentTypeOptionsNotWritten() throws Exception {
+		expectHeaderNamesNotPresent(ContentTypeOptionsServerHttpHeadersWriter.X_CONTENT_OPTIONS);
+		this.http
+				.headers(headers ->
+					headers.contentTypeOptions(contentTypeOptions -> contentTypeOptions.disable())
+				);
 
 		assertHeaders();
 	}
@@ -109,7 +151,18 @@ public class HeaderSpecTests {
 	@Test
 	public void headersWhenHstsDisableThenHstsNotWritten() {
 		expectHeaderNamesNotPresent(StrictTransportSecurityServerHttpHeadersWriter.STRICT_TRANSPORT_SECURITY);
-		this.headers.hsts().disable();
+		this.http.headers().hsts().disable();
+
+		assertHeaders();
+	}
+
+	@Test
+	public void headersWhenHstsDisableInLambdaThenHstsNotWritten() throws Exception {
+		expectHeaderNamesNotPresent(StrictTransportSecurityServerHttpHeadersWriter.STRICT_TRANSPORT_SECURITY);
+		this.http
+				.headers(headers ->
+					headers.hsts(hsts -> hsts.disable())
+				);
 
 		assertHeaders();
 	}
@@ -118,28 +171,73 @@ public class HeaderSpecTests {
 	public void headersWhenHstsCustomThenCustomHstsWritten() {
 		this.expectedHeaders.remove(StrictTransportSecurityServerHttpHeadersWriter.STRICT_TRANSPORT_SECURITY);
 		this.expectedHeaders.add(StrictTransportSecurityServerHttpHeadersWriter.STRICT_TRANSPORT_SECURITY, "max-age=60");
-		this.headers.hsts()
+		this.http.headers().hsts()
 					.maxAge(Duration.ofSeconds(60))
 					.includeSubdomains(false);
 
 		assertHeaders();
 	}
 
+	@Test
+	public void headersWhenHstsCustomInLambdaThenCustomHstsWritten() throws Exception {
+		this.expectedHeaders.remove(StrictTransportSecurityServerHttpHeadersWriter.STRICT_TRANSPORT_SECURITY);
+		this.expectedHeaders.add(StrictTransportSecurityServerHttpHeadersWriter.STRICT_TRANSPORT_SECURITY, "max-age=60");
+		this.http
+				.headers(headers ->
+					headers
+						.hsts(hsts ->
+							hsts
+								.maxAge(Duration.ofSeconds(60))
+								.includeSubdomains(false)
+						)
+				);
+
+		assertHeaders();
+	}
+
 	@Test
 	public void headersWhenHstsCustomWithPreloadThenCustomHstsWritten() {
 		this.expectedHeaders.remove(StrictTransportSecurityServerHttpHeadersWriter.STRICT_TRANSPORT_SECURITY);
 		this.expectedHeaders.add(StrictTransportSecurityServerHttpHeadersWriter.STRICT_TRANSPORT_SECURITY, "max-age=60 ; includeSubDomains ; preload");
-		this.headers.hsts()
+		this.http.headers().hsts()
 				.maxAge(Duration.ofSeconds(60))
 				.preload(true);
 
 		assertHeaders();
 	}
 
+	@Test
+	public void headersWhenHstsCustomWithPreloadInLambdaThenCustomHstsWritten() throws Exception {
+		this.expectedHeaders.remove(StrictTransportSecurityServerHttpHeadersWriter.STRICT_TRANSPORT_SECURITY);
+		this.expectedHeaders.add(StrictTransportSecurityServerHttpHeadersWriter.STRICT_TRANSPORT_SECURITY, "max-age=60 ; includeSubDomains ; preload");
+		this.http
+				.headers(headers ->
+					headers
+						.hsts(hsts ->
+							hsts
+								.maxAge(Duration.ofSeconds(60))
+								.preload(true)
+						)
+				);
+
+		assertHeaders();
+	}
+
 	@Test
 	public void headersWhenFrameOptionsDisableThenFrameOptionsNotWritten() {
 		expectHeaderNamesNotPresent(XFrameOptionsServerHttpHeadersWriter.X_FRAME_OPTIONS);
-		this.headers.frameOptions().disable();
+		this.http.headers().frameOptions().disable();
+
+		assertHeaders();
+	}
+
+	@Test
+	public void headersWhenFrameOptionsDisableInLambdaThenFrameOptionsNotWritten() throws Exception {
+		expectHeaderNamesNotPresent(XFrameOptionsServerHttpHeadersWriter.X_FRAME_OPTIONS);
+		this.http
+				.headers(headers ->
+					headers.frameOptions(frameOptions -> frameOptions.disable())
+				);
 
 		assertHeaders();
 	}
@@ -147,17 +245,43 @@ public class HeaderSpecTests {
 	@Test
 	public void headersWhenFrameOptionsModeThenFrameOptionsCustomMode() {
 		this.expectedHeaders.set(XFrameOptionsServerHttpHeadersWriter.X_FRAME_OPTIONS, "SAMEORIGIN");
-		this.headers
+		this.http.headers()
 				.frameOptions()
 					.mode(XFrameOptionsServerHttpHeadersWriter.Mode.SAMEORIGIN);
 
 		assertHeaders();
 	}
 
+	@Test
+	public void headersWhenFrameOptionsModeInLambdaThenFrameOptionsCustomMode() throws Exception {
+		this.expectedHeaders.set(XFrameOptionsServerHttpHeadersWriter.X_FRAME_OPTIONS, "SAMEORIGIN");
+		this.http
+				.headers(headers ->
+						headers
+							.frameOptions(frameOptions ->
+								frameOptions
+									.mode(XFrameOptionsServerHttpHeadersWriter.Mode.SAMEORIGIN)
+							)
+				);
+
+		assertHeaders();
+	}
+
 	@Test
 	public void headersWhenXssProtectionDisableThenXssProtectionNotWritten() {
 		expectHeaderNamesNotPresent("X-Xss-Protection");
-		this.headers.xssProtection().disable();
+		this.http.headers().xssProtection().disable();
+
+		assertHeaders();
+	}
+
+	@Test
+	public void headersWhenXssProtectionDisableInLambdaThenXssProtectionNotWritten() throws Exception {
+		expectHeaderNamesNotPresent("X-Xss-Protection");
+		this.http
+				.headers(headers ->
+						headers.xssProtection(xssProtection -> xssProtection.disable())
+				);
 
 		assertHeaders();
 	}
@@ -168,7 +292,7 @@ public class HeaderSpecTests {
 		this.expectedHeaders.add(FeaturePolicyServerHttpHeadersWriter.FEATURE_POLICY,
 				policyDirectives);
 
-		this.headers.featurePolicy(policyDirectives);
+		this.http.headers().featurePolicy(policyDirectives);
 
 		assertHeaders();
 	}
@@ -179,7 +303,39 @@ public class HeaderSpecTests {
 		this.expectedHeaders.add(ContentSecurityPolicyServerHttpHeadersWriter.CONTENT_SECURITY_POLICY,
 				policyDirectives);
 
-		this.headers.contentSecurityPolicy(policyDirectives);
+		this.http.headers().contentSecurityPolicy(policyDirectives);
+
+		assertHeaders();
+	}
+
+	@Test
+	public void headersWhenContentSecurityPolicyEnabledWithDefaultsInLambdaThenDefaultPolicyWritten() throws Exception {
+		String expectedPolicyDirectives = "default-src 'self'";
+		this.expectedHeaders.add(ContentSecurityPolicyServerHttpHeadersWriter.CONTENT_SECURITY_POLICY,
+				expectedPolicyDirectives);
+
+		this.http
+				.headers(headers ->
+					headers.contentSecurityPolicy(withDefaults())
+				);
+
+		assertHeaders();
+	}
+
+	@Test
+	public void headersWhenContentSecurityPolicyEnabledInLambdaThenContentSecurityPolicyWritten() throws Exception {
+		String policyDirectives = "default-src 'self' *.trusted.com";
+		this.expectedHeaders.add(ContentSecurityPolicyServerHttpHeadersWriter.CONTENT_SECURITY_POLICY,
+				policyDirectives);
+
+		this.http
+				.headers(headers ->
+					headers
+						.contentSecurityPolicy(contentSecurityPolicy ->
+							contentSecurityPolicy
+								.policyDirectives(policyDirectives)
+						)
+				);
 
 		assertHeaders();
 	}
@@ -188,7 +344,20 @@ public class HeaderSpecTests {
 	public void headersWhenReferrerPolicyEnabledThenFeaturePolicyWritten() {
 		this.expectedHeaders.add(ReferrerPolicyServerHttpHeadersWriter.REFERRER_POLICY,
 				ReferrerPolicy.NO_REFERRER.getPolicy());
-		this.headers.referrerPolicy();
+		this.http.headers().referrerPolicy();
+
+		assertHeaders();
+	}
+
+	@Test
+	public void headersWhenReferrerPolicyEnabledInLambdaThenReferrerPolicyWritten() throws Exception {
+		this.expectedHeaders.add(ReferrerPolicyServerHttpHeadersWriter.REFERRER_POLICY,
+				ReferrerPolicy.NO_REFERRER.getPolicy());
+		this.http
+				.headers(headers ->
+					headers
+						.referrerPolicy(withDefaults())
+				);
 
 		assertHeaders();
 	}
@@ -197,7 +366,23 @@ public class HeaderSpecTests {
 	public void headersWhenReferrerPolicyCustomEnabledThenFeaturePolicyCustomWritten() {
 		this.expectedHeaders.add(ReferrerPolicyServerHttpHeadersWriter.REFERRER_POLICY,
 				ReferrerPolicy.NO_REFERRER_WHEN_DOWNGRADE.getPolicy());
-		this.headers.referrerPolicy(ReferrerPolicy.NO_REFERRER_WHEN_DOWNGRADE);
+		this.http.headers().referrerPolicy(ReferrerPolicy.NO_REFERRER_WHEN_DOWNGRADE);
+
+		assertHeaders();
+	}
+
+	@Test
+	public void headersWhenReferrerPolicyCustomEnabledInLambdaThenCustomReferrerPolicyWritten() throws Exception {
+		this.expectedHeaders.add(ReferrerPolicyServerHttpHeadersWriter.REFERRER_POLICY,
+				ReferrerPolicy.NO_REFERRER_WHEN_DOWNGRADE.getPolicy());
+		this.http
+				.headers(headers ->
+					headers
+						.referrerPolicy(referrerPolicy ->
+							referrerPolicy
+								.policy(ReferrerPolicy.NO_REFERRER_WHEN_DOWNGRADE)
+						)
+				);
 
 		assertHeaders();
 	}
@@ -228,6 +413,6 @@ public class HeaderSpecTests {
 	}
 
 	private WebTestClient buildClient() {
-		return WebTestClientBuilder.bindToWebFilters(this.headers.and().build()).build();
+		return WebTestClientBuilder.bindToWebFilters(this.http.build()).build();
 	}
 }

+ 98 - 1
config/src/test/java/org/springframework/security/config/web/server/HttpsRedirectSpecTests.java

@@ -1,5 +1,5 @@
 /*
- * Copyright 2002-2018 the original author or authors.
+ * 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.
@@ -33,6 +33,7 @@ import org.springframework.web.reactive.config.EnableWebFlux;
 
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.when;
+import static org.springframework.security.config.Customizer.withDefaults;
 
 /**
  * Tests for {@link HttpsRedirectSpecTests}
@@ -71,6 +72,17 @@ public class HttpsRedirectSpecTests {
 				.expectHeader().valueEquals(HttpHeaders.LOCATION, "https://localhost");
 	}
 
+	@Test
+	public void getWhenInsecureAndRedirectConfiguredInLambdaThenRespondsWithRedirectToSecure() {
+		this.spring.register(RedirectToHttpsInLambdaConfig.class).autowire();
+
+		this.client.get()
+				.uri("http://localhost")
+				.exchange()
+				.expectStatus().isFound()
+				.expectHeader().valueEquals(HttpHeaders.LOCATION, "https://localhost");
+	}
+
 	@Test
 	public void getWhenInsecureAndPathRequiresTransportSecurityThenRedirects() {
 		this.spring.register(SometimesRedirectToHttpsConfig.class).autowire();
@@ -87,6 +99,22 @@ public class HttpsRedirectSpecTests {
 				.expectHeader().valueEquals(HttpHeaders.LOCATION, "https://localhost:8443/secure");
 	}
 
+	@Test
+	public void getWhenInsecureAndPathRequiresTransportSecurityInLambdaThenRedirects() {
+		this.spring.register(SometimesRedirectToHttpsInLambdaConfig.class).autowire();
+
+		this.client.get()
+				.uri("http://localhost:8080")
+				.exchange()
+				.expectStatus().isNotFound();
+
+		this.client.get()
+				.uri("http://localhost:8080/secure")
+				.exchange()
+				.expectStatus().isFound()
+				.expectHeader().valueEquals(HttpHeaders.LOCATION, "https://localhost:8443/secure");
+	}
+
 	@Test
 	public void getWhenInsecureAndUsingCustomPortMapperThenRespondsWithRedirectToSecurePort() {
 		this.spring.register(RedirectToHttpsViaCustomPortsConfig.class).autowire();
@@ -101,6 +129,20 @@ public class HttpsRedirectSpecTests {
 				.expectHeader().valueEquals(HttpHeaders.LOCATION, "https://localhost:4443");
 	}
 
+	@Test
+	public void getWhenInsecureAndUsingCustomPortMapperInLambdaThenRespondsWithRedirectToSecurePort() {
+		this.spring.register(RedirectToHttpsViaCustomPortsInLambdaConfig.class).autowire();
+
+		PortMapper portMapper = this.spring.getContext().getBean(PortMapper.class);
+		when(portMapper.lookupHttpsPort(4080)).thenReturn(4443);
+
+		this.client.get()
+				.uri("http://localhost:4080")
+				.exchange()
+				.expectStatus().isFound()
+				.expectHeader().valueEquals(HttpHeaders.LOCATION, "https://localhost:4443");
+	}
+
 	@EnableWebFlux
 	@EnableWebFluxSecurity
 	static class RedirectToHttpConfig {
@@ -115,6 +157,21 @@ public class HttpsRedirectSpecTests {
 		}
 	}
 
+
+	@EnableWebFlux
+	@EnableWebFluxSecurity
+	static class RedirectToHttpsInLambdaConfig {
+		@Bean
+		SecurityWebFilterChain springSecurity(ServerHttpSecurity http) throws Exception {
+			// @formatter:off
+			http
+				.redirectToHttps(withDefaults());
+			// @formatter:on
+
+			return http.build();
+		}
+	}
+
 	@EnableWebFlux
 	@EnableWebFluxSecurity
 	static class SometimesRedirectToHttpsConfig {
@@ -130,6 +187,24 @@ public class HttpsRedirectSpecTests {
 		}
 	}
 
+
+	@EnableWebFlux
+	@EnableWebFluxSecurity
+	static class SometimesRedirectToHttpsInLambdaConfig {
+		@Bean
+		SecurityWebFilterChain springSecurity(ServerHttpSecurity http) throws Exception {
+			// @formatter:off
+			http
+				.redirectToHttps(redirectToHttps ->
+					redirectToHttps
+						.httpsRedirectWhen(new PathPatternParserServerWebExchangeMatcher("/secure"))
+				);
+			// @formatter:on
+
+			return http.build();
+		}
+	}
+
 	@EnableWebFlux
 	@EnableWebFluxSecurity
 	static class RedirectToHttpsViaCustomPortsConfig {
@@ -149,4 +224,26 @@ public class HttpsRedirectSpecTests {
 			return mock(PortMapper.class);
 		}
 	}
+
+	@EnableWebFlux
+	@EnableWebFluxSecurity
+	static class RedirectToHttpsViaCustomPortsInLambdaConfig {
+		@Bean
+		SecurityWebFilterChain springSecurity(ServerHttpSecurity http) throws Exception {
+			// @formatter:off
+			http
+				.redirectToHttps(redirectToHttps ->
+					redirectToHttps
+						.portMapper(portMapper())
+				);
+			// @formatter:on
+
+			return http.build();
+		}
+
+		@Bean
+		public PortMapper portMapper() {
+			return mock(PortMapper.class);
+		}
+	}
 }

+ 48 - 1
config/src/test/java/org/springframework/security/config/web/server/LogoutSpecTests.java

@@ -1,5 +1,5 @@
 /*
- * Copyright 2002-2017 the original author or authors.
+ * 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.
@@ -25,6 +25,8 @@ import org.springframework.security.web.server.util.matcher.ServerWebExchangeMat
 import org.springframework.test.web.reactive.server.WebTestClient;
 import org.springframework.security.test.web.reactive.server.WebTestClientBuilder;
 
+import static org.springframework.security.config.Customizer.withDefaults;
+
 /**
  * @author Shazin Sadakath
  * @since 5.0
@@ -117,4 +119,49 @@ public class LogoutSpecTests {
 			.assertAt()
 			.assertLogout();
 	}
+
+	@Test
+	public void logoutWhenCustomLogoutInLambdaThenCustomLogoutUsed() throws Exception {
+		SecurityWebFilterChain securityWebFilter = this.http
+			.authorizeExchange(authorizeExchange ->
+				authorizeExchange
+					.anyExchange().authenticated()
+			)
+			.formLogin(withDefaults())
+			.logout(logout ->
+				logout
+					.requiresLogout(ServerWebExchangeMatchers.pathMatchers("/custom-logout"))
+			)
+			.build();
+
+		WebTestClient webTestClient = WebTestClientBuilder
+			.bindToWebFilters(securityWebFilter)
+			.build();
+
+		WebDriver driver = WebTestClientHtmlUnitDriverBuilder
+			.webTestClientSetup(webTestClient)
+			.build();
+
+		FormLoginTests.DefaultLoginPage loginPage = FormLoginTests.HomePage.to(driver, FormLoginTests.DefaultLoginPage.class)
+			.assertAt();
+
+		loginPage = loginPage.loginForm()
+			.username("user")
+			.password("invalid")
+			.submit(FormLoginTests.DefaultLoginPage.class)
+			.assertError();
+
+		FormLoginTests.HomePage homePage = loginPage.loginForm()
+			.username("user")
+			.password("password")
+			.submit(FormLoginTests.HomePage.class);
+
+		homePage.assertAt();
+
+		driver.get("http://localhost/custom-logout");
+
+		FormLoginTests.DefaultLoginPage.create(driver)
+			.assertAt()
+			.assertLogout();
+	}
 }

+ 44 - 0
config/src/test/java/org/springframework/security/config/web/server/OAuth2ClientSpecTests.java

@@ -185,4 +185,48 @@ public class OAuth2ClientSpecTests {
 			return http.build();
 		}
 	}
+
+	@Test
+	public void oauth2ClientWhenCustomObjectsInLambdaThenUsed() {
+		this.spring.register(ClientRegistrationConfig.class, OAuth2ClientInLambdaCustomConfig.class, AuthorizedClientController.class).autowire();
+
+		OAuth2ClientInLambdaCustomConfig config = this.spring.getContext().getBean(OAuth2ClientInLambdaCustomConfig.class);
+
+		ServerAuthenticationConverter converter = config.authenticationConverter;
+		ReactiveAuthenticationManager manager = config.manager;
+
+		OAuth2AuthorizationExchange exchange = TestOAuth2AuthorizationExchanges.success();
+		OAuth2AccessToken accessToken = TestOAuth2AccessTokens.noScopes();
+
+		OAuth2AuthorizationCodeAuthenticationToken result = new OAuth2AuthorizationCodeAuthenticationToken(this.registration, exchange, accessToken);
+
+		when(converter.convert(any())).thenReturn(Mono.just(new TestingAuthenticationToken("a", "b", "c")));
+		when(manager.authenticate(any())).thenReturn(Mono.just(result));
+
+		this.client.get()
+				.uri("/authorize/oauth2/code/registration-id")
+				.exchange()
+				.expectStatus().is3xxRedirection();
+
+		verify(converter).convert(any());
+		verify(manager).authenticate(any());
+	}
+
+	@Configuration
+	static class OAuth2ClientInLambdaCustomConfig {
+		ReactiveAuthenticationManager manager = mock(ReactiveAuthenticationManager.class);
+
+		ServerAuthenticationConverter authenticationConverter = mock(ServerAuthenticationConverter.class);
+
+		@Bean
+		public SecurityWebFilterChain springSecurityFilter(ServerHttpSecurity http) throws Exception {
+			http
+				.oauth2Client(oauth2Client ->
+					oauth2Client
+						.authenticationConverter(this.authenticationConverter)
+						.authenticationManager(this.manager)
+				);
+			return http.build();
+		}
+	}
 }

+ 81 - 0
config/src/test/java/org/springframework/security/config/web/server/OAuth2LoginTests.java

@@ -261,6 +261,87 @@ public class OAuth2LoginTests {
 		}
 	}
 
+	@Test
+	public void oauth2LoginWhenCustomObjectsInLambdaThenUsed() {
+		this.spring.register(OAuth2LoginWithSingleClientRegistrations.class,
+				OAuth2LoginMockAuthenticationManagerInLambdaConfig.class).autowire();
+
+		String redirectLocation = "/custom-redirect-location";
+
+		WebTestClient webTestClient = WebTestClientBuilder
+				.bindToWebFilters(this.springSecurity)
+				.build();
+
+		OAuth2LoginMockAuthenticationManagerInLambdaConfig config = this.spring.getContext()
+				.getBean(OAuth2LoginMockAuthenticationManagerInLambdaConfig.class);
+		ServerAuthenticationConverter converter = config.authenticationConverter;
+		ReactiveAuthenticationManager manager = config.manager;
+		ServerWebExchangeMatcher matcher = config.matcher;
+		ServerOAuth2AuthorizationRequestResolver resolver = config.resolver;
+		ServerAuthenticationSuccessHandler successHandler = config.successHandler;
+
+		OAuth2AuthorizationExchange exchange = TestOAuth2AuthorizationExchanges.success();
+		OAuth2User user = TestOAuth2Users.create();
+		OAuth2AccessToken accessToken = TestOAuth2AccessTokens.noScopes();
+
+		OAuth2LoginAuthenticationToken result = new OAuth2LoginAuthenticationToken(github, exchange, user, user.getAuthorities(), accessToken);
+
+		when(converter.convert(any())).thenReturn(Mono.just(new TestingAuthenticationToken("a", "b", "c")));
+		when(manager.authenticate(any())).thenReturn(Mono.just(result));
+		when(matcher.matches(any())).thenReturn(ServerWebExchangeMatcher.MatchResult.match());
+		when(resolver.resolve(any())).thenReturn(Mono.empty());
+		when(successHandler.onAuthenticationSuccess(any(), any())).thenAnswer((Answer<Mono<Void>>) invocation -> {
+			WebFilterExchange webFilterExchange = invocation.getArgument(0);
+			Authentication authentication = invocation.getArgument(1);
+
+			return new RedirectServerAuthenticationSuccessHandler(redirectLocation)
+					.onAuthenticationSuccess(webFilterExchange, authentication);
+		});
+
+		webTestClient.get()
+			.uri("/login/oauth2/code/github")
+			.exchange()
+			.expectStatus().is3xxRedirection()
+			.expectHeader().valueEquals("Location", redirectLocation);
+
+		verify(converter).convert(any());
+		verify(manager).authenticate(any());
+		verify(matcher).matches(any());
+		verify(resolver).resolve(any());
+		verify(successHandler).onAuthenticationSuccess(any(), any());
+	}
+
+	@Configuration
+	static class OAuth2LoginMockAuthenticationManagerInLambdaConfig {
+		ReactiveAuthenticationManager manager = mock(ReactiveAuthenticationManager.class);
+
+		ServerAuthenticationConverter authenticationConverter = mock(ServerAuthenticationConverter.class);
+
+		ServerWebExchangeMatcher matcher = mock(ServerWebExchangeMatcher.class);
+
+		ServerOAuth2AuthorizationRequestResolver resolver = mock(ServerOAuth2AuthorizationRequestResolver.class);
+
+		ServerAuthenticationSuccessHandler successHandler = mock(ServerAuthenticationSuccessHandler.class);
+
+		@Bean
+		public SecurityWebFilterChain springSecurityFilter(ServerHttpSecurity http) throws Exception {
+			http
+				.authorizeExchange(exchanges ->
+					exchanges
+						.anyExchange().authenticated()
+				)
+				.oauth2Login(oauth2Login ->
+					oauth2Login
+						.authenticationConverter(authenticationConverter)
+						.authenticationManager(manager)
+						.authenticationMatcher(matcher)
+						.authorizationRequestResolver(resolver)
+						.authenticationSuccessHandler(successHandler)
+				);
+			return http.build();
+		}
+	}
+
 	@Test
 	public void oauth2LoginWhenCustomBeansThenUsed() {
 		this.spring.register(OAuth2LoginWithMultipleClientRegistrations.class,

+ 179 - 0
config/src/test/java/org/springframework/security/config/web/server/OAuth2ResourceServerSpecTests.java

@@ -175,6 +175,27 @@ public class OAuth2ResourceServerSpecTests {
 				.expectHeader().value(HttpHeaders.WWW_AUTHENTICATE, startsWith("Bearer error=\"invalid_token\""));
 	}
 
+	@Test
+	public void getWhenValidTokenAndPublicKeyInLambdaThenReturnsOk() {
+		this.spring.register(PublicKeyInLambdaConfig.class, RootController.class).autowire();
+
+		this.client.get()
+				.headers(headers -> headers.setBearerAuth(this.messageReadToken))
+				.exchange()
+				.expectStatus().isOk();
+	}
+
+	@Test
+	public void getWhenExpiredTokenAndPublicKeyInLambdaThenReturnsInvalidToken() {
+		this.spring.register(PublicKeyInLambdaConfig.class).autowire();
+
+		this.client.get()
+				.headers(headers -> headers.setBearerAuth(this.expired))
+				.exchange()
+				.expectStatus().isUnauthorized()
+				.expectHeader().value(HttpHeaders.WWW_AUTHENTICATE, startsWith("Bearer error=\"invalid_token\""));
+	}
+
 	@Test
 	public void getWhenValidUsingPlaceholderThenReturnsOk() {
 		this.spring.register(PlaceholderConfig.class, RootController.class).autowire();
@@ -213,6 +234,18 @@ public class OAuth2ResourceServerSpecTests {
 				.expectStatus().isOk();
 	}
 
+	@Test
+	public void getWhenUsingJwkSetUriInLambdaThenConsultsAccordingly() {
+		this.spring.register(JwkSetUriInLambdaConfig.class, RootController.class).autowire();
+
+		MockWebServer mockWebServer = this.spring.getContext().getBean(MockWebServer.class);
+		mockWebServer.enqueue(new MockResponse().setBody(this.jwkSet));
+
+		this.client.get()
+				.headers(headers -> headers.setBearerAuth(this.messageReadTokenWithKid))
+				.exchange()
+				.expectStatus().isOk();
+	}
 
 	@Test
 	public void getWhenUsingCustomAuthenticationManagerThenUsesItAccordingly() {
@@ -230,6 +263,22 @@ public class OAuth2ResourceServerSpecTests {
 				.expectHeader().value(HttpHeaders.WWW_AUTHENTICATE, startsWith("Bearer error=\"mock-failure\""));
 	}
 
+	@Test
+	public void getWhenUsingCustomAuthenticationManagerInLambdaThenUsesItAccordingly() {
+		this.spring.register(CustomAuthenticationManagerInLambdaConfig.class).autowire();
+
+		ReactiveAuthenticationManager authenticationManager = this.spring.getContext().getBean(
+				ReactiveAuthenticationManager.class);
+		when(authenticationManager.authenticate(any(Authentication.class)))
+				.thenReturn(Mono.error(new OAuth2AuthenticationException(new OAuth2Error("mock-failure"))));
+
+		this.client.get()
+				.headers(headers -> headers.setBearerAuth(this.messageReadToken))
+				.exchange()
+				.expectStatus().isUnauthorized()
+				.expectHeader().value(HttpHeaders.WWW_AUTHENTICATE, startsWith("Bearer error=\"mock-failure\""));
+	}
+
 	@Test
 	public void getWhenUsingCustomAuthenticationManagerResolverThenUsesItAccordingly() {
 		this.spring.register(CustomAuthenticationManagerResolverConfig.class).autowire();
@@ -396,6 +445,18 @@ public class OAuth2ResourceServerSpecTests {
 				.expectStatus().isOk();
 	}
 
+	@Test
+	public void introspectWhenValidAndIntrospectionInLambdaThenReturnsOk() {
+		this.spring.register(IntrospectionInLambdaConfig.class, RootController.class).autowire();
+		this.spring.getContext().getBean(MockWebServer.class)
+				.setDispatcher(requiresAuth(clientId, clientSecret, active));
+
+		this.client.get()
+				.headers(headers -> headers.setBearerAuth(this.messageReadToken))
+				.exchange()
+				.expectStatus().isOk();
+	}
+
 	@EnableWebFlux
 	@EnableWebFluxSecurity
 	static class PublicKeyConfig {
@@ -416,6 +477,30 @@ public class OAuth2ResourceServerSpecTests {
 		}
 	}
 
+	@EnableWebFlux
+	@EnableWebFluxSecurity
+	static class PublicKeyInLambdaConfig {
+		@Bean
+		SecurityWebFilterChain springSecurity(ServerHttpSecurity http) throws Exception {
+			// @formatter:off
+			http
+				.authorizeExchange(exchanges ->
+					exchanges
+						.anyExchange().hasAuthority("SCOPE_message:read")
+				)
+				.oauth2ResourceServer(oauth2ResourceServer ->
+					oauth2ResourceServer
+						.jwt(jwt ->
+							jwt
+								.publicKey(publicKey())
+						)
+				);
+			// @formatter:on
+
+			return http.build();
+		}
+	}
+
 	@EnableWebFlux
 	@EnableWebFluxSecurity
 	static class PlaceholderConfig {
@@ -469,6 +554,40 @@ public class OAuth2ResourceServerSpecTests {
 		}
 	}
 
+	@EnableWebFlux
+	@EnableWebFluxSecurity
+	static class JwkSetUriInLambdaConfig {
+		private MockWebServer mockWebServer = new MockWebServer();
+
+		@Bean
+		SecurityWebFilterChain springSecurity(ServerHttpSecurity http) throws Exception {
+			String jwkSetUri = mockWebServer().url("/.well-known/jwks.json").toString();
+
+			// @formatter:off
+			http
+				.oauth2ResourceServer(oauth2ResourceServer ->
+					oauth2ResourceServer
+						.jwt(jwt ->
+							jwt
+								.jwkSetUri(jwkSetUri)
+						)
+				);
+			// @formatter:on
+
+			return http.build();
+		}
+
+		@Bean
+		MockWebServer mockWebServer() {
+			return this.mockWebServer;
+		}
+
+		@PreDestroy
+		void shutdown() throws IOException {
+			this.mockWebServer.shutdown();
+		}
+	}
+
 	@EnableWebFlux
 	@EnableWebFluxSecurity
 	static class CustomDecoderConfig {
@@ -531,6 +650,31 @@ public class OAuth2ResourceServerSpecTests {
 		}
 	}
 
+	@EnableWebFlux
+	@EnableWebFluxSecurity
+	static class CustomAuthenticationManagerInLambdaConfig {
+		@Bean
+		SecurityWebFilterChain springSecurity(ServerHttpSecurity http) throws Exception {
+			// @formatter:off
+			http
+				.oauth2ResourceServer(oauth2ResourceServer ->
+					oauth2ResourceServer
+						.jwt(jwt ->
+							jwt
+								.authenticationManager(authenticationManager())
+						)
+				);
+			// @formatter:on
+
+			return http.build();
+		}
+
+		@Bean
+		ReactiveAuthenticationManager authenticationManager() {
+			return mock(ReactiveAuthenticationManager.class);
+		}
+	}
+
 	@EnableWebFlux
 	@EnableWebFluxSecurity
 	static class CustomAuthenticationManagerResolverConfig {
@@ -670,6 +814,41 @@ public class OAuth2ResourceServerSpecTests {
 		}
 	}
 
+	@EnableWebFlux
+	@EnableWebFluxSecurity
+	static class IntrospectionInLambdaConfig {
+		private MockWebServer mockWebServer = new MockWebServer();
+
+		@Bean
+		SecurityWebFilterChain springSecurity(ServerHttpSecurity http) throws Exception {
+			String introspectionUri = mockWebServer().url("/introspect").toString();
+
+			// @formatter:off
+			http
+				.oauth2ResourceServer(oauth2ResourceServer ->
+					oauth2ResourceServer
+						.opaqueToken(opaqueToken ->
+								opaqueToken
+									.introspectionUri(introspectionUri)
+									.introspectionClientCredentials("client", "secret")
+						)
+				);
+			// @formatter:on
+
+			return http.build();
+		}
+
+		@Bean
+		MockWebServer mockWebServer() {
+			return this.mockWebServer;
+		}
+
+		@PreDestroy
+		void shutdown() throws IOException {
+			this.mockWebServer.shutdown();
+		}
+	}
+
 	@RestController
 	static class RootController {
 		@GetMapping

+ 35 - 0
config/src/test/java/org/springframework/security/config/web/server/RequestCacheTests.java

@@ -34,6 +34,7 @@ import org.springframework.web.bind.annotation.ResponseBody;
 import org.springframework.web.server.ServerWebExchange;
 
 import static org.assertj.core.api.Assertions.assertThat;
+import static org.springframework.security.config.Customizer.withDefaults;
 
 /**
  * @author Rob Winch
@@ -103,6 +104,40 @@ public class RequestCacheTests {
 		securedPage.assertAt();
 	}
 
+	@Test
+	public void requestWhenCustomRequestCacheInLambdaThenCustomCacheUsed() throws Exception {
+		SecurityWebFilterChain securityWebFilter = this.http
+			.authorizeExchange(authorizeExchange ->
+				authorizeExchange
+					.anyExchange().authenticated()
+			)
+			.formLogin(withDefaults())
+			.requestCache(requestCache ->
+				requestCache
+					.requestCache(NoOpServerRequestCache.getInstance())
+			)
+			.build();
+
+		WebTestClient webTestClient = WebTestClient
+			.bindToController(new SecuredPageController(), new WebTestClientBuilder.Http200RestController())
+			.webFilter(new WebFilterChainProxy(securityWebFilter))
+			.build();
+
+		WebDriver driver = WebTestClientHtmlUnitDriverBuilder
+			.webTestClientSetup(webTestClient)
+			.build();
+
+		DefaultLoginPage loginPage = SecuredPage.to(driver, DefaultLoginPage.class)
+			.assertAt();
+
+		HomePage securedPage = loginPage.loginForm()
+			.username("user")
+			.password("password")
+			.submit(HomePage.class);
+
+		securedPage.assertAt();
+	}
+
 	public static class SecuredPage {
 		private WebDriver driver;
 

+ 123 - 0
config/src/test/java/org/springframework/security/config/web/server/ServerHttpSecurityTests.java

@@ -20,8 +20,10 @@ import static org.assertj.core.api.Assertions.assertThat;
 import static org.mockito.BDDMockito.given;
 import static org.mockito.Matchers.any;
 import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.verifyZeroInteractions;
 import static org.mockito.Mockito.when;
+import static org.springframework.security.config.Customizer.withDefaults;
 
 import java.util.Arrays;
 import java.util.List;
@@ -36,6 +38,7 @@ import org.junit.runner.RunWith;
 import org.mockito.Mock;
 import org.mockito.junit.MockitoJUnitRunner;
 
+import org.springframework.security.core.Authentication;
 import org.springframework.security.web.authentication.preauth.x509.X509PrincipalExtractor;
 import org.springframework.security.web.server.authentication.ServerX509AuthenticationConverter;
 import reactor.core.publisher.Mono;
@@ -236,6 +239,19 @@ public class ServerHttpSecurityTests {
 
 	}
 
+	@Test
+	public void getWhenAnonymousConfiguredThenAuthenticationIsAnonymous() throws Exception {
+		SecurityWebFilterChain securityFilterChain = this.http.anonymous(withDefaults()).build();
+		WebTestClient client = WebTestClientBuilder.bindToControllerAndWebFilters(AnonymousAuthenticationWebFilterTests.HttpMeController.class,
+				securityFilterChain).build();
+
+		client.get()
+				.uri("/me")
+				.exchange()
+				.expectStatus().isOk()
+				.expectBody(String.class).isEqualTo("anonymousUser");
+	}
+
 	@Test
 	public void basicWithAnonymous() {
 		given(this.authenticationManager.authenticate(any())).willReturn(Mono.just(new TestingAuthenticationToken("rob", "rob", "ROLE_USER", "ROLE_ADMIN")));
@@ -283,6 +299,31 @@ public class ServerHttpSecurityTests {
 		assertThat(result.getResponseCookies().getFirst("SESSION")).isNull();
 	}
 
+	@Test
+	public void requestWhenBasicWithRealmNameInLambdaThenRealmNameUsed() throws Exception {
+		this.http.securityContextRepository(new WebSessionServerSecurityContextRepository());
+		HttpBasicServerAuthenticationEntryPoint authenticationEntryPoint = new HttpBasicServerAuthenticationEntryPoint();
+		authenticationEntryPoint.setRealm("myrealm");
+		this.http.httpBasic(httpBasic ->
+				httpBasic.authenticationEntryPoint(authenticationEntryPoint)
+		);
+		this.http.authenticationManager(this.authenticationManager);
+		ServerHttpSecurity.AuthorizeExchangeSpec authorize = this.http.authorizeExchange();
+		authorize.anyExchange().authenticated();
+
+		WebTestClient client = buildClient();
+
+		EntityExchangeResult<String> result = client.get()
+				.uri("/")
+				.exchange()
+				.expectStatus().isUnauthorized()
+				.expectHeader().value(HttpHeaders.WWW_AUTHENTICATE, value -> assertThat(value).contains("myrealm"))
+				.expectBody(String.class)
+				.returnResult();
+
+		assertThat(result.getResponseCookies().getFirst("SESSION")).isNull();
+	}
+
 	@Test
 	public void basicWithCustomAuthenticationManager() {
 		ReactiveAuthenticationManager customAuthenticationManager = mock(ReactiveAuthenticationManager.class);
@@ -302,6 +343,31 @@ public class ServerHttpSecurityTests {
 		verifyZeroInteractions(this.authenticationManager);
 	}
 
+	@Test
+	public void requestWhenBasicWithAuthenticationManagerInLambdaThenAuthenticationManagerUsed() throws Exception {
+		ReactiveAuthenticationManager customAuthenticationManager = mock(ReactiveAuthenticationManager.class);
+		given(customAuthenticationManager.authenticate(any()))
+				.willReturn(Mono.just(new TestingAuthenticationToken("rob", "rob", "ROLE_USER", "ROLE_ADMIN")));
+
+		SecurityWebFilterChain securityFilterChain = this.http
+				.httpBasic(httpBasic ->
+						httpBasic.authenticationManager(customAuthenticationManager)
+				)
+				.build();
+		WebFilterChainProxy springSecurityFilterChain = new WebFilterChainProxy(securityFilterChain);
+		WebTestClient client = WebTestClientBuilder.bindToWebFilters(springSecurityFilterChain).build();
+
+		client.get()
+				.uri("/")
+				.headers(headers -> headers.setBasicAuth("rob", "rob"))
+				.exchange()
+				.expectStatus().isOk()
+				.expectBody(String.class).consumeWith(b -> assertThat(b.getResponseBody()).isEqualTo("ok"));
+
+		verifyZeroInteractions(this.authenticationManager);
+		verify(customAuthenticationManager).authenticate(any(Authentication.class));
+	}
+
 	@Test
 	@SuppressWarnings("unchecked")
 	public void addsX509FilterWhenX509AuthenticationIsConfigured() {
@@ -319,6 +385,23 @@ public class ServerHttpSecurityTests {
 		assertThat(x509WebFilter).isNotNull();
 	}
 
+	@Test
+	public void x509WhenCustomizedThenAddsX509Filter() throws Exception {
+		X509PrincipalExtractor mockExtractor = mock(X509PrincipalExtractor.class);
+		ReactiveAuthenticationManager mockAuthenticationManager = mock(ReactiveAuthenticationManager.class);
+
+		this.http.x509(x509 ->
+				x509
+					.principalExtractor(mockExtractor)
+					.authenticationManager(mockAuthenticationManager)
+				);
+
+		SecurityWebFilterChain securityWebFilterChain = this.http.build();
+		WebFilter x509WebFilter = securityWebFilterChain.getWebFilters().filter(this::isX509Filter).blockFirst();
+
+		assertThat(x509WebFilter).isNotNull();
+	}
+
 	@Test
 	public void addsX509FilterWhenX509AuthenticationIsConfiguredWithDefaults() {
 		this.http.x509();
@@ -329,6 +412,46 @@ public class ServerHttpSecurityTests {
 		assertThat(x509WebFilter).isNotNull();
 	}
 
+	@Test
+	public void x509WhenDefaultsThenAddsX509Filter() throws Exception {
+		this.http.x509(withDefaults());
+
+		SecurityWebFilterChain securityWebFilterChain = this.http.build();
+		WebFilter x509WebFilter = securityWebFilterChain.getWebFilters().filter(this::isX509Filter).blockFirst();
+
+		assertThat(x509WebFilter).isNotNull();
+	}
+
+	@Test
+	public void postWhenCsrfDisabledThenPermitted() throws Exception {
+		SecurityWebFilterChain securityFilterChain = this.http.csrf(csrf -> csrf.disable()).build();
+		WebFilterChainProxy springSecurityFilterChain = new WebFilterChainProxy(securityFilterChain);
+		WebTestClient client = WebTestClientBuilder.bindToWebFilters(springSecurityFilterChain).build();
+
+		client.post()
+				.uri("/")
+				.exchange()
+				.expectStatus().isOk();
+	}
+
+	@Test
+	public void postWhenCustomCsrfTokenRepositoryThenUsed() throws Exception {
+		ServerCsrfTokenRepository customServerCsrfTokenRepository = mock(ServerCsrfTokenRepository.class);
+		when(customServerCsrfTokenRepository.loadToken(any(ServerWebExchange.class))).thenReturn(Mono.empty());
+		SecurityWebFilterChain securityFilterChain = this.http
+				.csrf(csrf -> csrf.csrfTokenRepository(customServerCsrfTokenRepository))
+				.build();
+		WebFilterChainProxy springSecurityFilterChain = new WebFilterChainProxy(securityFilterChain);
+		WebTestClient client = WebTestClientBuilder.bindToWebFilters(springSecurityFilterChain).build();
+
+		client.post()
+				.uri("/")
+				.exchange()
+				.expectStatus().isForbidden();
+
+		verify(customServerCsrfTokenRepository).loadToken(any());
+	}
+
 	private boolean isX509Filter(WebFilter filter) {
 		try {
 			Object converter = ReflectionTestUtils.getField(filter, "authenticationConverter");

+ 2 - 2
docs/manual/src/docs/asciidoc/_includes/reactive/cors.adoc

@@ -28,10 +28,10 @@ The following will disable the CORS integration within Spring Security:
 [source,java]
 ----
 @Bean
-SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) {
+SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) throws Exception {
 	http
 		// ...
-		.cors().disable();
+		.cors(cors -> cors.disable());
 	return http.build();
 }
 ----

+ 80 - 40
docs/manual/src/docs/asciidoc/_includes/reactive/headers.adoc

@@ -53,12 +53,20 @@ You can easily do this with the following Java Configuration:
 [source,java]
 ----
 @Bean
-SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) {
+SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) throws Exception {
 	http
 		// ...
-		.headers()
-			.hsts().disable()
-			.frameOptions().mode(Mode.SAMEORIGIN);
+		.headers(headers ->
+			headers
+				.hsts(hsts ->
+					hsts
+						.disable()
+				)
+				.frameOptions(frameOptions ->
+					frameOptions
+						.mode(Mode.SAMEORIGIN)
+				)
+		);
 	return http.build();
 }
 ----
@@ -72,11 +80,13 @@ If necessary, you can disable all of the HTTP Security response headers with the
 [source,java]
 ----
 @Bean
-SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) {
+SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) throws Exception {
 	http
 		// ...
-		.headers()
-			.disable();
+		.headers(headers ->
+			headers
+				.disable()
+		);
 	return http.build();
 }
 ----
@@ -104,11 +114,13 @@ You can also disable cache control using the following Java Configuration:
 [source,java]
 ----
 @Bean
-SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) {
+SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) throws Exception {
 	http
 		// ...
-		.headers()
-			.cache().disable();
+		.headers(headers ->
+			headers
+				.cache(cache -> cache.disable())
+		);
 	return http.build();
 }
 ----
@@ -143,11 +155,13 @@ However, if need to disable the header, the following may be used:
 [source,java]
 ----
 @Bean
-SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) {
+SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) throws Exception {
 	http
 		// ...
-		.headers()
-			.contentTypeOptions().disable();
+		.headers(headers ->
+			headers
+				.contentTypeOptions(contentTypeOptions -> contentTypeOptions.disable())
+		);
 	return http.build();
 }
 ----
@@ -188,14 +202,18 @@ You can customize HSTS headers with Java Configuration:
 [source,java]
 ----
 @Bean
-SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) {
+SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) throws Exception {
 	http
 		// ...
-		.headers()
-			.hsts()
-				.includeSubdomains(true)
-				.preload(true)
-				.maxAge(Duration.ofDays(365));
+		.headers(headers ->
+			headers
+				.hsts(hsts ->
+					hsts
+						.includeSubdomains(true)
+						.preload(true)
+						.maxAge(Duration.ofDays(365))
+				)
+		);
 	return http.build();
 }
 ----
@@ -232,12 +250,16 @@ You can customize X-Frame-Options with Java Configuration using the following:
 [source,java]
 ----
 @Bean
-SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) {
+SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) throws Exception {
 	http
 		// ...
-		.headers()
-			.frameOptions()
-				.mode(SAMEORIGIN);
+		.headers(headers ->
+			headers
+				.frameOptions(frameOptions ->
+					frameOptions
+						.mode(SAMEORIGIN)
+				)
+		);
 	return http.build();
 }
 ----
@@ -264,12 +286,13 @@ However, we can customize with Java Configuration with the following:
 [source,java]
 ----
 @Bean
-SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) {
+SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) throws Exception {
 	http
 		// ...
-		.headers()
-			.xssProtection()
-				.disable();
+		.headers(headers ->
+			headers
+				.xssProtection(xssProtection -> xssProtection.disable())
+		);
 	return http.build();
 }
 ----
@@ -345,11 +368,16 @@ You can enable the CSP header using Java configuration as shown below:
 [source,java]
 ----
 @Bean
-SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) {
+SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) throws Exception {
 	http
 		// ...
-		.headers()
-			.contentSecurityPolicy("script-src 'self' https://trustedscripts.example.com; object-src https://trustedplugins.example.com; report-uri /csp-report-endpoint/");
+		.headers(headers ->
+			headers
+				.contentSecurityPolicy(contentSecurityPolicy ->
+					contentSecurityPolicy
+						.policyDirectives("script-src 'self' https://trustedscripts.example.com; object-src https://trustedplugins.example.com; report-uri /csp-report-endpoint/")
+				)
+		);
 	return http.build();
 }
 ----
@@ -359,12 +387,17 @@ To enable the CSP _'report-only'_ header, provide the following Java configurati
 [source,java]
 ----
 @Bean
-SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) {
+SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) throws Exception {
 	http
 		// ...
-		.headers()
-			.contentSecurityPolicy("script-src 'self' https://trustedscripts.example.com; object-src https://trustedplugins.example.com; report-uri /csp-report-endpoint/")
-			.reportOnly();
+		.headers(headers ->
+			headers
+				.contentSecurityPolicy(contentSecurityPolicy ->
+					contentSecurityPolicy
+						.policyDirectives("script-src 'self' https://trustedscripts.example.com; object-src https://trustedplugins.example.com; report-uri /csp-report-endpoint/")
+						.reportOnly()
+				)
+		);
 	return http.build();
 }
 ----
@@ -405,11 +438,16 @@ You can enable the Referrer-Policy header using Java configuration as shown belo
 [source,java]
 ----
 @Bean
-SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) {
+SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) throws Exception {
 	http
 		// ...
-		.headers()
-			.referrerPolicy(ReferrerPolicy.SAME_ORIGIN);
+		.headers(headers ->
+			headers
+				.referrerPolicy(referrerPolicy ->
+					referrerPolicy
+						.policy(ReferrerPolicy.SAME_ORIGIN)
+				)
+		);
 	return http.build();
 }
 ----
@@ -438,11 +476,13 @@ You can enable the Feature-Policy header using Java configuration as shown below
 [source,java]
 ----
 @Bean
-SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) {
+SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) throws Exception {
 	http
 		// ...
-		.headers()
-			.featurePolicy("geolocation 'self'");
+		.headers(headers ->
+			headers
+				.featurePolicy("geolocation 'self'")
+		);
 	return http.build();
 }
 ----

+ 5 - 4
docs/manual/src/docs/asciidoc/_includes/reactive/method.adoc

@@ -88,10 +88,11 @@ public class SecurityConfig {
 		return http
 			// Demonstrate that method security works
 			// Best practice to use both for defense in depth
-			.authorizeExchange()
-				.anyExchange().permitAll()
-				.and()
-			.httpBasic().and()
+			.authorizeExchange(exchanges ->
+				exchanges
+					.anyExchange().permitAll()
+			)
+			.httpBasic(withDefaults())
 			.build();
 	}
 

+ 1 - 1
docs/manual/src/docs/asciidoc/_includes/reactive/oauth2/access-token.adoc

@@ -27,7 +27,7 @@ The next step is to instruct Spring Security that you wish to act as an OAuth2 C
 SecurityWebFilterChain configure(ServerHttpSecurity http) throws Exception {
 	http
 		// ...
-		.oauth2Client();
+		.oauth2Client(withDefaults());
 	return http.build();
 }
 ----

+ 10 - 8
docs/manual/src/docs/asciidoc/_includes/reactive/oauth2/login.adoc

@@ -128,10 +128,10 @@ ReactiveClientRegistrationRepository clientRegistrations() {
 }
 
 @Bean
-SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) {
+SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) throws Exception {
 	http
 		// ...
-		.oauth2Login();
+		.oauth2Login(withDefaults());
 	return http.build();
 }
 ----
@@ -141,14 +141,16 @@ Additional configuration options can be seen below:
 [source,java]
 ----
 @Bean
-SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) {
+SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) throws Exception {
 	http
 		// ...
-		.oauth2Login()
-			.authenticationConverter(converter)
-			.authenticationManager(manager)
-			.authorizedClientRepository(authorizedClients)
-			.clientRegistrationRepository(clientRegistrations);
+		.oauth2Login(oauth2Login ->
+			oauth2Login
+				.authenticationConverter(converter)
+				.authenticationManager(manager)
+				.authorizedClientRepository(authorizedClients)
+				.clientRegistrationRepository(clientRegistrations)
+		);
 	return http.build();
 }
 ----

+ 74 - 50
docs/manual/src/docs/asciidoc/_includes/reactive/oauth2/resource-server.adoc

@@ -121,14 +121,17 @@ The first is a `SecurityWebFilterChain` that configures the app as a resource se
 [source,java]
 ----
 @Bean
-SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) {
-    http
-        .authorizeExchange()
-            .anyExchange().authenticated()
-            .and()
-        .oauth2ResourceServer()
-            .jwt();
-    return http.build();
+SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) throws Exception {
+	http
+		.authorizeExchange(exchanges ->
+			exchanges
+				.anyExchange().authenticated()
+		)
+		.oauth2ResourceServer(oauth2ResourceServer ->
+			oauth2ResourceServer
+				.jwt(withDefaults())
+		);
+	return http.build();
 }
 ----
 
@@ -139,14 +142,17 @@ Replacing this is as simple as exposing the bean within the application:
 [source,java]
 ----
 @Bean
-SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) {
+SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) throws Exception {
 	http
-		.authorizeExchange()
-			.pathMatchers("/message/**").hasAuthority("SCOPE_message:read")
-			.anyExchange().authenticated()
-			.and()
-		.oauth2ResourceServer()
-			.jwt();
+		.authorizeExchange(exchanges ->
+			exchanges
+				.pathMatchers("/message/**").hasAuthority("SCOPE_message:read")
+				.anyExchange().authenticated()
+		)
+		.oauth2ResourceServer(oauth2ResourceServer ->
+			oauth2ResourceServer
+				.jwt(withDefaults())
+		);
 	return http.build();
 }
 ----
@@ -177,15 +183,20 @@ An authorization server's JWK Set Uri can be configured <<webflux-oauth2-resourc
 [source,java]
 ----
 @Bean
-SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) {
-    http
-        .authorizeExchange()
-            .anyExchange().authenticated()
-            .and()
-        .oauth2ResourceServer()
-            .jwt()
-                .jwkSetUri("https://idp.example.com/.well-known/jwks.json");
-    return http.build();
+SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) throws Exception {
+	http
+		.authorizeExchange(exchanges ->
+			exchanges
+				.anyExchange().authenticated()
+		)
+		.oauth2ResourceServer(oauth2ResourceServer ->
+			oauth2ResourceServer
+				.jwt(jwt ->
+					jwt
+						.jwkSetUri("https://idp.example.com/.well-known/jwks.json")
+				)
+		);
+	return http.build();
 }
 ----
 
@@ -199,14 +210,19 @@ More powerful than `jwkSetUri()` is `decoder()`, which will completely replace a
 [source,java]
 ----
 @Bean
-SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) {
-    http
-        .authorizeExchange()
-            .anyExchange().authenticated()
-            .and()
-        .oauth2ResourceServer()
-            .jwt()
-                .decoder(myCustomDecoder());
+SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) throws Exception {
+	http
+		.authorizeExchange(exchanges ->
+			exchanges
+				.anyExchange().authenticated()
+		)
+		.oauth2ResourceServer(oauth2ResourceServer ->
+			oauth2ResourceServer
+				.jwt(jwt ->
+					jwt
+						.decoder(myCustomDecoder())
+				)
+		);
     return http.build();
 }
 ----
@@ -240,15 +256,18 @@ This means that to protect an endpoint or method with a scope derived from a JWT
 [source,java]
 ----
 @Bean
-SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) {
-    http
-        .authorizeExchange()
-            .mvcMatchers("/contacts/**").hasAuthority("SCOPE_contacts")
-            .mvcMatchers("/messages/**").hasAuthority("SCOPE_messages")
-            .anyExchange().authenticated()
-            .and()
-        .oauth2ResourceServer()
-            .jwt();
+SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) throws Exception {
+	http
+		.authorizeExchange(exchanges ->
+			exchanges
+				.mvcMatchers("/contacts/**").hasAuthority("SCOPE_contacts")
+				.mvcMatchers("/messages/**").hasAuthority("SCOPE_messages")
+				.anyExchange().authenticated()
+		)
+		.oauth2ResourceServer(oauth2ResourceServer ->
+			oauth2ResourceServer
+				.jwt(withDefaults())
+		);
     return http.build();
 }
 ----
@@ -273,15 +292,20 @@ To this end, the DSL exposes `jwtAuthenticationConverter()`:
 [source,java]
 ----
 @Bean
-SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) {
-    http
-        .authorizeExchange()
-            .anyExchange().authenticated()
-            .and()
-        .oauth2ResourceServer()
-            .jwt()
-                .jwtAuthenticationConverter(grantedAuthoritiesExtractor());
-    return http.build();
+SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) throws Exception {
+	http
+		.authorizeExchange(exchanges ->
+			exchanges
+				.anyExchange().authenticated()
+		)
+		.oauth2ResourceServer(oauth2ResourceServer ->
+			oauth2ResourceServer
+				.jwt(jwt ->
+					jwt
+						.jwtAuthenticationConverter(grantedAuthoritiesExtractor())
+				)
+		);
+	return http.build();
 }
 
 Converter<Jwt, Mono<AbstractAuthenticationToken>> grantedAuthoritiesExtractor() {

+ 7 - 5
docs/manual/src/docs/asciidoc/_includes/reactive/redirect-https.adoc

@@ -7,10 +7,10 @@ Spring Security can be configured to perform a redirect to https using the follo
 [source,java]
 ----
 @Bean
-SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) {
+SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) throws Exception {
 	http
 		// ...
-		.redirectToHttps();
+		.redirectToHttps(withDefaults());
 	return http.build();
 }
 ----
@@ -22,11 +22,13 @@ For example, if the production environment adds a header named `X-Forwarded-Prot
 [source,java]
 ----
 @Bean
-SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) {
+SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) throws Exception {
 	http
 		// ...
-		.redirectToHttps()
-			.httpsRedirectWhen(e -> e.getRequest().getHeaders().containsKey("X-Forwarded-Proto"));
+		.redirectToHttps(redirectToHttps ->
+			redirectToHttps
+				.httpsRedirectWhen(e -> e.getRequest().getHeaders().containsKey("X-Forwarded-Proto"))
+		);
 	return http.build();
 }
 ----

+ 7 - 6
docs/manual/src/docs/asciidoc/_includes/reactive/webflux.adoc

@@ -52,13 +52,14 @@ public class HelloWebfluxSecurityConfig {
 	}
 
 	@Bean
-	public SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) {
+	public SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) throws Exception {
 		http
-			.authorizeExchange()
-				.anyExchange().authenticated()
-				.and()
-			.httpBasic().and()
-			.formLogin();
+			.authorizeExchange(exchanges ->
+				exchanges
+					.anyExchange().authenticated()
+			)
+			.httpBasic(withDefaults())
+			.formLogin(withDefaults());
 		return http.build();
 	}
 }

+ 30 - 30
docs/manual/src/docs/asciidoc/_includes/reactive/x509.adoc

@@ -7,14 +7,14 @@ Below is an example of a reactive x509 security configuration:
 [source,java]
 ----
 @Bean
-public SecurityWebFilterChain securityWebFilterChain(ServerHttpSecurity http) {
-    http
-        .x509()
-        .and()
-        .authorizeExchange()
-            .anyExchange().permitAll();
-
-    return http.build();
+public SecurityWebFilterChain securityWebFilterChain(ServerHttpSecurity http) throws Exception {
+	http
+		.x509(withDefaults())
+		.authorizeExchange(exchanges ->
+			exchanges
+				.anyExchange().permitAll()
+		);
+	return http.build();
 }
 ----
 
@@ -25,28 +25,28 @@ The next example demonstrates how these defaults can be overridden.
 [source,java]
 ----
 @Bean
-public SecurityWebFilterChain securityWebFilterChain(ServerHttpSecurity http) {
-    SubjectDnX509PrincipalExtractor principalExtractor =
-            new SubjectDnX509PrincipalExtractor();
-
-    principalExtractor.setSubjectDnRegex("OU=(.*?)(?:,|$)");
-
-    ReactiveAuthenticationManager authenticationManager = authentication -> {
-        authentication.setAuthenticated("Trusted Org Unit".equals(authentication.getName()));
-        return Mono.just(authentication);
-    };
-
-    // @formatter:off
-    http
-        .x509()
-            .principalExtractor(principalExtractor)
-            .authenticationManager(authenticationManager)
-        .and()
-            .authorizeExchange()
-            .anyExchange().authenticated();
-    // @formatter:on
-
-    return http.build();
+public SecurityWebFilterChain securityWebFilterChain(ServerHttpSecurity http) throws Exception {
+	SubjectDnX509PrincipalExtractor principalExtractor =
+	        new SubjectDnX509PrincipalExtractor();
+
+	principalExtractor.setSubjectDnRegex("OU=(.*?)(?:,|$)");
+
+	ReactiveAuthenticationManager authenticationManager = authentication -> {
+		authentication.setAuthenticated("Trusted Org Unit".equals(authentication.getName()));
+		return Mono.just(authentication);
+	};
+
+	http
+		.x509(x509 ->
+			x509
+				.principalExtractor(principalExtractor)
+				.authenticationManager(authenticationManager)
+		)
+		.authorizeExchange(exchanges ->
+			exchanges
+				.anyExchange().authenticated()
+		);
+	return http.build();
 }
 ----
 

+ 8 - 5
samples/boot/hellowebflux-method/src/main/java/sample/SecurityConfig.java

@@ -1,5 +1,5 @@
 /*
- * Copyright 2002-2017 the original author or authors.
+ * 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.
@@ -25,6 +25,8 @@ import org.springframework.security.core.userdetails.User;
 import org.springframework.security.core.userdetails.UserDetails;
 import org.springframework.security.web.server.SecurityWebFilterChain;
 
+import static org.springframework.security.config.Customizer.withDefaults;
+
 /**
  * @author Rob Winch
  * @since 5.0
@@ -38,10 +40,11 @@ public class SecurityConfig {
 		return http
 			// Demonstrate that method security works
 			// Best practice to use both for defense in depth
-			.authorizeExchange()
-				.anyExchange().permitAll()
-				.and()
-			.httpBasic().and()
+			.authorizeExchange(exchanges ->
+				exchanges
+					.anyExchange().permitAll()
+			)
+			.httpBasic(withDefaults())
 			.build();
 	}
 

+ 12 - 7
samples/boot/oauth2resourceserver-webflux/src/main/java/sample/SecurityConfig.java

@@ -1,5 +1,5 @@
 /*
- * Copyright 2002-2018 the original author or authors.
+ * 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.
@@ -21,6 +21,8 @@ import org.springframework.security.config.annotation.web.reactive.EnableWebFlux
 import org.springframework.security.config.web.server.ServerHttpSecurity;
 import org.springframework.security.web.server.SecurityWebFilterChain;
 
+import static org.springframework.security.config.Customizer.withDefaults;
+
 /**
  * @author Rob Winch
  * @since 5.1
@@ -31,12 +33,15 @@ public class SecurityConfig {
 	@Bean
 	SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) throws Exception {
 		http
-			.authorizeExchange()
-				.pathMatchers("/message/**").hasAuthority("SCOPE_message:read")
-				.anyExchange().authenticated()
-				.and()
-			.oauth2ResourceServer()
-				.jwt();
+			.authorizeExchange(exchanges ->
+				exchanges
+					.pathMatchers("/message/**").hasAuthority("SCOPE_message:read")
+					.anyExchange().authenticated()
+			)
+			.oauth2ResourceServer(oauth2ResourceServer ->
+				oauth2ResourceServer
+					.jwt(withDefaults())
+			);
 		return http.build();
 	}
 }

+ 11 - 10
samples/boot/oauth2webclient-webflux/src/main/java/sample/config/SecurityConfig.java

@@ -1,5 +1,5 @@
 /*
- * Copyright 2002-2018 the original author or authors.
+ * 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.
@@ -23,6 +23,8 @@ import org.springframework.security.core.userdetails.User;
 import org.springframework.security.core.userdetails.UserDetails;
 import org.springframework.security.web.server.SecurityWebFilterChain;
 
+import static org.springframework.security.config.Customizer.withDefaults;
+
 /**
  * @author Rob Winch
  */
@@ -32,15 +34,14 @@ public class SecurityConfig {
 	@Bean
 	SecurityWebFilterChain configure(ServerHttpSecurity http) throws Exception {
 		http
-			.authorizeExchange()
-				.pathMatchers("/", "/public/**").permitAll()
-				.anyExchange().authenticated()
-				.and()
-			.oauth2Login()
-				.and()
-			.formLogin()
-				.and()
-			.oauth2Client();
+			.authorizeExchange(exchanges ->
+				exchanges
+					.pathMatchers("/", "/public/**").permitAll()
+					.anyExchange().authenticated()
+			)
+			.oauth2Login(withDefaults())
+			.formLogin(withDefaults())
+			.oauth2Client(withDefaults());
 		return http.build();
 	}
 

+ 14 - 9
samples/boot/webflux-form/src/main/java/sample/WebfluxFormSecurityConfig.java

@@ -1,5 +1,5 @@
 /*
- * Copyright 2002-2017 the original author or authors.
+ * 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.
@@ -24,6 +24,8 @@ import org.springframework.security.core.userdetails.User;
 import org.springframework.security.core.userdetails.UserDetails;
 import org.springframework.security.web.server.SecurityWebFilterChain;
 
+import static org.springframework.security.config.Customizer.withDefaults;
+
 /**
  * @author Rob Winch
  * @since 5.0
@@ -42,15 +44,18 @@ public class WebfluxFormSecurityConfig {
 	}
 
 	@Bean
-	SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) {
+	SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) throws Exception {
 		http
-			.authorizeExchange()
-				.pathMatchers("/login").permitAll()
-				.anyExchange().authenticated()
-				.and()
-			.httpBasic().and()
-			.formLogin()
-				.loginPage("/login");
+			.authorizeExchange(exchanges ->
+				exchanges
+					.pathMatchers("/login").permitAll()
+					.anyExchange().authenticated()
+			)
+			.httpBasic(withDefaults())
+			.formLogin(formLogin ->
+				formLogin
+					.loginPage("/login")
+			);
 		return http.build();
 	}
 }

+ 9 - 6
samples/boot/webflux-x509/src/main/java/sample/WebfluxX509Application.java

@@ -1,5 +1,5 @@
 /*
- * Copyright 2002-2018 the original author or authors.
+ * 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.
@@ -25,6 +25,8 @@ import org.springframework.security.core.userdetails.ReactiveUserDetailsService;
 import org.springframework.security.core.userdetails.User;
 import org.springframework.security.web.server.SecurityWebFilterChain;
 
+import static org.springframework.security.config.Customizer.withDefaults;
+
 /**
  * @author Alexey Nesterov
  * @since 5.2
@@ -40,13 +42,14 @@ public class WebfluxX509Application {
 	}
 
 	@Bean
-	public SecurityWebFilterChain securityWebFilterChain(ServerHttpSecurity http) {
+	public SecurityWebFilterChain securityWebFilterChain(ServerHttpSecurity http) throws Exception {
 		// @formatter:off
 		http
-			.x509()
-				.and()
-				.authorizeExchange()
-				.anyExchange().authenticated();
+			.x509(withDefaults())
+			.authorizeExchange(exchanges ->
+				exchanges
+					.anyExchange().authenticated()
+			);
 		// @formatter:on
 
 		return http.build();