From 95caa4715f18d83fc96934fb52fd2c7940fe608a Mon Sep 17 00:00:00 2001 From: Josh Cummings Date: Mon, 19 Aug 2019 13:11:51 -0600 Subject: [PATCH] Add Reactive Mock Jwt Sample Tests Fixes gh-7278 --- .../OAuth2ResourceServerController.java | 7 ++ .../src/main/java/sample/SecurityConfig.java | 4 +- .../OAuth2ResourceServerControllerTests.java | 110 ++++++++++++++++++ 3 files changed, 120 insertions(+), 1 deletion(-) create mode 100644 samples/boot/oauth2resourceserver-webflux/src/test/java/sample/OAuth2ResourceServerControllerTests.java diff --git a/samples/boot/oauth2resourceserver-webflux/src/main/java/sample/OAuth2ResourceServerController.java b/samples/boot/oauth2resourceserver-webflux/src/main/java/sample/OAuth2ResourceServerController.java index 87b123b7f8..4e0fed8cdd 100644 --- a/samples/boot/oauth2resourceserver-webflux/src/main/java/sample/OAuth2ResourceServerController.java +++ b/samples/boot/oauth2resourceserver-webflux/src/main/java/sample/OAuth2ResourceServerController.java @@ -18,6 +18,8 @@ package sample; import org.springframework.security.core.annotation.AuthenticationPrincipal; import org.springframework.security.oauth2.jwt.Jwt; import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RestController; /** @@ -35,4 +37,9 @@ public class OAuth2ResourceServerController { public String message() { return "secret message"; } + + @PostMapping("/message") + public String createMessage(@RequestBody String message) { + return String.format("Message was created. Content: %s", message); + } } diff --git a/samples/boot/oauth2resourceserver-webflux/src/main/java/sample/SecurityConfig.java b/samples/boot/oauth2resourceserver-webflux/src/main/java/sample/SecurityConfig.java index 9c2e54afce..5f8f5f45db 100644 --- a/samples/boot/oauth2resourceserver-webflux/src/main/java/sample/SecurityConfig.java +++ b/samples/boot/oauth2resourceserver-webflux/src/main/java/sample/SecurityConfig.java @@ -17,6 +17,7 @@ package sample; import org.springframework.context.annotation.Bean; +import org.springframework.http.HttpMethod; import org.springframework.security.config.annotation.web.reactive.EnableWebFluxSecurity; import org.springframework.security.config.web.server.ServerHttpSecurity; import org.springframework.security.web.server.SecurityWebFilterChain; @@ -35,7 +36,8 @@ public class SecurityConfig { http .authorizeExchange(exchanges -> exchanges - .pathMatchers("/message/**").hasAuthority("SCOPE_message:read") + .pathMatchers(HttpMethod.GET, "/message/**").hasAuthority("SCOPE_message:read") + .pathMatchers(HttpMethod.POST, "/message/**").hasAuthority("SCOPE_message:write") .anyExchange().authenticated() ) .oauth2ResourceServer(oauth2ResourceServer -> diff --git a/samples/boot/oauth2resourceserver-webflux/src/test/java/sample/OAuth2ResourceServerControllerTests.java b/samples/boot/oauth2resourceserver-webflux/src/test/java/sample/OAuth2ResourceServerControllerTests.java new file mode 100644 index 0000000000..663f95792f --- /dev/null +++ b/samples/boot/oauth2resourceserver-webflux/src/test/java/sample/OAuth2ResourceServerControllerTests.java @@ -0,0 +1,110 @@ +/* + * Copyright 2002-2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package sample; + +import org.junit.Test; +import org.junit.runner.RunWith; +import reactor.core.publisher.Mono; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.web.reactive.WebFluxTest; +import org.springframework.boot.test.mock.mockito.MockBean; +import org.springframework.context.annotation.Import; +import org.springframework.security.core.authority.SimpleGrantedAuthority; +import org.springframework.security.oauth2.jwt.Jwt; +import org.springframework.security.oauth2.jwt.ReactiveJwtDecoder; +import org.springframework.test.context.junit4.SpringRunner; +import org.springframework.test.web.reactive.server.WebTestClient; + +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.Mockito.when; +import static org.springframework.security.test.web.reactive.server.SecurityMockServerConfigurers.mockJwt; + +/** + * @author Josh Cummings + */ +@RunWith(SpringRunner.class) +@WebFluxTest(OAuth2ResourceServerController.class) +@Import(SecurityConfig.class) +public class OAuth2ResourceServerControllerTests { + + @Autowired + WebTestClient rest; + + @MockBean + ReactiveJwtDecoder jwtDecoder; + + @Test + public void indexGreetsAuthenticatedUser() { + this.rest.mutateWith(mockJwt(jwt -> jwt.subject("test-subject"))) + .get().uri("/").exchange() + .expectBody(String.class).isEqualTo("Hello, test-subject!"); + } + + @Test + public void messageCanBeReadWithScopeMessageReadAuthority() { + this.rest.mutateWith(mockJwt(jwt -> jwt.claim("scope", "message:read"))) + .get().uri("/message").exchange() + .expectBody(String.class).isEqualTo("secret message"); + + this.rest.mutateWith(mockJwt().authorities(new SimpleGrantedAuthority("SCOPE_message:read"))) + .get().uri("/message").exchange() + .expectBody(String.class).isEqualTo("secret message"); + } + + @Test + public void messageCanNotBeReadWithoutScopeMessageReadAuthority() { + this.rest.mutateWith(mockJwt()) + .get().uri("/message").exchange() + .expectStatus().isForbidden(); + } + + @Test + public void messageCanNotBeCreatedWithoutAnyScope() { + Jwt jwt = jwt().claim("scope", "").build(); + when(this.jwtDecoder.decode(anyString())).thenReturn(Mono.just(jwt)); + this.rest.post().uri("/message") + .headers(headers -> headers.setBearerAuth(jwt.getTokenValue())) + .syncBody("Hello message").exchange() + .expectStatus().isForbidden(); + } + + @Test + public void messageCanNotBeCreatedWithScopeMessageReadAuthority() { + Jwt jwt = jwt().claim("scope", "message:read").build(); + when(this.jwtDecoder.decode(anyString())).thenReturn(Mono.just(jwt)); + this.rest.post().uri("/message") + .headers(headers -> headers.setBearerAuth(jwt.getTokenValue())) + .syncBody("Hello message").exchange() + .expectStatus().isForbidden(); + } + + @Test + public void messageCanBeCreatedWithScopeMessageWriteAuthority() { + Jwt jwt = jwt().claim("scope", "message:write").build(); + when(this.jwtDecoder.decode(anyString())).thenReturn(Mono.just(jwt)); + this.rest.post().uri("/message") + .headers(headers -> headers.setBearerAuth(jwt.getTokenValue())) + .syncBody("Hello message").exchange() + .expectStatus().isOk() + .expectBody(String.class).isEqualTo("Message was created. Content: Hello message"); + } + + private Jwt.Builder jwt() { + return Jwt.withTokenValue("token").header("alg", "none"); + } +}