8 changed files with 478 additions and 0 deletions
@ -0,0 +1,17 @@
@@ -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' |
||||
} |
||||
@ -0,0 +1,105 @@
@@ -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())); |
||||
} |
||||
} |
||||
@ -0,0 +1,55 @@
@@ -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(); |
||||
} |
||||
} |
||||
@ -0,0 +1,35 @@
@@ -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!"); |
||||
} |
||||
} |
||||
@ -0,0 +1,39 @@
@@ -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(); |
||||
} |
||||
} |
||||
@ -0,0 +1,59 @@
@@ -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); |
||||
} |
||||
|
||||
} |
||||
@ -0,0 +1,104 @@
@@ -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())); |
||||
} |
||||
} |
||||
@ -0,0 +1,64 @@
@@ -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(); |
||||
} |
||||
} |
||||
Loading…
Reference in new issue