Update WebFlux samples to use Spring Boot

Fixes: gh-5411
This commit is contained in:
Rob Winch
2018-06-05 10:27:08 -05:00
parent 1ee3c765f6
commit a9e6d7606e
39 changed files with 305 additions and 555 deletions

View File

@@ -1,16 +0,0 @@
apply plugin: 'io.spring.convention.spring-sample'
dependencies {
compile project(':spring-security-core')
compile project(':spring-security-config')
compile project(':spring-security-web')
compile 'com.fasterxml.jackson.core:jackson-databind'
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'
}

View File

@@ -1,100 +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.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.HttpStatus;
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.WebTestClient;
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);
}
@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()));
}
}

View File

@@ -1,56 +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.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.config.EnableWebFlux;
import org.springframework.web.server.adapter.WebHttpHandlerBuilder;
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 = WebHttpHandlerBuilder.applicationContext(context)
.build();
ReactorHttpHandlerAdapter adapter = new ReactorHttpHandlerAdapter(handler);
HttpServer httpServer = HttpServer.create("localhost", port);
return httpServer.newHandler(adapter).block();
}
}

View File

@@ -1,33 +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.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!");
}
}

View File

@@ -1,39 +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.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();
}
}

View File

@@ -1,56 +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.config.annotation.method.configuration.EnableReactiveMethodSecurity;
import org.springframework.security.core.userdetails.MapReactiveUserDetailsService;
import org.springframework.security.config.annotation.web.reactive.EnableWebFluxSecurity;
import org.springframework.security.config.web.server.ServerHttpSecurity;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.web.server.SecurityWebFilterChain;
/**
* @author Rob Winch
* @since 5.0
*/
@EnableWebFluxSecurity
@EnableReactiveMethodSecurity
public class SecurityConfig {
@Bean
SecurityWebFilterChain springWebFilterChain(ServerHttpSecurity http) throws Exception {
return http
// Demonstrate that method security works
// Best practice to use both for defense in depth
.authorizeExchange()
.anyExchange().permitAll()
.and()
.httpBasic().and()
.build();
}
@Bean
public MapReactiveUserDetailsService userDetailsService() {
User.UserBuilder userBuilder = User.withDefaultPasswordEncoder();
UserDetails rob = userBuilder.username("rob").password("rob").roles("USER").build();
UserDetails admin = userBuilder.username("admin").password("admin").roles("USER", "ADMIN").build();
return new MapReactiveUserDetailsService(rob, admin);
}
}

View File

@@ -1,147 +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 java.util.Map;
import java.util.function.Consumer;
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.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 org.springframework.test.web.reactive.server.WebTestClient;
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;
import static org.springframework.web.reactive.function.client.ExchangeFilterFunctions.Credentials.basicAuthenticationCredentials;
/**
* @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(this.context)
.apply(springSecurity())
.configureClient()
.filter(basicAuthentication())
.build();
}
@Test
public void messageWhenNotAuthenticated() throws Exception {
this.rest
.get()
.uri("/message")
.exchange()
.expectStatus().isUnauthorized();
}
// --- Basic Authentication ---
@Test
public void messageWhenUserThenForbidden() throws Exception {
this.rest
.get()
.uri("/message")
.attributes(robsCredentials())
.exchange()
.expectStatus().isEqualTo(HttpStatus.FORBIDDEN);
}
@Test
public void messageWhenAdminThenOk() throws Exception {
this.rest
.get()
.uri("/message")
.attributes(adminCredentials())
.exchange()
.expectStatus().isOk()
.expectBody(String.class).isEqualTo("Hello World!");
}
// --- WithMockUser ---
@Test
@WithMockUser
public void messageWhenWithMockUserThenForbidden() throws Exception {
this.rest
.get()
.uri("/message")
.exchange()
.expectStatus().isEqualTo(HttpStatus.FORBIDDEN);
}
@Test
@WithMockUser(roles = "ADMIN")
public void messageWhenWithMockAdminThenOk() throws Exception {
this.rest
.get()
.uri("/message")
.exchange()
.expectStatus().isOk()
.expectBody(String.class).isEqualTo("Hello World!");
}
// --- mutateWith mockUser ---
@Test
public void messageWhenMutateWithMockUserThenForbidden() throws Exception {
this.rest
.mutateWith(mockUser())
.get()
.uri("/message")
.exchange()
.expectStatus().isEqualTo(HttpStatus.FORBIDDEN);
}
@Test
public void messageWhenMutateWithMockAdminThenOk() throws Exception {
this.rest
.mutateWith(mockUser().roles("ADMIN"))
.get()
.uri("/message")
.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");
}
}

View File

@@ -1,62 +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.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();
}
}

View File

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

View File

@@ -1,96 +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 java.time.Duration;
import java.util.Map;
import java.util.function.Consumer;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Value;
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.WebTestClient;
import static org.springframework.web.reactive.function.client.ExchangeFilterFunctions.basicAuthentication;
import static org.springframework.web.reactive.function.client.ExchangeFilterFunctions.Credentials.basicAuthenticationCredentials;
/**
* @author Rob Winch
* @since 5.0
*/
@RunWith(SpringRunner.class)
@ContextConfiguration(classes = HelloWebfluxApplication.class)
@TestPropertySource(properties = "server.port=0")
public class HelloWebfluxApplicationITests {
@Value("#{@nettyContext.address().getPort()}")
int port;
WebTestClient rest;
@Before
public void setup() {
this.rest = WebTestClient.bindToServer()
.responseTimeout(Duration.ofDays(1))
.baseUrl("http://localhost:" + this.port)
.filter(basicAuthentication())
.build();
}
@Test
public void basicWhenNoCredentialsThenUnauthorized() throws Exception {
this.rest
.get()
.uri("/")
.exchange()
.expectStatus().isUnauthorized();
}
@Test
public void basicWhenValidCredentialsThenOk() throws Exception {
this.rest
.get()
.uri("/")
.attributes(userCredentials())
.exchange()
.expectStatus().isOk()
.expectBody().json("{\"message\":\"Hello user!\"}");
}
@Test
public void basicWhenInvalidCredentialsThenUnauthorized() throws Exception {
this.rest
.get()
.uri("/")
.attributes(invalidCredentials())
.exchange()
.expectStatus().isUnauthorized()
.expectBody().isEmpty();
}
private Consumer<Map<String, Object>> userCredentials() {
return basicAuthenticationCredentials("user", "user");
}
private Consumer<Map<String, Object>> invalidCredentials() {
return basicAuthenticationCredentials("user", "INVALID");
}
}

View File

@@ -1,44 +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 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<Map<String, String>> hello(Mono<Principal> principal) {
return principal
.map(Principal::getName)
.map(this::helloMessage);
}
private Map<String, String> helloMessage(String username) {
return Collections.singletonMap("message", "Hello " + username + "!");
}
}

View File

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

View File

@@ -1,41 +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.config.annotation.web.reactive.EnableWebFluxSecurity;
import org.springframework.security.core.userdetails.MapReactiveUserDetailsService;
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 MapReactiveUserDetailsService userDetailsService() {
UserDetails user = User.withDefaultPasswordEncoder()
.username("user")
.password("user")
.roles("USER")
.build();
return new MapReactiveUserDetailsService(user);
}
}

View File

@@ -1,121 +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 java.util.Map;
import java.util.function.Consumer;
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.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 org.springframework.test.web.reactive.server.WebTestClient;
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;
import static org.springframework.web.reactive.function.client.ExchangeFilterFunctions.Credentials.basicAuthenticationCredentials;
/**
* @author Rob Winch
* @since 5.0
*/
@RunWith(SpringRunner.class)
@ContextConfiguration(classes = HelloWebfluxApplication.class)
@ActiveProfiles("test")
public class HelloWebfluxApplicationTests {
@Autowired
ApplicationContext context;
WebTestClient rest;
@Before
public void setup() {
this.rest = WebTestClient
.bindToApplicationContext(this.context)
.apply(springSecurity())
.configureClient()
.filter(basicAuthentication())
.build();
}
@Test
public void basicWhenNoCredentialsThenUnauthorized() throws Exception {
this.rest
.get()
.uri("/")
.exchange()
.expectStatus().isUnauthorized();
}
@Test
public void basicWhenValidCredentialsThenOk() throws Exception {
this.rest
.get()
.uri("/")
.attributes(userCredentials())
.exchange()
.expectStatus().isOk()
.expectBody().json("{\"message\":\"Hello user!\"}");
}
@Test
public void basicWhenInvalidCredentialsThenUnauthorized() throws Exception {
this.rest
.get()
.uri("/")
.attributes(invalidCredentials())
.exchange()
.expectStatus().isUnauthorized()
.expectBody().isEmpty();
}
@Test
public void mockSupportWhenMutateWithMockUserThenOk() throws Exception {
this.rest
.mutateWith(mockUser())
.get()
.uri("/")
.exchange()
.expectStatus().isOk()
.expectBody().json("{\"message\":\"Hello user!\"}");
}
@Test
@WithMockUser
public void mockSupportWhenWithMockUserThenOk() throws Exception {
this.rest
.get()
.uri("/")
.exchange()
.expectStatus().isOk()
.expectBody().json("{\"message\":\"Hello user!\"}");
}
private Consumer<Map<String, Object>> userCredentials() {
return basicAuthenticationCredentials("user", "user");
}
private Consumer<Map<String, Object>> invalidCredentials() {
return basicAuthenticationCredentials("user", "INVALID");
}
}

View File

@@ -1,16 +0,0 @@
apply plugin: 'io.spring.convention.spring-sample'
dependencies {
compile project(':spring-security-core')
compile project(':spring-security-config')
compile project(':spring-security-web')
compile 'com.fasterxml.jackson.core:jackson-databind'
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'
}

View File

@@ -1,95 +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 java.time.Duration;
import java.util.Map;
import java.util.function.Consumer;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Value;
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.WebTestClient;
import static org.springframework.web.reactive.function.client.ExchangeFilterFunctions.basicAuthentication;
import static org.springframework.web.reactive.function.client.ExchangeFilterFunctions.Credentials.basicAuthenticationCredentials;
/**
* @author Rob Winch
* @since 5.0
*/
@RunWith(SpringRunner.class)
@ContextConfiguration(classes = HelloWebfluxFnApplication.class)
@TestPropertySource(properties = "server.port=0")
public class HelloWebfluxFnApplicationITests {
@Value("#{@nettyContext.address().getPort()}")
int port;
WebTestClient rest;
@Before
public void setup() {
this.rest = WebTestClient.bindToServer()
.responseTimeout(Duration.ofDays(1))
.baseUrl("http://localhost:" + this.port)
.filter(basicAuthentication())
.build();
}
@Test
public void basicWhenNoCredentialsThenUnauthorized() throws Exception {
this.rest
.get()
.uri("/")
.exchange()
.expectStatus().isUnauthorized();
}
@Test
public void basicWhenValidCredentialsThenOk() throws Exception {
this.rest
.get()
.uri("/")
.attributes(userCredentials())
.exchange()
.expectStatus().isOk()
.expectBody().json("{\"message\":\"Hello user!\"}");
}
@Test
public void basicWhenInvalidCredentialsThenUnauthorized() throws Exception {
this.rest
.get()
.uri("/")
.attributes(invalidCredentials())
.exchange()
.expectStatus().isUnauthorized()
.expectBody().isEmpty();
}
private Consumer<Map<String, Object>> userCredentials() {
return basicAuthenticationCredentials("user", "user");
}
private Consumer<Map<String, Object>> invalidCredentials() {
return basicAuthenticationCredentials("user", "INVALID");
}
}

View File

@@ -1,45 +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 java.security.Principal;
import java.util.Collections;
import reactor.core.publisher.Mono;
import org.springframework.http.MediaType;
import org.springframework.stereotype.Component;
import org.springframework.web.reactive.function.server.ServerRequest;
import org.springframework.web.reactive.function.server.ServerResponse;
/**
* @author Rob Winch
* @since 5.0
*/
@Component
public class HelloUserController {
public Mono<ServerResponse> hello(ServerRequest serverRequest) {
return serverRequest.principal()
.map(Principal::getName)
.flatMap(username ->
ServerResponse.ok()
.contentType(MediaType.APPLICATION_JSON)
.syncBody(Collections.singletonMap("message", "Hello " + username + "!"))
);
}
}

View File

@@ -1,75 +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.beans.factory.annotation.Value;
import org.springframework.context.annotation.*;
import org.springframework.http.server.reactive.HttpHandler;
import org.springframework.http.server.reactive.ReactorHttpHandlerAdapter;
import org.springframework.web.reactive.config.EnableWebFlux;
import org.springframework.web.reactive.function.server.HandlerStrategies;
import org.springframework.web.reactive.function.server.RouterFunction;
import org.springframework.web.reactive.function.server.RouterFunctions;
import org.springframework.web.reactive.function.server.ServerResponse;
import org.springframework.web.server.WebFilter;
import reactor.ipc.netty.NettyContext;
import reactor.ipc.netty.http.server.HttpServer;
import static org.springframework.web.reactive.function.server.RequestPredicates.GET;
import static org.springframework.web.reactive.function.server.RouterFunctions.route;
/**
* @author Rob Winch
* @since 5.0
*/
@Configuration
@EnableWebFlux
@ComponentScan
public class HelloWebfluxFnApplication {
@Value("${server.port:8080}")
private int port = 8080;
public static void main(String[] args) throws Exception {
try(AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(HelloWebfluxFnApplication.class)) {
context.getBean(NettyContext.class).onClose().block();
}
}
@Profile("default")
@Bean
public NettyContext nettyContext(HttpHandler handler) {
ReactorHttpHandlerAdapter adapter = new ReactorHttpHandlerAdapter(handler);
HttpServer httpServer = HttpServer.create("localhost", this.port);
return httpServer.newHandler(adapter).block();
}
@Bean
public RouterFunction<ServerResponse> routes(HelloUserController userController) {
return route(
GET("/"), userController::hello);
}
@Bean
public HttpHandler httpHandler(RouterFunction<ServerResponse> routes, WebFilter springSecurityFilterChain) {
HandlerStrategies handlerStrategies = HandlerStrategies.builder()
.webFilter(springSecurityFilterChain)
.build();
return RouterFunctions.toHttpHandler(routes, handlerStrategies);
}
}

View File

@@ -1,41 +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.config.annotation.web.reactive.EnableWebFluxSecurity;
import org.springframework.security.core.userdetails.MapReactiveUserDetailsService;
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 MapReactiveUserDetailsService userDetailsService() {
UserDetails user = User.withDefaultPasswordEncoder()
.username("user")
.password("user")
.roles("USER")
.build();
return new MapReactiveUserDetailsService(user);
}
}

View File

@@ -1,124 +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 java.util.Map;
import java.util.function.Consumer;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.test.context.support.WithMockUser;
import org.springframework.security.web.server.WebFilterChainProxy;
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 org.springframework.web.reactive.function.server.RouterFunction;
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;
import static org.springframework.web.reactive.function.client.ExchangeFilterFunctions.Credentials.basicAuthenticationCredentials;
/**
* @author Rob Winch
* @since 5.0
*/
@RunWith(SpringRunner.class)
@ContextConfiguration(classes = HelloWebfluxFnApplication.class)
@ActiveProfiles("test")
public class HelloWebfluxFnApplicationTests {
@Autowired
RouterFunction<?> routerFunction;
@Autowired WebFilterChainProxy springSecurityFilterChain;
WebTestClient rest;
@Before
public void setup() {
this.rest = WebTestClient
.bindToRouterFunction(this.routerFunction)
.webFilter(this.springSecurityFilterChain)
.apply(springSecurity())
.configureClient()
.filter(basicAuthentication())
.build();
}
@Test
public void basicWhenNoCredentialsThenUnauthorized() throws Exception {
this.rest
.get()
.uri("/")
.exchange()
.expectStatus().isUnauthorized();
}
@Test
public void basicWhenValidCredentialsThenOk() throws Exception {
this.rest
.get()
.uri("/")
.attributes(userCredentials())
.exchange()
.expectStatus().isOk()
.expectBody().json("{\"message\":\"Hello user!\"}");
}
@Test
public void basicWhenInvalidCredentialsThenUnauthorized() throws Exception {
this.rest
.get()
.uri("/")
.attributes(invalidCredentials())
.exchange()
.expectStatus().isUnauthorized()
.expectBody().isEmpty();
}
@Test
public void mockSupportWhenMutateWithMockUserThenOk() throws Exception {
this.rest
.mutateWith(mockUser())
.get()
.uri("/")
.exchange()
.expectStatus().isOk()
.expectBody().json("{\"message\":\"Hello user!\"}");
}
@Test
@WithMockUser
public void mockSupportWhenWithMockUserThenOk() throws Exception {
this.rest
.get()
.uri("/")
.exchange()
.expectStatus().isOk()
.expectBody().json("{\"message\":\"Hello user!\"}");
}
private Consumer<Map<String, Object>> userCredentials() {
return basicAuthenticationCredentials("user", "user");
}
private Consumer<Map<String, Object>> invalidCredentials() {
return basicAuthenticationCredentials("user", "INVALID");
}
}

View File

@@ -1,37 +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.
*/
apply plugin: 'io.spring.convention.spring-sample'
dependencies {
compile project(':spring-security-core')
compile project(':spring-security-config')
compile project(':spring-security-web')
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'
compile 'org.thymeleaf:thymeleaf-spring5'
compile slf4jDependencies
testCompile project(':spring-security-test')
testCompile 'io.projectreactor:reactor-test'
testCompile 'org.skyscreamer:jsonassert'
testCompile 'org.springframework:spring-test'
integrationTestCompile seleniumDependencies
}

View File

@@ -1,79 +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 com.gargoylesoftware.htmlunit.BrowserVersion;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.htmlunit.HtmlUnitDriver;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.TestPropertySource;
import org.springframework.test.context.junit4.SpringRunner;
import sample.webdriver.IndexPage;
import sample.webdriver.LoginPage;
/**
* @author Rob Winch
* @since 5.0
*/
@RunWith(SpringRunner.class)
@ContextConfiguration(classes = WebfluxFormApplication.class)
@TestPropertySource(properties = "server.port=0")
public class WebfluxFormApplicationTests {
WebDriver driver;
@Value("#{@nettyContext.address().getPort()}")
int port;
@Before
public void setup() {
this.driver = new HtmlUnitDriver(BrowserVersion.CHROME);
}
@Test
public void loginWhenInvalidUsernameThenError() throws Exception {
LoginPage login = IndexPage.to(this.driver, this.port, LoginPage.class);
login.assertAt();
login
.loginForm()
.username("invalid")
.password("password")
.submit(LoginPage.class)
.assertError();
}
@Test
public void loginAndLogout() throws Exception {
LoginPage login = IndexPage.to(this.driver, this.port, LoginPage.class);
login.assertAt();
IndexPage index = login
.loginForm()
.username("user")
.password("password")
.submit(IndexPage.class);
index.assertAt();
login = index.logout();
login
.assertAt()
.assertLogout();
}
}

View File

@@ -1,53 +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.webdriver;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.WebElement;
import org.openqa.selenium.support.PageFactory;
import static org.assertj.core.api.Assertions.assertThat;
/**
* @author Rob Winch
* @since 5.0
*/
public class IndexPage {
private WebDriver driver;
private WebElement logout;
public IndexPage(WebDriver webDriver) {
this.driver = webDriver;
}
public static <T> T to(WebDriver driver, int port, Class<T> page) {
driver.get("http://localhost:" + port +"/");
return (T) PageFactory.initElements(driver, page);
}
public IndexPage assertAt() {
assertThat(this.driver.getTitle()).isEqualTo("Secured");
return this;
}
public LoginPage logout() {
this.logout.click();
return LoginPage.create(this.driver);
}
}

View File

@@ -1,92 +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.webdriver;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.WebElement;
import org.openqa.selenium.support.FindBy;
import org.openqa.selenium.support.PageFactory;
import static org.assertj.core.api.Assertions.assertThat;
/**
* @author Rob Winch
* @since 5.0
*/
public class LoginPage {
private WebDriver driver;
@FindBy(css = "div[role=alert]")
private WebElement alert;
private LoginForm loginForm;
public LoginPage(WebDriver webDriver) {
this.driver = webDriver;
this.loginForm = PageFactory.initElements(webDriver, LoginForm.class);
}
static LoginPage create(WebDriver driver) {
return PageFactory.initElements(driver, LoginPage.class);
}
public LoginPage assertAt() {
assertThat(this.driver.getTitle()).isEqualTo("Please Log In");
return this;
}
public LoginPage assertError() {
assertThat(this.alert.getText()).isEqualTo("Invalid username and password.");
return this;
}
public LoginPage assertLogout() {
assertThat(this.alert.getText()).isEqualTo("You have been logged out.");
return this;
}
public LoginForm loginForm() {
return this.loginForm;
}
public static class LoginForm {
private WebDriver driver;
private WebElement username;
private WebElement password;
@FindBy(css = "button[type=submit]")
private WebElement submit;
public LoginForm(WebDriver driver) {
this.driver = driver;
}
public LoginForm username(String username) {
this.username.sendKeys(username);
return this;
}
public LoginForm password(String password) {
this.password.sendKeys(password);
return this;
}
public <T> T submit(Class<T> page) {
this.submit.click();
return PageFactory.initElements(this.driver, page);
}
}
}

View File

@@ -1,38 +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.web.server.csrf.CsrfToken;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;
import static org.springframework.security.web.reactive.result.view.CsrfRequestDataValueProcessor.DEFAULT_CSRF_ATTR_NAME;
/**
* @author Rob Winch
* @since 5.0
*/
@ControllerAdvice
public class CsrfControllerAdvice {
@ModelAttribute
public Mono<CsrfToken> csrfToken(ServerWebExchange exchange) {
Mono<CsrfToken> csrfToken = exchange.getAttribute(CsrfToken.class.getName());
return csrfToken.doOnSuccess(token -> exchange.getAttributes().put(DEFAULT_CSRF_ATTR_NAME, token));
}
}

View File

@@ -1,38 +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.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
/**
* @author Rob Winch
* @since 5.0
*/
@Controller
public class IndexController {
@GetMapping("/")
public String index() {
return "index";
}
@GetMapping("/login")
public String login() {
return "login";
}
}

View File

@@ -1,76 +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.ApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.reactive.config.ViewResolverRegistry;
import org.springframework.web.reactive.config.WebFluxConfigurer;
import org.thymeleaf.spring5.ISpringWebFluxTemplateEngine;
import org.thymeleaf.spring5.SpringWebFluxTemplateEngine;
import org.thymeleaf.spring5.templateresolver.SpringResourceTemplateResolver;
import org.thymeleaf.spring5.view.reactive.ThymeleafReactiveViewResolver;
import org.thymeleaf.templatemode.TemplateMode;
/**
* @author Rob Winch
* @since 5.0
*/
@Configuration
public class ThymeleafConfig implements WebFluxConfigurer {
private ApplicationContext applicationContext;
public ThymeleafConfig(final ApplicationContext applicationContext) {
this.applicationContext = applicationContext;
}
@Bean
public SpringResourceTemplateResolver thymeleafTemplateResolver() {
SpringResourceTemplateResolver resolver = new SpringResourceTemplateResolver();
resolver.setApplicationContext(this.applicationContext);
resolver.setPrefix("classpath:/templates/");
resolver.setSuffix(".html");
resolver.setTemplateMode(TemplateMode.HTML);
resolver.setCacheable(false);
resolver.setCheckExistence(true);
return resolver;
}
@Bean
public ISpringWebFluxTemplateEngine thymeleafTemplateEngine() {
SpringWebFluxTemplateEngine templateEngine = new SpringWebFluxTemplateEngine();
templateEngine.setTemplateResolver(thymeleafTemplateResolver());
return templateEngine;
}
@Bean
public ThymeleafReactiveViewResolver thymeleafChunkedAndDataDrivenViewResolver() {
ThymeleafReactiveViewResolver viewResolver = new ThymeleafReactiveViewResolver();
viewResolver.setTemplateEngine(thymeleafTemplateEngine());
viewResolver.setOrder(1);
viewResolver.setResponseMaxChunkSizeBytes(8192); // OUTPUT BUFFER size limit
return viewResolver;
}
@Override
public void configureViewResolvers(ViewResolverRegistry registry) {
registry.viewResolver(thymeleafChunkedAndDataDrivenViewResolver());
}
}

View File

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

View File

@@ -1,56 +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.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;
/**
* @author Rob Winch
* @since 5.0
*/
@EnableWebFluxSecurity
public class WebfluxFormSecurityConfig {
@Bean
public MapReactiveUserDetailsService userDetailsService() {
UserDetails user = User.withDefaultPasswordEncoder()
.username("user")
.password("password")
.roles("USER")
.build();
return new MapReactiveUserDetailsService(user);
}
@Bean
SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) {
http
.authorizeExchange()
.pathMatchers("/login").permitAll()
.anyExchange().authenticated()
.and()
.httpBasic().and()
.formLogin()
.loginPage("/login");
return http.build();
}
}

View File

@@ -1,12 +0,0 @@
<configuration>
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
</encoder>
</appender>
<root level="WARN">
<appender-ref ref="STDOUT" />
</root>
</configuration>

View File

@@ -1,17 +0,0 @@
<!DOCTYPE html>
<html>
<head>
<title>Secured</title>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
</head>
<body>
<div class="container">
<h1>Secured</h1>
<form th:action="@{/logout}" method="post">
<input id="logout" type="submit" value="Log Out"/>
</form>
</div>
</body>
</html>

View File

@@ -1,32 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<meta name="description" content="">
<meta name="author" content="">
<title>Please Log In</title>
<link href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0-beta/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-/Y6pD6FV/Vv2HJnA6t+vslU6fwYXjCFtcEpHbNJ0lyAFsXTsjBbfaDjzALeQsN6M" crossorigin="anonymous">
<link href="http://getbootstrap.com/docs/4.0/examples/signin/signin.css" rel="stylesheet" crossorigin="anonymous"/>
</head>
<body>
<div class="container">
<form class="form-signin" method="post" th:action="@{/login}">
<h2 class="form-signin-heading">Please Log In</h2>
<div th:if="${param.error}" class="alert alert-danger" role="alert">Invalid
username and password.</div>
<div th:if="${param.logout}" class="alert alert-success" role="alert">You
have been logged out.</div>
<p>
<label for="username" class="sr-only">Username</label>
<input type="text" id="username" name="username" class="form-control" placeholder="Username" required autofocus>
</p>
<p>
<label for="password" class="sr-only">Password</label>
<input type="password" id="password" name="password" class="form-control" placeholder="Password" required>
</p>
<button class="btn btn-lg btn-primary btn-block" type="submit">Sign in</button>
</form>
</div>
</body>
</html>