From 1fe414379c34ce3e22723f98df8c491f72dc1ce0 Mon Sep 17 00:00:00 2001 From: Rob Winch Date: Wed, 13 Sep 2017 10:39:42 -0500 Subject: [PATCH] Simplify hellowebfluxfn This sample should be absolute minimal example --- .../HelloWebfluxFnApplicationITests.java | 139 +++------------ ...ntroller.java => HelloUserController.java} | 38 ++--- .../sample/HelloWebfluxFnApplication.java | 8 +- .../sample/HelloWebfluxFnSecurityConfig.java | 42 +++++ .../src/main/java/sample/SecurityConfig.java | 65 ------- .../HelloWebfluxFnApplicationTests.java | 158 ++++-------------- 6 files changed, 109 insertions(+), 341 deletions(-) rename samples/javaconfig/hellowebfluxfn/src/main/java/sample/{UserController.java => HelloUserController.java} (50%) create mode 100644 samples/javaconfig/hellowebfluxfn/src/main/java/sample/HelloWebfluxFnSecurityConfig.java delete mode 100644 samples/javaconfig/hellowebfluxfn/src/main/java/sample/SecurityConfig.java diff --git a/samples/javaconfig/hellowebfluxfn/src/integration-test/java/sample/HelloWebfluxFnApplicationITests.java b/samples/javaconfig/hellowebfluxfn/src/integration-test/java/sample/HelloWebfluxFnApplicationITests.java index 7d44430c46..6b529eb71b 100644 --- a/samples/javaconfig/hellowebfluxfn/src/integration-test/java/sample/HelloWebfluxFnApplicationITests.java +++ b/samples/javaconfig/hellowebfluxfn/src/integration-test/java/sample/HelloWebfluxFnApplicationITests.java @@ -15,13 +15,14 @@ */ package sample; +import java.time.Duration; + import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; + import org.springframework.beans.factory.annotation.Value; -import org.springframework.http.HttpHeaders; -import org.springframework.http.HttpStatus; -import org.springframework.security.web.server.header.ContentTypeOptionsHttpHeadersWriter; +import org.springframework.http.ResponseCookie; import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.TestPropertySource; import org.springframework.test.context.junit4.SpringRunner; @@ -29,10 +30,6 @@ import org.springframework.test.web.reactive.server.ExchangeResult; import org.springframework.test.web.reactive.server.WebTestClient; import org.springframework.web.reactive.function.client.ExchangeFilterFunction; -import java.nio.charset.Charset; -import java.time.Duration; -import java.util.Base64; - import static org.springframework.web.reactive.function.client.ExchangeFilterFunctions.basicAuthentication; /** @@ -57,161 +54,67 @@ public class HelloWebfluxFnApplicationITests { } @Test - public void basicRequired() throws Exception { + public void basicWhenNoCredentialsThenUnauthorized() throws Exception { this.rest .get() - .uri("/users") + .uri("/") .exchange() .expectStatus().isUnauthorized(); } @Test - public void basicWorks() throws Exception { + public void basicWhenValidCredentialsThenOk() throws Exception { this.rest .mutate() - .filter(robsCredentials()) + .filter(userCredentials()) .build() .get() - .uri("/principal") + .uri("/") .exchange() .expectStatus().isOk() - .expectBody().json("{\"username\":\"rob\"}"); + .expectBody().json("{\"message\":\"Hello user!\"}"); } @Test - public void basicWhenPasswordInvalid401() throws Exception { + public void basicWhenInvalidCredentialsThenUnauthorized() throws Exception { this.rest .mutate() .filter(invalidPassword()) .build() .get() - .uri("/principal") + .uri("/") .exchange() .expectStatus().isUnauthorized() .expectBody().isEmpty(); } - @Test - public void authorizationAdmin403() throws Exception { - this.rest - .mutate() - .filter(robsCredentials()) - .build() - .get() - .uri("/admin") - .exchange() - .expectStatus().isEqualTo(HttpStatus.FORBIDDEN) - .expectBody().isEmpty(); - } - - @Test - public void authorizationAdmin200() throws Exception { - this.rest - .mutate() - .filter(adminCredentials()) - .build() - .get() - .uri("/admin") - .exchange() - .expectStatus().isOk(); - } - - @Test - public void basicMissingUser401() throws Exception { - this.rest - .mutate() - .filter(basicAuthentication("missing-user", "password")) - .build() - .get() - .uri("/admin") - .exchange() - .expectStatus().isUnauthorized(); - } - - @Test - public void basicInvalidPassword401() throws Exception { - this.rest - .mutate() - .filter(invalidPassword()) - .build() - .get() - .uri("/admin") - .exchange() - .expectStatus().isUnauthorized(); - } - - @Test - public void basicInvalidParts401() throws Exception { - this.rest - .get() - .uri("/admin") - .header("Authorization", "Basic " + base64Encode("no colon")) - .exchange() - .expectStatus().isUnauthorized(); - } - @Test public void sessionWorks() throws Exception { ExchangeResult result = this.rest .mutate() - .filter(robsCredentials()) + .filter(userCredentials()) .build() .get() - .uri("/principal") + .uri("/") .exchange() + .expectStatus().isOk() .returnResult(String.class); - String session = result.getResponseHeaders().getFirst("Set-Cookie"); + ResponseCookie session = result.getResponseCookies().getFirst("SESSION"); this.rest .get() - .uri("/principal") - .header("Cookie", session) + .uri("/") + .cookie(session.getName(), session.getValue()) .exchange() .expectStatus().isOk(); } - @Test - public void principal() throws Exception { - this.rest - .mutate() - .filter(robsCredentials()) - .build() - .get() - .uri("/principal") - .exchange() - .expectStatus().isOk() - .expectBody().json("{\"username\" : \"rob\"}"); - } - - @Test - public void headers() throws Exception { - this.rest - .mutate() - .filter(robsCredentials()) - .build() - .get() - .uri("/principal") - .exchange() - .expectHeader().valueEquals(HttpHeaders.CACHE_CONTROL, "no-cache, no-store, max-age=0, must-revalidate") - .expectHeader().valueEquals(HttpHeaders.EXPIRES, "0") - .expectHeader().valueEquals(HttpHeaders.PRAGMA, "no-cache") - .expectHeader().valueEquals(ContentTypeOptionsHttpHeadersWriter.X_CONTENT_OPTIONS, ContentTypeOptionsHttpHeadersWriter.NOSNIFF); - } - - private ExchangeFilterFunction robsCredentials() { - return basicAuthentication("rob","rob"); + private ExchangeFilterFunction userCredentials() { + return basicAuthentication("user","user"); } private ExchangeFilterFunction invalidPassword() { - return basicAuthentication("rob","INVALID"); - } - - private ExchangeFilterFunction adminCredentials() { - return basicAuthentication("admin","admin"); - } - - private String base64Encode(String value) { - return Base64.getEncoder().encodeToString(value.getBytes(Charset.defaultCharset())); + return basicAuthentication("user","INVALID"); } } diff --git a/samples/javaconfig/hellowebfluxfn/src/main/java/sample/UserController.java b/samples/javaconfig/hellowebfluxfn/src/main/java/sample/HelloUserController.java similarity index 50% rename from samples/javaconfig/hellowebfluxfn/src/main/java/sample/UserController.java rename to samples/javaconfig/hellowebfluxfn/src/main/java/sample/HelloUserController.java index 62a64c502f..3bee633b94 100644 --- a/samples/javaconfig/hellowebfluxfn/src/main/java/sample/UserController.java +++ b/samples/javaconfig/hellowebfluxfn/src/main/java/sample/HelloUserController.java @@ -16,40 +16,30 @@ package sample; +import java.security.Principal; +import java.util.Collections; + +import reactor.core.publisher.Mono; + import org.springframework.http.MediaType; -import org.springframework.security.core.Authentication; -import org.springframework.security.core.userdetails.User; -import org.springframework.security.web.server.context.SecurityContextRepository; -import org.springframework.security.web.server.context.WebSessionSecurityContextRepository; import org.springframework.stereotype.Component; import org.springframework.web.reactive.function.server.ServerRequest; import org.springframework.web.reactive.function.server.ServerResponse; -import reactor.core.publisher.Flux; -import reactor.core.publisher.Mono; - -import java.util.Collections; -import java.util.Map; /** * @author Rob Winch * @since 5.0 */ @Component -public class UserController { - private final SecurityContextRepository repo = new WebSessionSecurityContextRepository(); +public class HelloUserController { - - public Mono principal(ServerRequest serverRequest) { - return serverRequest.principal().cast(Authentication.class).flatMap(p -> - ServerResponse.ok() - .contentType(MediaType.APPLICATION_JSON) - .syncBody(p.getPrincipal())); - } - - public Mono admin(ServerRequest serverRequest) { - return serverRequest.principal().cast(Authentication.class).flatMap(p -> - ServerResponse.ok() - .contentType(MediaType.APPLICATION_JSON) - .syncBody( Collections.singletonMap("isadmin", "true"))); + public Mono hello(ServerRequest serverRequest) { + return serverRequest.principal() + .map(Principal::getName) + .flatMap(username -> + ServerResponse.ok() + .contentType(MediaType.APPLICATION_JSON) + .syncBody(Collections.singletonMap("message", "Hello " + username + "!")) + ); } } diff --git a/samples/javaconfig/hellowebfluxfn/src/main/java/sample/HelloWebfluxFnApplication.java b/samples/javaconfig/hellowebfluxfn/src/main/java/sample/HelloWebfluxFnApplication.java index b38ab617eb..4885ae6058 100644 --- a/samples/javaconfig/hellowebfluxfn/src/main/java/sample/HelloWebfluxFnApplication.java +++ b/samples/javaconfig/hellowebfluxfn/src/main/java/sample/HelloWebfluxFnApplication.java @@ -53,16 +53,16 @@ public class HelloWebfluxFnApplication { @Bean public NettyContext nettyContext(HttpHandler handler) { ReactorHttpHandlerAdapter adapter = new ReactorHttpHandlerAdapter(handler); - HttpServer httpServer = HttpServer.create("localhost", port); + HttpServer httpServer = HttpServer.create("localhost", this.port); return httpServer.newHandler(adapter).block(); } @Bean - public RouterFunction routes(UserController userController) { + public RouterFunction routes(HelloUserController userController) { return route( - GET("/principal"), userController::principal).andRoute( - GET("/admin"), userController::admin); + GET("/"), userController::hello); } + @Bean public HttpHandler httpHandler(RouterFunction routes, WebFilter springSecurityFilterChain) { HandlerStrategies handlerStrategies = HandlerStrategies.builder() diff --git a/samples/javaconfig/hellowebfluxfn/src/main/java/sample/HelloWebfluxFnSecurityConfig.java b/samples/javaconfig/hellowebfluxfn/src/main/java/sample/HelloWebfluxFnSecurityConfig.java new file mode 100644 index 0000000000..e6e4d7c0f5 --- /dev/null +++ b/samples/javaconfig/hellowebfluxfn/src/main/java/sample/HelloWebfluxFnSecurityConfig.java @@ -0,0 +1,42 @@ +/* + * + * * Copyright 2002-2017 the original author or authors. + * * + * * Licensed under the Apache License, Version 2.0 (the "License"); + * * you may not use this file except in compliance with the License. + * * You may obtain a copy of the License at + * * + * * http://www.apache.org/licenses/LICENSE-2.0 + * * + * * Unless required by applicable law or agreed to in writing, software + * * distributed under the License is distributed on an "AS IS" BASIS, + * * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * * See the License for the specific language governing permissions and + * * limitations under the License. + * + */ + +package sample; + +import org.springframework.context.annotation.Bean; +import org.springframework.security.config.annotation.web.reactive.EnableWebFluxSecurity; +import org.springframework.security.core.userdetails.MapUserDetailsRepository; +import org.springframework.security.core.userdetails.User; +import org.springframework.security.core.userdetails.UserDetails; + +/** + * @author Rob Winch + * @since 5.0 + */ +@EnableWebFluxSecurity +public class HelloWebfluxFnSecurityConfig { + + @Bean + public MapUserDetailsRepository userDetailsRepository() { + UserDetails user = User.withUsername("user") + .password("user") + .roles("USER") + .build(); + return new MapUserDetailsRepository(user); + } +} diff --git a/samples/javaconfig/hellowebfluxfn/src/main/java/sample/SecurityConfig.java b/samples/javaconfig/hellowebfluxfn/src/main/java/sample/SecurityConfig.java deleted file mode 100644 index 86f856a01e..0000000000 --- a/samples/javaconfig/hellowebfluxfn/src/main/java/sample/SecurityConfig.java +++ /dev/null @@ -1,65 +0,0 @@ -/* - * - * * Copyright 2002-2017 the original author or authors. - * * - * * Licensed under the Apache License, Version 2.0 (the "License"); - * * you may not use this file except in compliance with the License. - * * You may obtain a copy of the License at - * * - * * http://www.apache.org/licenses/LICENSE-2.0 - * * - * * Unless required by applicable law or agreed to in writing, software - * * distributed under the License is distributed on an "AS IS" BASIS, - * * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * * See the License for the specific language governing permissions and - * * limitations under the License. - * - */ - -package sample; - -import org.springframework.context.annotation.Bean; -import org.springframework.security.core.userdetails.MapUserDetailsRepository; -import org.springframework.security.authorization.AuthorizationDecision; -import org.springframework.security.config.annotation.web.reactive.EnableWebFluxSecurity; -import org.springframework.security.config.web.server.HttpSecurity; -import org.springframework.security.core.Authentication; -import org.springframework.security.core.userdetails.User; -import org.springframework.security.core.userdetails.UserDetails; -import org.springframework.security.web.server.SecurityWebFilterChain; -import org.springframework.security.web.server.WebFilterChainFilter; -import org.springframework.security.web.server.authorization.AuthorizationContext; -import reactor.core.publisher.Mono; - -/** - * @author Rob Winch - * @since 5.0 - */ -@EnableWebFluxSecurity -public class SecurityConfig { - - @Bean - SecurityWebFilterChain httpSecurity(HttpSecurity http) throws Exception { - return http - .authorizeExchange() - .pathMatchers("/admin/**").hasRole("ADMIN") - .pathMatchers("/users/{user}/**").access(this::currentUserMatchesPath) - .anyExchange().authenticated() - .and() - .build(); - } - - private Mono currentUserMatchesPath(Mono authentication, AuthorizationContext context) { - return authentication - .map( a -> context.getVariables().get("user").equals(a.getName())) - .map( granted -> new AuthorizationDecision(granted)); - } - - @Bean - public MapUserDetailsRepository userDetailsRepository() { - UserDetails rob = User.withUsername("rob").password("rob").roles("USER").build(); - UserDetails admin = User.withUsername("admin").password("admin").roles("USER","ADMIN").build(); - return new MapUserDetailsRepository(rob, admin); - } - -} diff --git a/samples/javaconfig/hellowebfluxfn/src/test/java/sample/HelloWebfluxFnApplicationTests.java b/samples/javaconfig/hellowebfluxfn/src/test/java/sample/HelloWebfluxFnApplicationTests.java index 8cf64c84d2..4771f4d3ff 100644 --- a/samples/javaconfig/hellowebfluxfn/src/test/java/sample/HelloWebfluxFnApplicationTests.java +++ b/samples/javaconfig/hellowebfluxfn/src/test/java/sample/HelloWebfluxFnApplicationTests.java @@ -20,13 +20,10 @@ package sample; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; + import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.http.HttpHeaders; -import org.springframework.http.HttpStatus; import org.springframework.http.ResponseCookie; -import org.springframework.security.test.web.reactive.server.SecurityMockServerConfigurers; import org.springframework.security.web.server.WebFilterChainFilter; -import org.springframework.security.web.server.header.ContentTypeOptionsHttpHeadersWriter; import org.springframework.test.context.ActiveProfiles; import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.junit4.SpringRunner; @@ -35,9 +32,7 @@ import org.springframework.test.web.reactive.server.WebTestClient; import org.springframework.web.reactive.function.client.ExchangeFilterFunction; import org.springframework.web.reactive.function.server.RouterFunction; -import java.nio.charset.Charset; -import java.util.Base64; - +import static org.springframework.security.test.web.reactive.server.SecurityMockServerConfigurers.mockUser; import static org.springframework.security.test.web.reactive.server.SecurityMockServerConfigurers.springSecurity; import static org.springframework.web.reactive.function.client.ExchangeFilterFunctions.basicAuthentication; @@ -59,188 +54,91 @@ public class HelloWebfluxFnApplicationTests { @Before public void setup() { this.rest = WebTestClient - .bindToRouterFunction(routerFunction) - .webFilter(springSecurityFilterChain) + .bindToRouterFunction(this.routerFunction) + .webFilter(this.springSecurityFilterChain) + .apply(springSecurity()) .build(); } @Test - public void basicRequired() throws Exception { + public void basicWhenNoCredentialsThenUnauthorized() throws Exception { this.rest .get() - .uri("/principal") + .uri("/") .exchange() .expectStatus().isUnauthorized(); } @Test - public void basicWorks() throws Exception { + public void basicWhenValidCredentialsThenOk() throws Exception { this.rest .mutate() - .filter(robsCredentials()) + .filter(userCredentials()) .build() .get() - .uri("/principal") + .uri("/") .exchange() .expectStatus().isOk() - .expectBody().json("{\"username\":\"rob\"}"); + .expectBody().json("{\"message\":\"Hello user!\"}"); } @Test - public void basicWhenPasswordInvalid401() throws Exception { + public void basicWhenInvalidCredentialsThenUnauthorized() throws Exception { this.rest .mutate() .filter(invalidPassword()) .build() .get() - .uri("/principal") + .uri("/") .exchange() .expectStatus().isUnauthorized() .expectBody().isEmpty(); } - @Test - public void authorizationAdmin403() throws Exception { - this.rest - .mutate() - .filter(robsCredentials()) - .build() - .get() - .uri("/admin") - .exchange() - .expectStatus().isEqualTo(HttpStatus.FORBIDDEN) - .expectBody().isEmpty(); - } - - @Test - public void authorizationAdmin200() throws Exception { - this.rest - .mutate() - .filter(adminCredentials()) - .build() - .get() - .uri("/admin") - .exchange() - .expectStatus().isOk(); - } - - @Test - public void basicMissingUser401() throws Exception { - this.rest - .mutate() - .filter(basicAuthentication("missing-user", "password")) - .build() - .get() - .uri("/admin") - .exchange() - .expectStatus().isUnauthorized(); - } - - @Test - public void basicInvalidPassword401() throws Exception { - this.rest - .mutate() - .filter(invalidPassword()) - .build() - .get() - .uri("/admin") - .exchange() - .expectStatus().isUnauthorized(); - } - - @Test - public void basicInvalidParts401() throws Exception { - this.rest - .get() - .uri("/admin") - .header("Authorization", "Basic " + base64Encode("no colon")) - .exchange() - .expectStatus().isUnauthorized(); - } - @Test public void sessionWorks() throws Exception { ExchangeResult result = this.rest .mutate() - .filter(robsCredentials()) + .filter(userCredentials()) .build() .get() - .uri("/principal") + .uri("/") .exchange() + .expectStatus().isOk() .returnResult(String.class); ResponseCookie session = result.getResponseCookies().getFirst("SESSION"); this.rest .get() - .uri("/principal") + .uri("/") .cookie(session.getName(), session.getValue()) .exchange() .expectStatus().isOk(); } @Test - public void mockSupport() throws Exception { - WebTestClient mockRest = WebTestClient.bindToRouterFunction(this.routerFunction) - .webFilter(springSecurityFilterChain) - .apply(springSecurity()) - .build(); - - mockRest - .mutateWith(SecurityMockServerConfigurers.mockUser()) + public void mockSupportWhenValidMockUserThenOk() throws Exception { + this.rest + .mutateWith(mockUser()) .get() - .uri("/principal") + .uri("/") .exchange() - .expectStatus().isOk(); + .expectStatus().isOk() + .expectBody().json("{\"message\":\"Hello user!\"}"); - mockRest + this.rest .get() - .uri("/principal") + .uri("/") .exchange() .expectStatus().isUnauthorized(); } - @Test - public void principal() throws Exception { - this.rest - .mutate() - .filter(robsCredentials()) - .build() - .get() - .uri("/principal") - .exchange() - .expectStatus().isOk() - .expectBody().json("{\"username\" : \"rob\"}"); - } - - @Test - public void headers() throws Exception { - this.rest - .mutate() - .filter(robsCredentials()) - .build() - .get() - .uri("/principal") - .exchange() - .expectHeader().valueEquals(HttpHeaders.CACHE_CONTROL, "no-cache, no-store, max-age=0, must-revalidate") - .expectHeader().valueEquals(HttpHeaders.EXPIRES, "0") - .expectHeader().valueEquals(HttpHeaders.PRAGMA, "no-cache") - .expectHeader().valueEquals(ContentTypeOptionsHttpHeadersWriter.X_CONTENT_OPTIONS, ContentTypeOptionsHttpHeadersWriter.NOSNIFF); - } - - private ExchangeFilterFunction robsCredentials() { - return basicAuthentication("rob","rob"); + private ExchangeFilterFunction userCredentials() { + return basicAuthentication("user","user"); } private ExchangeFilterFunction invalidPassword() { - return basicAuthentication("rob","INVALID"); - } - - private ExchangeFilterFunction adminCredentials() { - return basicAuthentication("admin","admin"); - } - - private String base64Encode(String value) { - return Base64.getEncoder().encodeToString(value.getBytes(Charset.defaultCharset())); + return basicAuthentication("user","INVALID"); } }