1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 package org.bremersee.spring.boot.autoconfigure.keycloak;
18
19 import java.util.ArrayList;
20 import java.util.List;
21 import org.apache.commons.logging.Log;
22 import org.apache.commons.logging.LogFactory;
23 import org.bremersee.exception.feign.FeignClientExceptionErrorDecoder;
24 import org.bremersee.exception.webclient.DefaultWebClientErrorDecoder;
25 import org.bremersee.keycloak.api.webflux.AdminApi;
26 import org.bremersee.keycloak.api.webflux.AdminApiMock;
27 import org.bremersee.keycloak.api.webflux.KeycloakAdminClient;
28 import org.bremersee.keycloak.api.webflux.KeycloakClientFactory;
29 import org.springframework.beans.factory.ObjectProvider;
30 import org.springframework.boot.autoconfigure.AutoConfiguration;
31 import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
32 import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
33 import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;
34 import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication.Type;
35 import org.springframework.boot.context.event.ApplicationReadyEvent;
36 import org.springframework.boot.context.properties.EnableConfigurationProperties;
37 import org.springframework.context.annotation.Bean;
38 import org.springframework.context.event.EventListener;
39 import org.springframework.http.HttpStatusCode;
40 import org.springframework.util.Assert;
41 import org.springframework.util.ClassUtils;
42 import reactivefeign.client.statushandler.ReactiveStatusHandlers;
43 import reactivefeign.webclient.WebClientFeignCustomizer;
44
45
46
47
48
49
50 @ConditionalOnWebApplication(type = Type.REACTIVE)
51 @EnableConfigurationProperties(KeycloakProperties.class)
52 @ConditionalOnClass(name = {"org.bremersee.keycloak.api.webflux.KeycloakClientFactory"})
53 @AutoConfiguration
54 public class ReactiveKeycloakClientAutoConfiguration {
55
56 private static final Log log = LogFactory.getLog(ReactiveKeycloakClientAutoConfiguration.class);
57
58 private final KeycloakProperties properties;
59
60
61
62
63
64
65 public ReactiveKeycloakClientAutoConfiguration(KeycloakProperties properties) {
66 this.properties = properties;
67 }
68
69
70
71
72 @EventListener(ApplicationReadyEvent.class)
73 public void init() {
74 log.info(String.format("""
75
76 *********************************************************************************
77 * %s
78 *********************************************************************************
79 * properties = %s
80 *********************************************************************************""",
81 ClassUtils.getUserClass(getClass()).getSimpleName(), properties));
82 if (properties.getAdminClient().isEnabled()) {
83 Assert.hasText(properties.getBaseUri(), "Keycloak Base URI is required.");
84 Assert.hasText(properties.getAdminClient().getLoginRealm(),
85 "Keycloak Login Realm is required.");
86 Assert.hasText(properties.getAdminClient().getClientId(), "Keycloak Client ID is required.");
87 Assert.hasText(properties.getAdminClient().getUsername(),
88 "Keycloak Client Username is required.");
89 Assert.hasText(properties.getAdminClient().getPassword(),
90 "Keycloak Client Password is required.");
91 }
92 }
93
94
95
96
97
98
99
100
101
102
103 @ConditionalOnMissingBean
104 @Bean
105 public AdminApi keycloakAdminApi(
106 List<WebClientFeignCustomizer> webClientCustomizers,
107 List<KeycloakAdminApiCustomizer> keycloakAdminApiCustomizers,
108 ObjectProvider<DefaultWebClientErrorDecoder> errorDecoderProvider,
109 ObjectProvider<FeignClientExceptionErrorDecoder> feignErrorDecoderProvider) {
110
111 if (!properties.getAdminClient().isEnabled()) {
112 log.warn("Keycloak Admin Client is disabled. Creating admin api MOCK!");
113 return new AdminApiMock();
114 }
115
116 List<WebClientFeignCustomizer> webClientExtendedCustomizers = new ArrayList<>();
117 errorDecoderProvider.ifAvailable(decoder -> webClientExtendedCustomizers
118 .add(builder -> builder.defaultStatusHandler(HttpStatusCode::isError, decoder)));
119 webClientExtendedCustomizers.addAll(webClientCustomizers);
120
121 List<KeycloakAdminApiCustomizer> keycloakAdminApiExtendedCustomizers = new ArrayList<>();
122 feignErrorDecoderProvider.ifAvailable(decoder -> keycloakAdminApiExtendedCustomizers
123 .add(builder -> builder.statusHandler(ReactiveStatusHandlers.errorDecoder(decoder))));
124 keycloakAdminApiExtendedCustomizers.addAll(keycloakAdminApiCustomizers);
125
126 KeycloakClientFactory factory = createKeycloakClientFactory();
127
128 factory.setWebClientBuilderCustomizer(builder -> webClientExtendedCustomizers
129 .forEach(c -> c.accept(builder)));
130 factory.setAdminApiCustomizer(builder -> keycloakAdminApiExtendedCustomizers
131 .forEach(c -> c.customize(builder)));
132
133 return factory.newClient();
134 }
135
136 private KeycloakClientFactory createKeycloakClientFactory() {
137 return new KeycloakClientFactory(
138 properties.getBaseUri(),
139 properties.getAdminClient().getLoginRealm(),
140 properties.getAdminClient().getClientId(),
141 properties.getAdminClient().getUsername(),
142 properties.getAdminClient().getPassword());
143 }
144
145
146
147
148
149
150
151 @ConditionalOnMissingBean
152 @Bean
153 public KeycloakAdminClient keycloakAdminClient(AdminApi adminApi) {
154 return new KeycloakAdminClient(adminApi);
155 }
156
157 }