View Javadoc
1   /*
2    * Copyright 2024-2026 the original author or authors.
3    *
4    * Licensed under the Apache License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    *
8    *      http://www.apache.org/licenses/LICENSE-2.0
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   */
16  
17  package org.bremersee.spring.boot.autoconfigure.security.authentication;
18  
19  import static java.util.Objects.nonNull;
20  import static org.springframework.util.ObjectUtils.isEmpty;
21  
22  import org.apache.commons.logging.Log;
23  import org.apache.commons.logging.LogFactory;
24  import org.bremersee.ldaptive.LdaptiveTemplate;
25  import org.bremersee.spring.boot.autoconfigure.ldaptive.LdaptiveAutoConfiguration;
26  import org.bremersee.spring.security.core.EmailToUsernameResolver;
27  import org.bremersee.spring.security.core.mapping.authority.NormalizedGrantedAuthoritiesMapper;
28  import org.bremersee.spring.security.ldaptive.authentication.AccountControlEvaluator;
29  import org.bremersee.spring.security.ldaptive.authentication.LdaptiveAuthentication;
30  import org.bremersee.spring.security.ldaptive.authentication.LdaptiveAuthenticationManager;
31  import org.bremersee.spring.security.ldaptive.authentication.LdaptiveAuthenticationProperties;
32  import org.bremersee.spring.security.ldaptive.authentication.ReactiveLdaptiveAuthenticationManager;
33  import org.bremersee.spring.security.ldaptive.userdetails.LdaptiveEvaluatedRememberMeTokenProvider;
34  import org.bremersee.spring.security.ldaptive.userdetails.LdaptivePwdLastSetRememberMeTokenProvider;
35  import org.bremersee.spring.security.ldaptive.userdetails.LdaptiveRememberMeTokenProvider;
36  import org.bremersee.spring.security.ldaptive.userdetails.LdaptiveUserDetails;
37  import org.ldaptive.ConnectionConfig;
38  import org.ldaptive.ConnectionFactory;
39  import org.ldaptive.DefaultConnectionFactory;
40  import org.mapstruct.Mapper;
41  import org.mapstruct.NullValueCheckStrategy;
42  import org.mapstruct.factory.Mappers;
43  import org.springframework.beans.factory.ObjectProvider;
44  import org.springframework.boot.autoconfigure.AutoConfiguration;
45  import org.springframework.boot.autoconfigure.AutoConfigureAfter;
46  import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
47  import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
48  import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
49  import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
50  import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;
51  import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication.Type;
52  import org.springframework.boot.context.event.ApplicationReadyEvent;
53  import org.springframework.boot.context.properties.EnableConfigurationProperties;
54  import org.springframework.context.annotation.Bean;
55  import org.springframework.context.event.EventListener;
56  import org.springframework.core.convert.converter.Converter;
57  import org.springframework.security.core.authority.mapping.GrantedAuthoritiesMapper;
58  import org.springframework.util.ClassUtils;
59  
60  /**
61   * The ldaptive authentication autoconfiguration.
62   *
63   * @author Christian Bremer
64   */
65  @AutoConfiguration
66  @ConditionalOnClass(name = {
67      "org.ldaptive.ConnectionFactory",
68      "org.bremersee.ldaptive.LdaptiveTemplate",
69      "org.bremersee.spring.security.ldaptive.authentication.LdaptiveAuthenticationManager"
70  })
71  @ConditionalOnBean({ConnectionConfig.class})
72  @ConditionalOnProperty(prefix = "bremersee.authentication.ldaptive", name = "user-base-dn")
73  @AutoConfigureAfter({LdaptiveAutoConfiguration.class})
74  @EnableConfigurationProperties(AuthenticationProperties.class)
75  public class LdaptiveAuthenticationAutoConfiguration {
76  
77    private static final Log log = LogFactory.getLog(LdaptiveAuthenticationAutoConfiguration.class);
78  
79    private final LdaptiveAuthenticationProperties properties;
80  
81    private final String rememberMeKey;
82  
83    /**
84     * Instantiates a new ldaptive authentication autoconfiguration.
85     *
86     * @param properties the properties
87     */
88    public LdaptiveAuthenticationAutoConfiguration(AuthenticationProperties properties) {
89      this.properties = LdaptivePropertiesMapper.map(properties);
90      this.rememberMeKey = properties.getRememberMe().getKey();
91    }
92  
93    /**
94     * Init.
95     */
96    @EventListener(ApplicationReadyEvent.class)
97    public void init() {
98      log.info(String.format("""
99              
100             *********************************************************************************
101             * %s
102             * properties = %s
103             *********************************************************************************""",
104         ClassUtils.getUserClass(getClass()).getSimpleName(),
105         properties));
106   }
107 
108   /**
109    * Creates ldaptive password encoder provider.
110    *
111    * @return the ldaptive password encoder provider
112    */
113   @ConditionalOnMissingBean
114   @Bean
115   public LdaptivePasswordEncoderProvider ldaptivePasswordEncoderProvider() {
116     return LdaptivePasswordEncoderProvider.defaultProvider();
117   }
118 
119   /**
120    * The ldaptive remember-me token provider.
121    *
122    * @param accountControlEvaluator the account control evaluator
123    * @return the ldaptive remember-me token provider
124    */
125   @ConditionalOnMissingBean
126   @Bean
127   public LdaptiveRememberMeTokenProvider ldaptiveRememberMeTokenProvider(
128       ObjectProvider<AccountControlEvaluator> accountControlEvaluator) {
129 
130     AccountControlEvaluator evaluator = accountControlEvaluator
131         .getIfAvailable(() -> properties.getAccountControlEvaluator().get());
132     if (!isEmpty(properties.getPasswordLastSetAttribute())) {
133       return new LdaptivePwdLastSetRememberMeTokenProvider(
134           evaluator, properties.getPasswordLastSetAttribute());
135     }
136     return new LdaptiveEvaluatedRememberMeTokenProvider(evaluator);
137   }
138 
139   /**
140    * Creates ldaptive authentication manager.
141    *
142    * @param connectionConfig the connection config
143    * @param connectionFactoryProvider the connection factory provider
144    * @param ldaptiveTemplateProvider the ldaptive template provider
145    * @param ldaptivePasswordEncoderProvider the ldaptive password encoder provider
146    * @param ldaptiveRememberMeTokenProvider the ldaptive remember-me token provider
147    * @param emailToUsernameResolver the email to username resolver
148    * @param accountControlEvaluator the account control evaluator
149    * @param grantedAuthoritiesMapper the groups mapper
150    * @param tokenConverter the token converter
151    * @return the ldaptive authentication manager
152    */
153   @ConditionalOnWebApplication(type = Type.SERVLET)
154   @Bean(initMethod = "init")
155   public LdaptiveAuthenticationManager ldaptiveAuthenticationManager(
156       ConnectionConfig connectionConfig,
157       ObjectProvider<ConnectionFactory> connectionFactoryProvider,
158       ObjectProvider<LdaptiveTemplate> ldaptiveTemplateProvider,
159       LdaptivePasswordEncoderProvider ldaptivePasswordEncoderProvider,
160       LdaptiveRememberMeTokenProvider ldaptiveRememberMeTokenProvider,
161       ObjectProvider<EmailToUsernameResolver> emailToUsernameResolver,
162       ObjectProvider<AccountControlEvaluator> accountControlEvaluator,
163       ObjectProvider<GrantedAuthoritiesMapper> grantedAuthoritiesMapper,
164       ObjectProvider<Converter<LdaptiveUserDetails, LdaptiveAuthentication>> tokenConverter) {
165 
166     LdaptiveAuthenticationManager manager = new LdaptiveAuthenticationManager(
167         getLdaptiveTemplate(connectionConfig, connectionFactoryProvider, ldaptiveTemplateProvider),
168         properties,
169         rememberMeKey);
170     manager.setPasswordEncoder(ldaptivePasswordEncoderProvider.get());
171     manager.setPasswordProvider(ldaptiveRememberMeTokenProvider);
172     emailToUsernameResolver.ifAvailable(manager::setEmailToUsernameResolver);
173     accountControlEvaluator.ifAvailable(manager::setAccountControlEvaluator);
174     manager.setGrantedAuthoritiesMapper(getGrantedAuthoritiesMapper(grantedAuthoritiesMapper));
175     tokenConverter.ifAvailable(manager::setTokenConverter);
176     return manager;
177   }
178 
179   /**
180    * Creates reactive ldaptive authentication manager.
181    *
182    * @param connectionConfig the connection config
183    * @param connectionFactoryProvider the connection factory provider
184    * @param ldaptiveTemplateProvider the ldaptive template provider
185    * @param ldaptivePasswordEncoderProvider the ldaptive password encoder provider
186    * @param ldaptiveRememberMeTokenProvider the ldaptive remember-me token provider
187    * @param emailToUsernameResolver the email to username resolver
188    * @param accountControlEvaluator the account control evaluator
189    * @param grantedAuthoritiesMapper the groups mapper
190    * @param tokenConverter the token converter
191    * @return the reactive ldaptive authentication manager
192    */
193   @ConditionalOnWebApplication(type = Type.REACTIVE)
194   @Bean
195   public ReactiveLdaptiveAuthenticationManager reactiveLdaptiveAuthenticationManager(
196       ConnectionConfig connectionConfig,
197       ObjectProvider<ConnectionFactory> connectionFactoryProvider,
198       ObjectProvider<LdaptiveTemplate> ldaptiveTemplateProvider,
199       LdaptivePasswordEncoderProvider ldaptivePasswordEncoderProvider,
200       LdaptiveRememberMeTokenProvider ldaptiveRememberMeTokenProvider,
201       ObjectProvider<EmailToUsernameResolver> emailToUsernameResolver,
202       ObjectProvider<AccountControlEvaluator> accountControlEvaluator,
203       ObjectProvider<GrantedAuthoritiesMapper> grantedAuthoritiesMapper,
204       ObjectProvider<Converter<LdaptiveUserDetails, LdaptiveAuthentication>> tokenConverter) {
205     return new ReactiveLdaptiveAuthenticationManager(
206         ldaptiveAuthenticationManager(
207             connectionConfig,
208             connectionFactoryProvider,
209             ldaptiveTemplateProvider,
210             ldaptivePasswordEncoderProvider,
211             ldaptiveRememberMeTokenProvider,
212             emailToUsernameResolver,
213             accountControlEvaluator,
214             grantedAuthoritiesMapper,
215             tokenConverter));
216   }
217 
218   private LdaptiveTemplate getLdaptiveTemplate(
219       ConnectionConfig connectionConfig,
220       ObjectProvider<ConnectionFactory> connectionFactoryProvider,
221       ObjectProvider<LdaptiveTemplate> ldaptiveTemplateProvider) {
222 
223     LdaptiveTemplate ldaptiveTemplate = ldaptiveTemplateProvider.getIfAvailable();
224     if (nonNull(ldaptiveTemplate)) {
225       return ldaptiveTemplate;
226     }
227     ConnectionFactory connectionFactory = connectionFactoryProvider.getIfAvailable();
228     if (nonNull(connectionFactory)) {
229       return new LdaptiveTemplate(connectionFactory);
230     }
231     return new LdaptiveTemplate(new DefaultConnectionFactory(connectionConfig));
232   }
233 
234   private GrantedAuthoritiesMapper getGrantedAuthoritiesMapper(
235       ObjectProvider<GrantedAuthoritiesMapper> grantedAuthoritiesMapper) {
236     return grantedAuthoritiesMapper.getIfAvailable(() -> new NormalizedGrantedAuthoritiesMapper(
237         properties.getDefaultRoles(),
238         properties.toRoleMappings(),
239         properties.getRolePrefix(),
240         properties.getRoleCaseTransformation(),
241         properties.toRoleStringReplacements()));
242   }
243 
244   /**
245    * The interface Properties mapper.
246    */
247   @Mapper(nullValueCheckStrategy = NullValueCheckStrategy.ALWAYS)
248   interface PropertiesMapper {
249 
250     /**
251      * The constant INSTANCE.
252      */
253     PropertiesMapper INSTANCE = Mappers.getMapper(PropertiesMapper.class);
254 
255     /**
256      * Map ldaptive authentication properties.
257      *
258      * @param source the source
259      * @return the ldaptive authentication properties
260      */
261     LdaptiveAuthenticationProperties map(AuthenticationProperties.LdaptiveProperties source);
262 
263   }
264 
265 }