Skip to content

Commit

Permalink
Auto-configured RestClient and WebClient instances as named beans
Browse files Browse the repository at this point in the history
  • Loading branch information
ch4mpy committed Oct 31, 2024
1 parent 1789b5a commit bc46caa
Show file tree
Hide file tree
Showing 64 changed files with 2,108 additions and 1,575 deletions.
6 changes: 3 additions & 3 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,11 @@
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.4.0-M3</version>
<version>3.4.0-RC1</version>
</parent>
<groupId>com.c4-soft.springaddons</groupId>
<artifactId>spring-addons</artifactId>
<version>7.9.0-M5</version>
<version>7.9.0-M6-SNAPSHOT</version>
<packaging>pom</packaging>
<name>spring-addons</name>
<description>Set of tools I find useful to work with Spring (mostly
Expand All @@ -35,7 +35,7 @@
<connection>scm:git:git://github.com/ch4mpy/spring-addons.git</connection>
<developerConnection>scm:git:git@github.com:ch4mpy/spring-addons.git</developerConnection>
<url>https://github.com/ch4mpy/spring-addons</url>
<tag>spring-addons-7.9.0-M5</tag>
<tag>spring-addons-7.8.8</tag>
</scm>

<distributionManagement>
Expand Down
4 changes: 2 additions & 2 deletions samples/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
<parent>
<groupId>com.c4-soft.springaddons</groupId>
<artifactId>spring-addons</artifactId>
<version>7.9.0-M5</version>
<version>7.9.0-M6-SNAPSHOT</version>
<relativePath>..</relativePath>
</parent>
<groupId>com.c4-soft.springaddons.samples</groupId>
Expand All @@ -13,7 +13,7 @@

<properties>
<java.version>17</java.version>
<spring-cloud.version>2024.0.0-M1</spring-cloud.version>
<spring-cloud.version>2024.0.0-M2</spring-cloud.version>

<!-- OpenAPI -->
<io.swagger.core.v3.version>2.2.19</io.swagger.core.v3.version>
Expand Down
2 changes: 1 addition & 1 deletion samples/tutorials/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
<parent>
<groupId>com.c4-soft.springaddons.samples</groupId>
<artifactId>spring-addons-samples</artifactId>
<version>7.9.0-M5</version>
<version>7.9.0-M6-SNAPSHOT</version>
<relativePath>..</relativePath>
</parent>
<groupId>com.c4-soft.springaddons.samples.tutorials</groupId>
Expand Down
2 changes: 1 addition & 1 deletion samples/tutorials/reactive-client/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
<parent>
<groupId>com.c4-soft.springaddons.samples.tutorials</groupId>
<artifactId>tutorials</artifactId>
<version>7.9.0-M5</version>
<version>7.9.0-M6-SNAPSHOT</version>
<relativePath>..</relativePath>
</parent>
<artifactId>reactive-client</artifactId>
Expand Down
2 changes: 1 addition & 1 deletion samples/tutorials/reactive-resource-server/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
<parent>
<groupId>com.c4-soft.springaddons.samples.tutorials</groupId>
<artifactId>tutorials</artifactId>
<version>7.9.0-M5</version>
<version>7.9.0-M6-SNAPSHOT</version>
<relativePath>..</relativePath>
</parent>
<artifactId>reactive-resource-server</artifactId>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
<parent>
<groupId>com.c4-soft.springaddons.samples.tutorials</groupId>
<artifactId>tutorials</artifactId>
<version>7.9.0-M5</version>
<version>7.9.0-M6-SNAPSHOT</version>
<relativePath>..</relativePath>
</parent>
<artifactId>resource-server_multitenant_dynamic</artifactId>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
<parent>
<groupId>com.c4-soft.springaddons.samples.tutorials</groupId>
<artifactId>tutorials</artifactId>
<version>7.9.0-M5</version>
<version>7.9.0-M6-SNAPSHOT</version>
<relativePath>..</relativePath>
</parent>
<artifactId>resource-server_with_additional-header</artifactId>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
<parent>
<groupId>com.c4-soft.springaddons.samples.tutorials</groupId>
<artifactId>tutorials</artifactId>
<version>7.9.0-M5</version>
<version>7.9.0-M6-SNAPSHOT</version>
<relativePath>..</relativePath>
</parent>
<artifactId>resource-server_with_introspection</artifactId>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
<parent>
<groupId>com.c4-soft.springaddons.samples.tutorials</groupId>
<artifactId>tutorials</artifactId>
<version>7.9.0-M5</version>
<version>7.9.0-M6-SNAPSHOT</version>
<relativePath>..</relativePath>
</parent>
<artifactId>resource-server_with_oauthentication</artifactId>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
<parent>
<groupId>com.c4-soft.springaddons.samples.tutorials</groupId>
<artifactId>tutorials</artifactId>
<version>7.9.0-M5</version>
<version>7.9.0-M6-SNAPSHOT</version>
<relativePath>..</relativePath>
</parent>
<artifactId>resource-server_with_specialized_oauthentication</artifactId>
Expand Down
6 changes: 1 addition & 5 deletions samples/tutorials/resource-server_with_ui/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
<parent>
<groupId>com.c4-soft.springaddons.samples.tutorials</groupId>
<artifactId>tutorials</artifactId>
<version>7.9.0-M5</version>
<version>7.9.0-M6-SNAPSHOT</version>
<relativePath>..</relativePath>
</parent>
<artifactId>resource-server_with_ui</artifactId>
Expand Down Expand Up @@ -42,10 +42,6 @@
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-webflux</artifactId>
</dependency>

<!-- tools -->
<dependency>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,15 @@

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import com.c4_soft.springaddons.rest.SpringAddonsRestClientSupport;
import org.springframework.web.client.RestClient;
import com.c4_soft.springaddons.rest.RestClientHttpExchangeProxyFactoryBean;

@Configuration
public class RestClientsConfig {

@Bean
GreetApi greetApi(SpringAddonsRestClientSupport restSupport) {
// binds to com.c4-soft.springaddons.rest.client.greet-api properties
return restSupport.service("greet-api", GreetApi.class);
}
@Bean
GreetApi greetApi(RestClient greetClient) throws Exception {
return new RestClientHttpExchangeProxyFactoryBean<>(GreetApi.class, greetClient).getObject();
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@
import java.util.ArrayList;
import java.util.Optional;
import java.util.stream.StreamSupport;

import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
Expand All @@ -23,11 +22,9 @@
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.servlet.view.RedirectView;
import org.springframework.web.util.UriComponentsBuilder;

import com.c4_soft.springaddons.security.oidc.starter.LogoutRequestUriBuilder;
import com.c4_soft.springaddons.security.oidc.starter.properties.SpringAddonsOidcProperties;
import com.c4_soft.springaddons.security.oidc.starter.synchronised.client.MultiTenantOAuth2PrincipalSupport;

import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.AllArgsConstructor;
Expand All @@ -41,111 +38,115 @@
@RequiredArgsConstructor
@Slf4j
public class UiController {
private final GreetApi api;
private final InMemoryClientRegistrationRepository clientRegistrationRepository;
private final OAuth2AuthorizedClientRepository authorizedClientRepo;
private final SpringAddonsOidcProperties addonsClientProps;
private final LogoutRequestUriBuilder logoutRequestUriBuilder;
private final GreetApi greetApi;
private final InMemoryClientRegistrationRepository clientRegistrationRepository;
private final OAuth2AuthorizedClientRepository authorizedClientRepo;
private final SpringAddonsOidcProperties addonsClientProps;
private final LogoutRequestUriBuilder logoutRequestUriBuilder;

@GetMapping("/")
public String getIndex(Model model, Authentication auth) {
model.addAttribute("isAuthenticated", auth != null && auth.isAuthenticated());
return "index";
}
@GetMapping("/")
public String getIndex(Model model, Authentication auth) {
model.addAttribute("isAuthenticated", auth != null && auth.isAuthenticated());
return "index";
}

@GetMapping("/greet")
@PreAuthorize("isAuthenticated()")
public String getGreeting(HttpServletRequest request, Authentication auth, Model model) throws URISyntaxException {
final var unauthorizedClients = new ArrayList<UnauthorizedClientDto>();
final var authorizedClients = new ArrayList<AuthorizedClientDto>();
StreamSupport
.stream(this.clientRegistrationRepository.spliterator(), false)
.filter(registration -> AuthorizationGrantType.AUTHORIZATION_CODE.equals(registration.getAuthorizationGrantType()))
.forEach(registration -> {
final var authorizedClient = auth == null ? null : authorizedClientRepo.loadAuthorizedClient(registration.getRegistrationId(), auth, request);
if (authorizedClient == null) {
unauthorizedClients.add(new UnauthorizedClientDto(registration.getClientName(), registration.getRegistrationId()));
} else {
SecurityContextHolder
.getContext()
.setAuthentication(
MultiTenantOAuth2PrincipalSupport.getAuthentication(request.getSession(), registration.getRegistrationId()).orElse(auth));
final var greeting = api.getGreeting();
@GetMapping("/greet")
@PreAuthorize("isAuthenticated()")
public String getGreeting(HttpServletRequest request, Authentication auth, Model model)
throws URISyntaxException {
final var unauthorizedClients = new ArrayList<UnauthorizedClientDto>();
final var authorizedClients = new ArrayList<AuthorizedClientDto>();
StreamSupport.stream(this.clientRegistrationRepository.spliterator(), false)
.filter(registration -> AuthorizationGrantType.AUTHORIZATION_CODE
.equals(registration.getAuthorizationGrantType()))
.forEach(registration -> {
final var authorizedClient = auth == null ? null
: authorizedClientRepo.loadAuthorizedClient(registration.getRegistrationId(), auth,
request);
if (authorizedClient == null) {
unauthorizedClients.add(new UnauthorizedClientDto(registration.getClientName(),
registration.getRegistrationId()));
} else {
SecurityContextHolder.getContext()
.setAuthentication(MultiTenantOAuth2PrincipalSupport
.getAuthentication(request.getSession(), registration.getRegistrationId())
.orElse(auth));
final var greeting = greetApi.getGreeting();

authorizedClients
.add(
new AuthorizedClientDto(
registration.getClientName(),
greeting,
"/ui/logout-idp?clientRegistrationId=%s".formatted(registration.getRegistrationId())));
}
});
model.addAttribute("unauthorizedClients", unauthorizedClients);
model.addAttribute("authorizedClients", authorizedClients);
return "greet";
}
authorizedClients.add(new AuthorizedClientDto(registration.getClientName(), greeting,
"/ui/logout-idp?clientRegistrationId=%s"
.formatted(registration.getRegistrationId())));
}
});
model.addAttribute("unauthorizedClients", unauthorizedClients);
model.addAttribute("authorizedClients", authorizedClients);
return "greet";
}

@PostMapping("/logout-idp")
@PreAuthorize("isAuthenticated()")
public RedirectView logout(
@RequestParam("clientRegistrationId") String clientRegistrationId,
@RequestParam(name = "redirectTo", required = false) Optional<String> redirectTo,
HttpServletRequest request,
HttpServletResponse response) {
final var postLogoutUri = UriComponentsBuilder
.fromUri(addonsClientProps.getClient().getClientUri())
.path(redirectTo.orElse("/ui/greet"))
.encode(StandardCharsets.UTF_8)
.build()
.toUriString();
final var authentication = MultiTenantOAuth2PrincipalSupport.getAuthentication(request.getSession(), clientRegistrationId).orElse(null);
final var authorizedClient = authorizedClientRepo.loadAuthorizedClient(clientRegistrationId, authentication, request);
final var idToken = authentication.getPrincipal() instanceof OidcUser oidcUser ? oidcUser.getIdToken().getTokenValue() : null;
String logoutUri = logoutRequestUriBuilder
.getLogoutRequestUri(authorizedClient.getClientRegistration(), idToken, Optional.of(URI.create(postLogoutUri)))
.orElse("");
@PostMapping("/logout-idp")
@PreAuthorize("isAuthenticated()")
public RedirectView logout(@RequestParam("clientRegistrationId") String clientRegistrationId,
@RequestParam(name = "redirectTo", required = false) Optional<String> redirectTo,
HttpServletRequest request, HttpServletResponse response) {
final var postLogoutUri = UriComponentsBuilder
.fromUri(addonsClientProps.getClient().getClientUri()).path(redirectTo.orElse("/ui/greet"))
.encode(StandardCharsets.UTF_8).build().toUriString();
final var authentication = MultiTenantOAuth2PrincipalSupport
.getAuthentication(request.getSession(), clientRegistrationId).orElse(null);
final var authorizedClient =
authorizedClientRepo.loadAuthorizedClient(clientRegistrationId, authentication, request);
final var idToken = authentication.getPrincipal() instanceof OidcUser oidcUser
? oidcUser.getIdToken().getTokenValue()
: null;
String logoutUri =
logoutRequestUriBuilder.getLogoutRequestUri(authorizedClient.getClientRegistration(),
idToken, Optional.of(URI.create(postLogoutUri))).orElse("");

log.info("Remove authorized client with ID {} for {}", clientRegistrationId, authentication.getName());
this.authorizedClientRepo.removeAuthorizedClient(clientRegistrationId, authentication, request, response);
final var authorizedClientIds = MultiTenantOAuth2PrincipalSupport.getAuthenticationsByClientRegistrationId(request.getSession());
if (authorizedClientIds.isEmpty()) {
request.getSession().invalidate();
}

log.info("Redirecting {} to {} for logout", authentication.getName(), logoutUri);
return new RedirectView(logoutUri);
log.info("Remove authorized client with ID {} for {}", clientRegistrationId,
authentication.getName());
this.authorizedClientRepo.removeAuthorizedClient(clientRegistrationId, authentication, request,
response);
final var authorizedClientIds = MultiTenantOAuth2PrincipalSupport
.getAuthenticationsByClientRegistrationId(request.getSession());
if (authorizedClientIds.isEmpty()) {
request.getSession().invalidate();
}

@PostMapping("/bulk-logout-idps")
@PreAuthorize("isAuthenticated()")
public RedirectView bulkLogout(HttpServletRequest request) {
final var authorizedClientIds = MultiTenantOAuth2PrincipalSupport.getAuthenticationsByClientRegistrationId(request.getSession()).entrySet().iterator();
if (authorizedClientIds.hasNext()) {
final var id = authorizedClientIds.next();
final var builder = UriComponentsBuilder.fromPath("/ui/logout-idp");
builder.queryParam("clientRegistrationId", id.getKey());
builder.queryParam("redirectTo", "/ui/bulk-logout-idps");
return new RedirectView(builder.encode(StandardCharsets.UTF_8).build().toUriString());
}
return new RedirectView(addonsClientProps.getClient().getPostLogoutRedirectUri().toString());
log.info("Redirecting {} to {} for logout", authentication.getName(), logoutUri);
return new RedirectView(logoutUri);
}

@PostMapping("/bulk-logout-idps")
@PreAuthorize("isAuthenticated()")
public RedirectView bulkLogout(HttpServletRequest request) {
final var authorizedClientIds = MultiTenantOAuth2PrincipalSupport
.getAuthenticationsByClientRegistrationId(request.getSession()).entrySet().iterator();
if (authorizedClientIds.hasNext()) {
final var id = authorizedClientIds.next();
final var builder = UriComponentsBuilder.fromPath("/ui/logout-idp");
builder.queryParam("clientRegistrationId", id.getKey());
builder.queryParam("redirectTo", "/ui/bulk-logout-idps");
return new RedirectView(builder.encode(StandardCharsets.UTF_8).build().toUriString());
}
return new RedirectView(addonsClientProps.getClient().getPostLogoutRedirectUri().toString());
}

@Data
@NoArgsConstructor
@AllArgsConstructor
public static class AuthorizedClientDto implements Serializable {
private static final long serialVersionUID = -6623594577844506618L;
@Data
@NoArgsConstructor
@AllArgsConstructor
public static class AuthorizedClientDto implements Serializable {
private static final long serialVersionUID = -6623594577844506618L;

private String label;
private String message;
private String logoutUri;
}
private String label;
private String message;
private String logoutUri;
}

@Data
@NoArgsConstructor
@AllArgsConstructor
public static class UnauthorizedClientDto {
private String label;
private String registrationId;
}
@Data
@NoArgsConstructor
@AllArgsConstructor
public static class UnauthorizedClientDto {
private String label;
private String registrationId;
}
}
Loading

0 comments on commit bc46caa

Please sign in to comment.