Răsfoiți Sursa

Add hellowebflux-method sample

Fixes gh-4503
Rob Winch 8 ani în urmă
părinte
comite
0eb0e3556b

+ 17 - 0
samples/javaconfig/hellowebflux-method/spring-security-samples-javaconfig-hellowebflux-method.gradle

@@ -0,0 +1,17 @@
+apply plugin: 'io.spring.convention.spring-sample'
+
+dependencies {
+	compile project(':spring-security-core')
+	compile project(':spring-security-config')
+	compile project(':spring-security-webflux')
+	compile 'com.fasterxml.jackson.core:jackson-databind'
+	compile 'io.netty:netty-buffer'
+	compile 'io.projectreactor.ipc:reactor-netty'
+	compile 'org.springframework:spring-context'
+	compile 'org.springframework:spring-webflux'
+
+	testCompile project(':spring-security-test')
+	testCompile 'io.projectreactor:reactor-test'
+	testCompile 'org.skyscreamer:jsonassert'
+	testCompile 'org.springframework:spring-test'
+}

+ 105 - 0
samples/javaconfig/hellowebflux-method/src/integration-test/java/sample/HelloWebfluxMethodApplicationITests.java

@@ -0,0 +1,105 @@
+/*
+ * 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.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.test.context.ContextConfiguration;
+import org.springframework.test.context.TestPropertySource;
+import org.springframework.test.context.junit4.SpringRunner;
+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 java.util.Map;
+import java.util.function.Consumer;
+
+import static org.springframework.web.reactive.function.client.ExchangeFilterFunctions.Credentials.basicAuthenticationCredentials;
+import static org.springframework.web.reactive.function.client.ExchangeFilterFunctions.basicAuthentication;
+
+/**
+ * @author Rob Winch
+ * @since 5.0
+ */
+@RunWith(SpringRunner.class)
+@ContextConfiguration(classes = HelloWebfluxMethodApplication.class)
+@TestPropertySource(properties = "server.port=0")
+public class HelloWebfluxMethodApplicationITests {
+	@Value("#{@nettyContext.address().getPort()}")
+	int port;
+
+	WebTestClient rest;
+
+	@Before
+	public void setup() {
+		this.rest = WebTestClient.bindToServer()
+				.filter(basicAuthentication())
+				.responseTimeout(Duration.ofDays(1))
+				.baseUrl("http://localhost:" + this.port)
+				.build();
+	}
+
+	@Test
+	public void messageWhenNotAuthenticated() throws Exception {
+		this.rest
+			.get()
+			.uri("/message")
+			.exchange()
+			.expectStatus().isUnauthorized();
+	}
+
+	@Test
+	public void messageWhenUserThenForbidden() throws Exception {
+		this.rest
+			.get()
+			.uri("/message")
+			.attributes(robsCredentials())
+			.exchange()
+			.expectStatus().isEqualTo(HttpStatus.FORBIDDEN)
+			.expectBody().isEmpty();
+	}
+
+	@Test
+	public void messageWhenAdminThenOk() throws Exception {
+		this.rest
+			.get()
+			.uri("/message")
+			.attributes(adminCredentials())
+			.exchange()
+			.expectStatus().isOk()
+			.expectBody(String.class).isEqualTo("Hello World!");
+	}
+
+	private Consumer<Map<String, Object>> robsCredentials() {
+		return basicAuthenticationCredentials("rob","rob");
+	}
+
+	private Consumer<Map<String, Object>> adminCredentials() {
+		return basicAuthenticationCredentials("admin","admin");
+	}
+
+	private String base64Encode(String value) {
+		return Base64.getEncoder().encodeToString(value.getBytes(Charset.defaultCharset()));
+	}
+}

+ 55 - 0
samples/javaconfig/hellowebflux-method/src/main/java/sample/HelloWebfluxMethodApplication.java

@@ -0,0 +1,55 @@
+/*
+ * 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.beans.factory.annotation.Value;
+import org.springframework.context.ApplicationContext;
+import org.springframework.context.annotation.*;
+import org.springframework.http.server.reactive.HttpHandler;
+import org.springframework.http.server.reactive.ReactorHttpHandlerAdapter;
+import org.springframework.web.reactive.DispatcherHandler;
+import org.springframework.web.reactive.config.EnableWebFlux;
+import reactor.ipc.netty.NettyContext;
+import reactor.ipc.netty.http.server.HttpServer;
+
+/**
+ * @author Rob Winch
+ * @since 5.0
+ */
+@Configuration
+@EnableWebFlux
+@ComponentScan
+public class HelloWebfluxMethodApplication {
+	@Value("${server.port:8080}")
+	private int port = 8080;
+
+	public static void main(String[] args) throws Exception {
+		try(AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(
+			HelloWebfluxMethodApplication.class)) {
+			context.getBean(NettyContext.class).onClose().block();
+		}
+	}
+
+	@Profile("default")
+	@Bean
+	public NettyContext nettyContext(ApplicationContext context) {
+		HttpHandler handler = DispatcherHandler.toHttpHandler(context);
+		ReactorHttpHandlerAdapter adapter = new ReactorHttpHandlerAdapter(handler);
+		HttpServer httpServer = HttpServer.create("localhost", port);
+		return httpServer.newHandler(adapter).block();
+	}
+}

+ 35 - 0
samples/javaconfig/hellowebflux-method/src/main/java/sample/HelloWorldMessageService.java

@@ -0,0 +1,35 @@
+/*
+ *
+ *  * 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.access.prepost.PreAuthorize;
+import org.springframework.stereotype.Component;
+import reactor.core.publisher.Mono;
+
+/**
+ * @author Rob Winch
+ * @since 5.0
+ */
+@Component
+public class HelloWorldMessageService {
+	@PreAuthorize("hasRole('ADMIN')")
+	public Mono<String> findMessage() {
+		return Mono.just("Hello World!");
+	}
+}

+ 39 - 0
samples/javaconfig/hellowebflux-method/src/main/java/sample/MessageController.java

@@ -0,0 +1,39 @@
+/*
+ * 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.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 MessageController {
+	private final HelloWorldMessageService messages;
+
+	public MessageController(HelloWorldMessageService messages) {
+		this.messages = messages;
+	}
+
+	@GetMapping("/message")
+	public Mono<String> message() {
+		return this.messages.findMessage();
+	}
+}

+ 59 - 0
samples/javaconfig/hellowebflux-method/src/main/java/sample/SecurityConfig.java

@@ -0,0 +1,59 @@
+/*
+ *
+ *  * 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.method.configuration.EnableReactiveMethodSecurity;
+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
+@EnableReactiveMethodSecurity
+public class SecurityConfig {
+
+	@Bean
+	SecurityWebFilterChain springWebFilterChain(HttpSecurity http) throws Exception {
+		return http
+			// we rely on method security
+			.authorizeExchange()
+				.anyExchange().permitAll()
+				.and()
+			.build();
+	}
+
+	@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);
+	}
+
+}

+ 104 - 0
samples/javaconfig/hellowebflux-method/src/test/java/sample/HelloWebfluxMethodApplicationTests.java

@@ -0,0 +1,104 @@
+/*
+ *
+ *  * 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.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.HttpStatus;
+import org.springframework.test.context.ActiveProfiles;
+import org.springframework.test.context.ContextConfiguration;
+import org.springframework.test.context.junit4.SpringRunner;
+import org.springframework.test.web.reactive.server.WebTestClient;
+
+import java.nio.charset.Charset;
+import java.util.Base64;
+import java.util.Map;
+import java.util.function.Consumer;
+
+import static org.springframework.web.reactive.function.client.ExchangeFilterFunctions.Credentials.basicAuthenticationCredentials;
+import static org.springframework.web.reactive.function.client.ExchangeFilterFunctions.basicAuthentication;
+
+/**
+ * @author Rob Winch
+ * @since 5.0
+ */
+@RunWith(SpringRunner.class)
+@ContextConfiguration(classes = HelloWebfluxMethodApplication.class)
+@ActiveProfiles("test")
+public class HelloWebfluxMethodApplicationTests {
+	@Autowired
+	ApplicationContext context;
+
+	WebTestClient rest;
+
+	@Before
+	public void setup() {
+		this.rest = WebTestClient
+			.bindToApplicationContext(context)
+			.configureClient()
+			.filter(basicAuthentication())
+			.build();
+	}
+
+	@Test
+	public void messageWhenNotAuthenticated() throws Exception {
+		this.rest
+			.get()
+			.uri("/message")
+			.exchange()
+			.expectStatus().isUnauthorized();
+	}
+
+	@Test
+	public void messageWhenUserThenForbidden() throws Exception {
+		this.rest
+			.get()
+			.uri("/message")
+			.attributes(robsCredentials())
+			.exchange()
+			.expectStatus().isEqualTo(HttpStatus.FORBIDDEN)
+			.expectBody().isEmpty();
+	}
+
+	@Test
+	public void messageWhenAdminThenOk() throws Exception {
+		this.rest
+			.get()
+			.uri("/message")
+			.attributes(adminCredentials())
+			.exchange()
+			.expectStatus().isOk()
+			.expectBody(String.class).isEqualTo("Hello World!");
+	}
+
+	private Consumer<Map<String, Object>> robsCredentials() {
+		return basicAuthenticationCredentials("rob","rob");
+	}
+
+
+	private Consumer<Map<String, Object>> adminCredentials() {
+		return basicAuthenticationCredentials("admin","admin");
+	}
+
+	private String base64Encode(String value) {
+		return Base64.getEncoder().encodeToString(value.getBytes(Charset.defaultCharset()));
+	}
+}

+ 64 - 0
samples/javaconfig/hellowebflux-method/src/test/java/sample/HelloWorldMessageServiceTests.java

@@ -0,0 +1,64 @@
+/*
+ *
+ *  * 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.junit.Test;
+import org.junit.runner.RunWith;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.security.access.AccessDeniedException;
+import org.springframework.security.test.context.support.WithMockUser;
+import org.springframework.test.context.ActiveProfiles;
+import org.springframework.test.context.ContextConfiguration;
+import org.springframework.test.context.junit4.SpringRunner;
+import reactor.test.StepVerifier;
+
+/**
+ * @author Rob Winch
+ * @since 5.0
+ */
+@RunWith(SpringRunner.class)
+@ContextConfiguration(classes = HelloWebfluxMethodApplication.class)
+@ActiveProfiles("test")
+public class HelloWorldMessageServiceTests {
+	@Autowired
+	HelloWorldMessageService messages;
+
+	@Test
+	public void messagesWhenNotAuthenticatedThenDenied() {
+		StepVerifier.create(this.messages.findMessage())
+			.expectError(AccessDeniedException.class)
+			.verify();
+	}
+
+	@Test
+	@WithMockUser
+	public void messagesWhenUserThenDenied() {
+		StepVerifier.create(this.messages.findMessage())
+			.expectError(AccessDeniedException.class)
+			.verify();
+	}
+
+	@Test
+	@WithMockUser(roles = "ADMIN")
+	public void messagesWhenAdminThenOk() {
+		StepVerifier.create(this.messages.findMessage())
+			.expectNext("Hello World!")
+			.verifyComplete();
+	}
+}