add reactive/webflux/java folder

This commit is contained in:
Rob Winch
2020-07-29 14:51:50 -05:00
parent 56beccdef9
commit 1077d2c75c
138 changed files with 9 additions and 9 deletions

View File

@@ -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 sample;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
/**
* Index controller.
*
* @author Rob Winch
*/
@Controller
public class IndexController {
@GetMapping("/")
String index() {
return "index";
}
}

View File

@@ -0,0 +1,69 @@
/*
* 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 sample;
import reactor.core.publisher.Mono;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.reactive.function.client.WebClient;
import static org.springframework.security.oauth2.client.web.reactive.function.client.ServerOAuth2AuthorizedClientExchangeFilterFunction.clientRegistrationId;
/**
* A controller that demonstrates how to use WebClient with OAuth.
*
* @author Joe Grandja
* @author Rob Winch
*/
@Controller
@RequestMapping(path = { "/webclient", "/public/webclient" })
public class OAuth2WebClientController {
private final WebClient webClient;
public OAuth2WebClientController(WebClient webClient) {
this.webClient = webClient;
}
@GetMapping("/explicit")
String explicit(Model model) {
// @formatter:off
Mono<String> body = this.webClient
.get()
.attributes(clientRegistrationId("client-id"))
.retrieve()
.bodyToMono(String.class);
// @formatter:on
model.addAttribute("body", body);
return "response";
}
@GetMapping("/implicit")
String implicit(Model model) {
// @formatter:off
Mono<String> body = this.webClient
.get()
.retrieve()
.bodyToMono(String.class);
// @formatter:on
model.addAttribute("body", body);
return "response";
}
}

View File

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

View File

@@ -0,0 +1,71 @@
/*
* 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 sample;
import reactor.core.publisher.Mono;
import org.springframework.security.oauth2.client.OAuth2AuthorizedClient;
import org.springframework.security.oauth2.client.annotation.RegisteredOAuth2AuthorizedClient;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.reactive.function.client.WebClient;
import static org.springframework.security.oauth2.client.web.reactive.function.client.ServerOAuth2AuthorizedClientExchangeFilterFunction.oauth2AuthorizedClient;
/**
* Demonstrates usage of {@link RegisteredOAuth2AuthorizedClient}.
*
* @author Joe Grandja
* @author Rob Winch
*/
@Controller
@RequestMapping(path = { "/annotation", "/public/annotation" })
public class RegisteredOAuth2AuthorizedClientController {
private final WebClient webClient;
public RegisteredOAuth2AuthorizedClientController(WebClient webClient) {
this.webClient = webClient;
}
@GetMapping("/explicit")
String explicit(Model model,
@RegisteredOAuth2AuthorizedClient("client-id") OAuth2AuthorizedClient authorizedClient) {
// @formatter:off
Mono<String> body = this.webClient.get()
.attributes(oauth2AuthorizedClient(authorizedClient))
.retrieve()
.bodyToMono(String.class);
// @formatter:on
model.addAttribute("body", body);
return "response";
}
@GetMapping("/implicit")
String implicit(Model model, @RegisteredOAuth2AuthorizedClient OAuth2AuthorizedClient authorizedClient) {
// @formatter:off
Mono<String> body = this.webClient.get()
.attributes(oauth2AuthorizedClient(authorizedClient))
.retrieve()
.bodyToMono(String.class);
// @formatter:on
model.addAttribute("body", body);
return "response";
}
}

View File

@@ -0,0 +1,63 @@
/*
* 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 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;
import static org.springframework.security.config.Customizer.withDefaults;
/**
* Example of security configuration for oauth client usage.
*
* @author Rob Winch
*/
@EnableWebFluxSecurity
public class SecurityConfiguration {
@Bean
SecurityWebFilterChain configure(ServerHttpSecurity http) {
// @formatter:off
http
.authorizeExchange((exchanges) -> exchanges
.pathMatchers("/", "/public/**").permitAll()
.anyExchange().authenticated()
)
.oauth2Login(withDefaults())
.formLogin(withDefaults())
.oauth2Client(withDefaults());
// @formatter:on
return http.build();
}
@Bean
MapReactiveUserDetailsService userDetailsService() {
// @formatter:off
UserDetails userDetails = User.withDefaultPasswordEncoder()
.username("user")
.password("password")
.roles("USER")
.build();
// @formatter:on
return new MapReactiveUserDetailsService(userDetails);
}
}

View File

@@ -0,0 +1,77 @@
/*
* 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 sample;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.oauth2.client.ReactiveOAuth2AuthorizedClientManager;
import org.springframework.security.oauth2.client.ReactiveOAuth2AuthorizedClientProvider;
import org.springframework.security.oauth2.client.ReactiveOAuth2AuthorizedClientProviderBuilder;
import org.springframework.security.oauth2.client.registration.ReactiveClientRegistrationRepository;
import org.springframework.security.oauth2.client.web.DefaultReactiveOAuth2AuthorizedClientManager;
import org.springframework.security.oauth2.client.web.reactive.function.client.ServerOAuth2AuthorizedClientExchangeFilterFunction;
import org.springframework.security.oauth2.client.web.server.ServerOAuth2AuthorizedClientRepository;
import org.springframework.web.reactive.function.client.WebClient;
/**
* OAuth WebClient configuration.
*
* @author Rob Winch
* @since 5.1
*/
@Configuration
public class WebClientConfiguration {
@Value("${resource-uri}")
String uri;
@Bean
WebClient webClient(ReactiveOAuth2AuthorizedClientManager authorizedClientManager) {
ServerOAuth2AuthorizedClientExchangeFilterFunction oauth = new ServerOAuth2AuthorizedClientExchangeFilterFunction(
authorizedClientManager);
oauth.setDefaultOAuth2AuthorizedClient(true);
// @formatter:off
return WebClient.builder()
.baseUrl(this.uri)
.filter(oauth)
.build();
// @formatter:on
}
@Bean
ReactiveOAuth2AuthorizedClientManager authorizedClientManager(
ReactiveClientRegistrationRepository clientRegistrationRepository,
ServerOAuth2AuthorizedClientRepository authorizedClientRepository) {
// @formatter:off
ReactiveOAuth2AuthorizedClientProvider authorizedClientProvider =
ReactiveOAuth2AuthorizedClientProviderBuilder.builder()
.authorizationCode()
.refreshToken()
.clientCredentials()
.password()
.build();
// @formatter:on
DefaultReactiveOAuth2AuthorizedClientManager authorizedClientManager = new DefaultReactiveOAuth2AuthorizedClientManager(
clientRegistrationRepository, authorizedClientRepository);
authorizedClientManager.setAuthorizedClientProvider(authorizedClientProvider);
return authorizedClientManager;
}
}

View File

@@ -0,0 +1,21 @@
logging:
level:
root: INFO
org.springframework.web: INFO
org.springframework.security: INFO
# org.springframework.boot.autoconfigure: DEBUG
spring:
thymeleaf:
cache: false
security:
oauth2:
client:
registration:
client-id:
client-id: replace-with-client-id
client-secret: replace-with-client-secret
provider: github
scope: read:user,public_repo
resource-uri: https://api.github.com/user/repos

View File

@@ -0,0 +1,51 @@
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:th="https://www.thymeleaf.org" xmlns:sec="https://www.thymeleaf.org/thymeleaf-extras-springsecurity5">
<head>
<title>OAuth2 WebClient Showcase</title>
<meta charset="utf-8" />
</head>
<body>
<a th:href="@{/logout}">Log Out</a>
<h1>Examples</h1>
<h2>@RegisteredOAuth2AuthorizedClient</h2>
<p>
Examples on RegisteredOAuth2AuthorizedClientController
<h3>Authenticated</h3>
<ul>
<li><a th:href="@{/annotation/explicit}">Explicit</a> - Explicitly provide a Client Registration Id</li>
<li>
<a th:href="@{/annotation/implicit}">Implicit</a> - Use the currently logged in user's OAuth Token. This will
only work if the user authenticates with oauth2Login and the token provided is the correct token provided at
log in is authorized.</li>
</ul>
<h3>Public</h3>
<ul>
<li><a th:href="@{/public/annotation/explicit}">Explicit</a> - Explicitly provide a Client Registration Id</li>
<li>
<a th:href="@{/public/annotation/implicit}">Implicit</a> - This will fail if the user is not authenticated.
Since it is mapped to permitAll, it is going to fail unless the user already took an action to log in and then
authenticates with oauth2Login()</li>
</ul>
<h2>ServerOAuth2AuthorizedClientExchangeFilterFunction</h2>
<p>
Examples on OAuth2WebClientController that demonstrate how to use ServerOAuth2AuthorizedClientExchangeFilterFunction
<h3>Authenticated</h3>
<ul>
<li><a th:href="@{/webclient/explicit}">Explicit</a> - Explicitly provide a Client Registration Id</li>
<li>
<a th:href="@{/webclient/implicit}">Implicit</a> - Use the currently logged in user's OAuth Token. This will
only work if the user authenticates with oauth2Login and the token provided is the correct token provided at
log in is authorized.</li>
</ul>
<h3>Public</h3>
<ul>
<li><a th:href="@{/public/webclient/explicit}">Explicit</a> - Explicitly provide a Client Registration Id</li>
<li>
<a th:href="@{/public/webclient/implicit}">Implicit</a> - This will fail if the user is not authenticated.
Since it is mapped to permitAll, it is going to fail unless the user already took an action to log in and then
authenticates with oauth2Login()</li>
</ul>
</body>
</html>

View File

@@ -0,0 +1,31 @@
<!--
~ Copyright 2002-2018 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.
-->
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:th="https://www.thymeleaf.org">
<head>
<title>OAuth2 WebClient Showcase</title>
<meta charset="utf-8" />
</head>
<body>
<a th:href="@{/}">Back</a>
<h1>Response</h1>
<pre><code id="json" class="json" th:text="${body}"></code></pre>
<script>
json.innerHTML = JSON.stringify(JSON.parse(json.innerHTML), null, 4);
</script>
</body>
</html>

View File

@@ -0,0 +1,122 @@
/*
* Copyright 2002-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 sample;
import okhttp3.mockwebserver.MockResponse;
import okhttp3.mockwebserver.MockWebServer;
import org.junit.jupiter.api.AfterAll;
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.autoconfigure.web.reactive.WebFluxTest;
import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.security.oauth2.client.registration.ReactiveClientRegistrationRepository;
import org.springframework.security.oauth2.client.web.server.ServerOAuth2AuthorizedClientRepository;
import org.springframework.security.oauth2.client.web.server.WebSessionServerOAuth2AuthorizedClientRepository;
import org.springframework.test.web.reactive.server.WebTestClient;
import org.springframework.web.reactive.function.client.WebClient;
import static org.springframework.security.test.web.reactive.server.SecurityMockServerConfigurers.mockOAuth2Client;
import static org.springframework.security.test.web.reactive.server.SecurityMockServerConfigurers.mockOAuth2Login;
@WebFluxTest
@Import({ SecurityConfiguration.class, OAuth2WebClientController.class })
@AutoConfigureWebTestClient
public class OAuth2WebClientControllerTests {
private static MockWebServer web = new MockWebServer();
@Autowired
private WebTestClient client;
@MockBean
ReactiveClientRegistrationRepository clientRegistrationRepository;
@AfterAll
static void shutdown() throws Exception {
web.shutdown();
}
@Test
void explicitWhenAuthenticatedThenUsesClientIdRegistration() throws Exception {
web.enqueue(new MockResponse().setBody("body").setResponseCode(200));
// @formatter:off
this.client.mutateWith(mockOAuth2Login())
.mutateWith(mockOAuth2Client("client-id"))
.get()
.uri("/webclient/explicit")
.exchange()
.expectStatus().isOk();
// @formatter:on
}
@Test
void implicitWhenAuthenticatedThenUsesDefaultRegistration() throws Exception {
web.enqueue(new MockResponse().setBody("body").setResponseCode(200));
// @formatter:off
this.client.mutateWith(mockOAuth2Login())
.get()
.uri("/webclient/implicit")
.exchange()
.expectStatus().isOk();
// @formatter:on
}
@Test
void publicExplicitWhenAuthenticatedThenUsesClientIdRegistration() throws Exception {
web.enqueue(new MockResponse().setBody("body").setResponseCode(200));
// @formatter:off
this.client.mutateWith(mockOAuth2Client("client-id"))
.get()
.uri("/public/webclient/explicit")
.exchange()
.expectStatus().isOk();
// @formatter:on
}
@Test
void publicImplicitWhenAuthenticatedThenUsesDefaultRegistration() throws Exception {
web.enqueue(new MockResponse().setBody("body").setResponseCode(200));
// @formatter:off
this.client.mutateWith(mockOAuth2Login())
.get()
.uri("/public/webclient/implicit")
.exchange()
.expectStatus().isOk();
// @formatter:on
}
@Configuration
static class WebClientConfig {
@Bean
WebClient web() {
return WebClient.create(web.url("/").toString());
}
@Bean
ServerOAuth2AuthorizedClientRepository authorizedClientRepository() {
return new WebSessionServerOAuth2AuthorizedClientRepository();
}
}
}

View File

@@ -0,0 +1,46 @@
/*
* Copyright 2002-2018 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package sample;
import org.junit.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.test.web.reactive.server.WebTestClient;
/**
* @author Rob Winch
*/
@SpringBootTest
@AutoConfigureWebTestClient
public class OAuth2WebClientWebFluxApplicationTests {
@Autowired
private WebTestClient client;
@Test
void annotationExplicitWhenNotAuthenticatedThenLoginRequested() {
// @formatter:off
this.client.get()
.uri("/annotation/explicit")
.exchange()
.expectStatus().is3xxRedirection();
// @formatter:on
}
}

View File

@@ -0,0 +1,122 @@
/*
* Copyright 2002-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 sample;
import okhttp3.mockwebserver.MockResponse;
import okhttp3.mockwebserver.MockWebServer;
import org.junit.jupiter.api.AfterAll;
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.autoconfigure.web.reactive.WebFluxTest;
import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.security.oauth2.client.registration.ReactiveClientRegistrationRepository;
import org.springframework.security.oauth2.client.web.server.ServerOAuth2AuthorizedClientRepository;
import org.springframework.security.oauth2.client.web.server.WebSessionServerOAuth2AuthorizedClientRepository;
import org.springframework.test.web.reactive.server.WebTestClient;
import org.springframework.web.reactive.function.client.WebClient;
import static org.springframework.security.test.web.reactive.server.SecurityMockServerConfigurers.mockOAuth2Client;
import static org.springframework.security.test.web.reactive.server.SecurityMockServerConfigurers.mockOAuth2Login;
@WebFluxTest
@Import({ SecurityConfiguration.class, RegisteredOAuth2AuthorizedClientController.class })
@AutoConfigureWebTestClient
public class RegisteredOAuth2AuthorizedClientControllerTests {
private static MockWebServer web = new MockWebServer();
@Autowired
private WebTestClient client;
@MockBean
ReactiveClientRegistrationRepository clientRegistrationRepository;
@AfterAll
static void shutdown() throws Exception {
web.shutdown();
}
@Test
void annotationExplicitWhenAuthenticatedThenUsesClientIdRegistration() throws Exception {
web.enqueue(new MockResponse().setBody("body").setResponseCode(200));
// @formatter:off
this.client.mutateWith(mockOAuth2Login())
.mutateWith(mockOAuth2Client("client-id"))
.get()
.uri("/annotation/explicit")
.exchange()
.expectStatus().isOk();
// @formatter:on
}
@Test
void annotationImplicitWhenAuthenticatedThenUsesDefaultRegistration() throws Exception {
web.enqueue(new MockResponse().setBody("body").setResponseCode(200));
// @formatter:off
this.client.mutateWith(mockOAuth2Login())
.get()
.uri("/annotation/implicit")
.exchange()
.expectStatus().isOk();
// @formatter:on
}
@Test
void publicAnnotationExplicitWhenAuthenticatedThenUsesClientIdRegistration() throws Exception {
web.enqueue(new MockResponse().setBody("body").setResponseCode(200));
// @formatter:off
this.client.mutateWith(mockOAuth2Client("client-id"))
.get()
.uri("/public/annotation/explicit")
.exchange()
.expectStatus().isOk();
// @formatter:on
}
@Test
void publicAnnotationImplicitWhenAuthenticatedThenUsesDefaultRegistration() throws Exception {
web.enqueue(new MockResponse().setBody("body").setResponseCode(200));
// @formatter:off
this.client.mutateWith(mockOAuth2Login())
.get()
.uri("/public/annotation/implicit")
.exchange()
.expectStatus().isOk();
// @formatter:on
}
@Configuration
static class WebClientConfig {
@Bean
WebClient web() {
return WebClient.create(web.url("/").toString());
}
@Bean
ServerOAuth2AuthorizedClientRepository authorizedClientRepository() {
return new WebSessionServerOAuth2AuthorizedClientRepository();
}
}
}