diff --git a/samples/javaconfig/hellowebflux/src/integration-test/java/sample/HelloWebfluxApplicationITests.java b/samples/javaconfig/hellowebflux/src/integration-test/java/sample/HelloWebfluxApplicationITests.java index 629d580086..d83e1a33a1 100644 --- a/samples/javaconfig/hellowebflux/src/integration-test/java/sample/HelloWebfluxApplicationITests.java +++ b/samples/javaconfig/hellowebflux/src/integration-test/java/sample/HelloWebfluxApplicationITests.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; /** @@ -56,188 +53,69 @@ public class HelloWebfluxApplicationITests { .build(); } + @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()) - .build() - .get() - .uri("/principal") - .exchange() - .returnResult(String.class); + .mutate() + .filter(userCredentials()) + .build() + .get() + .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 me() throws Exception { - this.rest - .mutate() - .filter(robsCredentials()) - .build() - .get() - .uri("/me") - .exchange() - .expectStatus().isOk() - .expectBody().json("{\"username\" : \"rob\"}"); - } - - @Test - public void monoMe() throws Exception { - this.rest - .mutate() - .filter(robsCredentials()) - .build() - .get() - .uri("/mono/me") - .exchange() - .expectStatus().isOk() - .expectBody().json("{\"username\" : \"rob\"}"); - } - - @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/hellowebflux/src/main/java/sample/HelloUserController.java b/samples/javaconfig/hellowebflux/src/main/java/sample/HelloUserController.java new file mode 100644 index 0000000000..f5edaf33fa --- /dev/null +++ b/samples/javaconfig/hellowebflux/src/main/java/sample/HelloUserController.java @@ -0,0 +1,44 @@ +/* + * 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 java.security.Principal; +import java.util.Collections; +import java.util.Map; + +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RestController; +import reactor.core.publisher.Mono; + +/** + * @author Rob Winch + * @since 5.0 + */ +@RestController +public class HelloUserController { + + @GetMapping("/") + public Mono> hello(Mono principal) { + return principal + .map(Principal::getName) + .map(this::helloMessage); + } + + private Map helloMessage(String username) { + return Collections.singletonMap("message", "Hello " + username + "!"); + } +} diff --git a/samples/javaconfig/hellowebflux/src/main/java/sample/HelloWebfluxSecurityConfig.java b/samples/javaconfig/hellowebflux/src/main/java/sample/HelloWebfluxSecurityConfig.java new file mode 100644 index 0000000000..8640fe3d79 --- /dev/null +++ b/samples/javaconfig/hellowebflux/src/main/java/sample/HelloWebfluxSecurityConfig.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 HelloWebfluxSecurityConfig { + + @Bean + public MapUserDetailsRepository userDetailsRepository() { + UserDetails user = User.withUsername("user") + .password("user") + .roles("USER") + .build(); + return new MapUserDetailsRepository(user); + } +} diff --git a/samples/javaconfig/hellowebflux/src/main/java/sample/SecurityConfig.java b/samples/javaconfig/hellowebflux/src/main/java/sample/SecurityConfig.java deleted file mode 100644 index 3166596d6b..0000000000 --- a/samples/javaconfig/hellowebflux/src/main/java/sample/SecurityConfig.java +++ /dev/null @@ -1,64 +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.authorization.AuthorizationContext; -import reactor.core.publisher.Mono; - -/** - * @author Rob Winch - * @since 5.0 - */ -@EnableWebFluxSecurity -public class SecurityConfig { - - @Bean - SecurityWebFilterChain springWebFilterChain(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/hellowebflux/src/main/java/sample/UserController.java b/samples/javaconfig/hellowebflux/src/main/java/sample/UserController.java deleted file mode 100644 index fb9192fbb4..0000000000 --- a/samples/javaconfig/hellowebflux/src/main/java/sample/UserController.java +++ /dev/null @@ -1,66 +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.security.core.annotation.AuthenticationPrincipal; -import org.springframework.security.core.userdetails.UserDetails; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.RestController; -import org.springframework.web.server.WebSession; -import reactor.core.publisher.Mono; - -import java.security.Principal; -import java.util.Collections; -import java.util.Map; - -/** - * @author Rob Winch - * @since 5.0 - */ -@RestController -public class UserController { - - @GetMapping("/me") - public Mono> me(@AuthenticationPrincipal UserDetails user) { - return me(Mono.just(user)); - } - - @GetMapping("/mono/me") - public Mono> me(@AuthenticationPrincipal Mono user) { - return user.flatMap( u -> Mono.just(Collections.singletonMap("username", u.getUsername()))); - } - - @GetMapping("/mono/session") - public Mono> Session(Mono session) { - return session.flatMap( s -> Mono.just(s.getAttributes())); - } - - @GetMapping("/principal") - public Mono> principal(Principal principal) { - return principal(Mono.just(principal)); - } - - @GetMapping("/mono/principal") - public Mono> principal(Mono principal) { - return principal.flatMap( p -> Mono.just(Collections.singletonMap("username", p.getName()))); - } - - @GetMapping("/admin") - public Map admin() { - return Collections.singletonMap("isadmin", "true"); - } -} diff --git a/samples/javaconfig/hellowebflux/src/test/java/sample/HelloWebfluxApplicationTests.java b/samples/javaconfig/hellowebflux/src/test/java/sample/HelloWebfluxApplicationTests.java index cd2708c361..9fadddaa90 100644 --- a/samples/javaconfig/hellowebflux/src/test/java/sample/HelloWebfluxApplicationTests.java +++ b/samples/javaconfig/hellowebflux/src/test/java/sample/HelloWebfluxApplicationTests.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.context.ApplicationContext; -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.header.ContentTypeOptionsHttpHeadersWriter; import org.springframework.test.context.ActiveProfiles; import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.junit4.SpringRunner; @@ -34,9 +31,7 @@ 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.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; @@ -55,213 +50,91 @@ public class HelloWebfluxApplicationTests { @Before public void setup() { - this.rest = WebTestClient.bindToApplicationContext(context).build(); + this.rest = WebTestClient + .bindToApplicationContext(this.context) + .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 - .bindToApplicationContext(this.context) - .apply(springSecurity()) - .build(); - - mockRest - .mutateWith(SecurityMockServerConfigurers.mockUser()) + public void mockSupportWhenValidMockUserThenOk() throws Exception { + this.rest + .mutateWith(mockUser()) .get() - .uri("/principal") + .uri("/") .exchange() .expectStatus().isOk() - .expectBody(String.class).isEqualTo("{\"username\":\"user\"}"); + .expectBody().json("{\"message\":\"Hello user!\"}"); - mockRest + this.rest .get() - .uri("/principal") + .uri("/") .exchange() .expectStatus().isUnauthorized(); } - @Test - public void me() throws Exception { - this.rest - .mutate() - .filter(robsCredentials()) - .build() - .get() - .uri("/me") - .exchange() - .expectStatus().isOk() - .expectBody().json("{\"username\" : \"rob\"}"); - } - - @Test - public void monoMe() throws Exception { - this.rest - .mutate() - .filter(robsCredentials()) - .build() - .get() - .uri("/mono/me") - .exchange() - .expectStatus().isOk() - .expectBody().json("{\"username\" : \"rob\"}"); - } - - @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"); } }