Browse Source

Add :reactive:webflux:method

Rob Winch 5 năm trước cách đây
mục cha
commit
474fd5ca95

+ 20 - 0
reactive/webflux/method/build.gradle

@@ -0,0 +1,20 @@
+plugins {
+	id 'org.springframework.boot' version '2.3.1.RELEASE'
+	id 'io.spring.dependency-management' version '1.0.9.RELEASE'
+	id "nebula.integtest" version "7.0.9"
+	id 'java'
+}
+
+repositories {
+	mavenCentral()
+	maven { url "https://repo.spring.io/snapshot" }
+}
+
+dependencies {
+	implementation 'org.springframework.boot:spring-boot-starter-security'
+	implementation 'org.springframework.boot:spring-boot-starter-webflux'
+
+	testImplementation 'io.projectreactor:reactor-test'
+	testImplementation 'org.springframework.boot:spring-boot-starter-test'
+	testImplementation 'org.springframework.security:spring-security-test'
+}

+ 105 - 0
reactive/webflux/method/src/integTest/java/example/HelloMethodApplicationITests.java

@@ -0,0 +1,105 @@
+/*
+ * Copyright 2020 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package example;
+
+import java.util.function.Consumer;
+
+import org.junit.jupiter.api.Test;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.http.HttpHeaders;
+import org.springframework.test.web.reactive.server.WebTestClient;
+
+/**
+ * Integration tests.
+ *
+ * @author Rob Winch
+ * @since 5.0
+ */
+@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
+public class HelloMethodApplicationITests {
+
+	@Autowired
+	WebTestClient rest;
+
+	// --- /message ---
+
+	@Test
+	void messageWhenNotAuthenticated() {
+		// @formatter:off
+		this.rest.get()
+				.uri("/message")
+				.exchange()
+				.expectStatus().isUnauthorized();
+		// @formatter:on
+	}
+
+	@Test
+	void messageWhenUserThenOk() {
+		// @formatter:off
+		this.rest.get()
+			.uri("/message")
+			.headers(userCredentials())
+			.exchange()
+			.expectStatus().isOk();
+		// @formatter:on
+	}
+
+	// --- /secret ---
+
+	@Test
+	void secretWhenNotAuthenticated() {
+		// @formatter:off
+		this.rest.get()
+			.uri("/secret")
+			.exchange()
+			.expectStatus().isUnauthorized();
+		// @formatter:on
+	}
+
+	@Test
+	void secretWhenUserThenForbidden() {
+		// @formatter:off
+		this.rest.get()
+			.uri("/secret")
+			.headers(userCredentials())
+			.exchange()
+			.expectStatus().isForbidden();
+		// @formatter:on
+	}
+
+	@Test
+	void secretWhenAdminThenOk() {
+		// @formatter:off
+		this.rest.get()
+			.uri("/secret")
+			.headers(adminCredentials())
+			.exchange()
+			.expectStatus().isOk()
+			.expectBody(String.class).isEqualTo("Hello Admin!");
+		// @formatter:on
+	}
+
+	private Consumer<HttpHeaders> userCredentials() {
+		return (httpHeaders) -> httpHeaders.setBasicAuth("user", "password");
+	}
+
+	private Consumer<HttpHeaders> adminCredentials() {
+		return (httpHeaders) -> httpHeaders.setBasicAuth("admin", "password");
+	}
+
+}

+ 35 - 0
reactive/webflux/method/src/main/java/example/HelloMethodApplication.java

@@ -0,0 +1,35 @@
+/*
+ * Copyright 2020 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package example;
+
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+
+/**
+ * Simple application that uses method security.
+ *
+ * @author Rob Winch
+ * @since 5.0
+ */
+@SpringBootApplication
+public class HelloMethodApplication {
+
+	public static void main(String[] args) {
+		SpringApplication.run(HelloMethodApplication.class, args);
+	}
+
+}

+ 49 - 0
reactive/webflux/method/src/main/java/example/MessageController.java

@@ -0,0 +1,49 @@
+/*
+ * Copyright 2020 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package example;
+
+import reactor.core.publisher.Mono;
+
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+/**
+ * Controller for the messages.
+ *
+ * @author Rob Winch
+ * @since 5.0
+ */
+@RestController
+public class MessageController {
+
+	private final MessageService messages;
+
+	public MessageController(MessageService messages) {
+		this.messages = messages;
+	}
+
+	@GetMapping("/message")
+	public Mono<String> message() {
+		return this.messages.findMessage();
+	}
+
+	@GetMapping("/secret")
+	public Mono<String> secretMessage() {
+		return this.messages.findSecretMessage();
+	}
+
+}

+ 51 - 0
reactive/webflux/method/src/main/java/example/MessageService.java

@@ -0,0 +1,51 @@
+/*
+ * Copyright 2020 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package example;
+
+import reactor.core.publisher.Mono;
+
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.stereotype.Component;
+
+/**
+ * Message service that has method security on it.
+ *
+ * @author Rob Winch
+ * @since 5.0
+ */
+@Component
+public class MessageService {
+
+	/**
+	 * Gets a message if authenticated.
+	 * @return the message
+	 */
+	@PreAuthorize("authenticated")
+	public Mono<String> findMessage() {
+		return Mono.just("Hello User!");
+	}
+
+	/**
+	 * Gets a message if admin.
+	 * @return the message
+	 */
+	@PreAuthorize("hasRole('ADMIN')")
+	public Mono<String> findSecretMessage() {
+		return Mono.just("Hello Admin!");
+	}
+
+}

+ 73 - 0
reactive/webflux/method/src/main/java/example/SecurityConfiguration.java

@@ -0,0 +1,73 @@
+/*
+ * Copyright 2020 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package example;
+
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.security.config.annotation.method.configuration.EnableReactiveMethodSecurity;
+import org.springframework.security.config.annotation.web.reactive.EnableWebFluxSecurity;
+import org.springframework.security.config.web.server.ServerHttpSecurity;
+import org.springframework.security.core.userdetails.MapReactiveUserDetailsService;
+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;
+
+/**
+ * Minimal method security configuration.
+ *
+ * @author Rob Winch
+ * @since 5.0
+ */
+@Configuration
+@EnableWebFluxSecurity
+@EnableReactiveMethodSecurity
+public class SecurityConfiguration {
+
+	@Bean
+	SecurityWebFilterChain springWebFilterChain(ServerHttpSecurity http) {
+		// @formatter:off
+		http
+			// Demonstrate that method security works
+			// Best practice to use both for defense in depth
+			.authorizeExchange((exchanges) -> exchanges
+				.anyExchange().permitAll()
+			)
+			.httpBasic(withDefaults());
+		// @formatter:on
+		return http.build();
+	}
+
+	@Bean
+	MapReactiveUserDetailsService userDetailsService() {
+		// @formatter:off
+		UserDetails user = User.withDefaultPasswordEncoder()
+			.username("user")
+			.password("password")
+			.roles("USER")
+			.build();
+		UserDetails admin = User.withDefaultPasswordEncoder()
+			.username("admin")
+			.password("password")
+			.roles("ADMIN", "USER")
+			.build();
+		// @formatter:on
+		return new MapReactiveUserDetailsService(user, admin);
+	}
+
+}

+ 94 - 0
reactive/webflux/method/src/test/java/example/HelloMethodApplicationTests.java

@@ -0,0 +1,94 @@
+/*
+ * Copyright 2020 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package example;
+
+import org.junit.jupiter.api.Test;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.autoconfigure.web.reactive.AutoConfigureWebTestClient;
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.security.test.context.support.WithMockUser;
+import org.springframework.test.web.reactive.server.WebTestClient;
+
+/**
+ * @author Rob Winch
+ * @since 5.0
+ */
+@SpringBootTest
+@AutoConfigureWebTestClient
+public class HelloMethodApplicationTests {
+
+	@Autowired
+	WebTestClient rest;
+
+	// --- /message ---
+
+	@Test
+	void messageWhenNotAuthenticatedThenUnAuthorized() {
+		// @formatter:off
+		this.rest.get()
+				.uri("/message")
+				.exchange().
+				expectStatus().isUnauthorized();
+		// @formatter:on
+	}
+
+	@Test
+	@WithMockUser
+	void messageWhenAuthenticatedThenOk() {
+		// @formatter:off
+		this.rest.get()
+				.uri("/message")
+				.exchange()
+				.expectStatus().isUnauthorized();
+		// @formatter:on
+	}
+
+	// --- /secret ---
+
+	@Test
+	void secretWhenNotAuthenticatedThenUnAuthorized() {
+		// @formatter:off
+		this.rest.get()
+				.uri("/secret")
+				.exchange()
+				.expectStatus().isUnauthorized();
+		// @formatter:on
+	}
+
+	@Test
+	@WithMockUser
+	void secretWhenNotAuthorizedThenForbidden() {
+		// @formatter:off
+		this.rest.get()
+				.uri("/secret")
+				.exchange()
+				.expectStatus().isForbidden();
+		// @formatter:on
+	}
+
+	@Test
+	@WithMockUser(roles = "ADMIN")
+	void secretWhenAuthorizedThenOk() {
+		// @formatter:off
+		this.rest.get()
+				.uri("/secret")
+				.exchange()
+				.expectStatus().isOk();
+		// @formatter:on
+	}
+
+}

+ 89 - 0
reactive/webflux/method/src/test/java/example/MessageServiceTests.java

@@ -0,0 +1,89 @@
+/*
+ * Copyright 2020 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package example;
+
+import org.junit.jupiter.api.Test;
+import reactor.test.StepVerifier;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.security.access.AccessDeniedException;
+import org.springframework.security.test.context.support.WithMockUser;
+
+/**
+ * @author Rob Winch
+ * @since 5.0
+ */
+@SpringBootTest
+public class MessageServiceTests {
+
+	@Autowired
+	MessageService messages;
+
+	// -- findMessage ---
+
+	@Test
+	void findMessageWhenNotAuthenticatedThenDenied() {
+		// @formatter:off
+		StepVerifier.create(this.messages.findMessage())
+				.expectError(AccessDeniedException.class)
+				.verify();
+		// @formatter:on
+	}
+
+	@Test
+	@WithMockUser
+	void findMessageWhenUserThenDenied() {
+		// @formatter:off
+		StepVerifier.create(this.messages.findMessage())
+				.expectNext("Hello User!")
+				.verifyComplete();
+		// @formatter:on
+	}
+
+	// -- findSecretMessage ---
+
+	@Test
+	void findSecretMessageWhenNotAuthenticatedThenDenied() {
+		// @formatter:off
+		StepVerifier.create(this.messages.findSecretMessage())
+				.expectError(AccessDeniedException.class)
+				.verify();
+		// @formatter:on
+	}
+
+	@Test
+	@WithMockUser
+	void findSecretMessageWhenNotAuthorizedThenDenied() {
+		// @formatter:off
+		StepVerifier.create(this.messages.findSecretMessage())
+				.expectError(AccessDeniedException.class)
+				.verify();
+		// @formatter:on
+	}
+
+	@Test
+	@WithMockUser(roles = "ADMIN")
+	void findSecretMessageWhenAuthorizedThenSuccess() {
+		// @formatter:off
+		StepVerifier.create(this.messages.findSecretMessage())
+				.expectNext("Hello Admin!")
+				.verifyComplete();
+		// @formatter:on
+	}
+
+}

+ 2 - 0
settings.gradle

@@ -15,8 +15,10 @@ pluginManagement {
 
 include ':reactive:rsocket:hello-security'
 include ':reactive:webflux:hello'
+include ':reactive:webflux:Initializationmethod'
 include ':reactive:webflux:hello-security'
 include ':reactive:webflux:hello-security-explicit'
+include ':reactive:webflux:method'
 include ':servlet:spring-boot:java:hello'
 include ':servlet:spring-boot:java:hello-security'
 include ':servlet:spring-boot:java:hello-security-explicit'