View Javadoc
1   /*
2    * Copyright 2021 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.security.authentication;
18  
19  import lombok.extern.slf4j.Slf4j;
20  import org.bremersee.data.ldaptive.LdaptiveAutoConfiguration;
21  import org.bremersee.data.ldaptive.LdaptiveOperations;
22  import org.bremersee.data.ldaptive.LdaptiveProperties;
23  import org.bremersee.data.ldaptive.LdaptiveProperties.UserDetailsProperties;
24  import org.bremersee.data.ldaptive.reactive.ReactiveLdaptiveOperations;
25  import org.bremersee.security.core.userdetails.LdaptivePasswordEncoder;
26  import org.bremersee.security.core.userdetails.LdaptivePasswordMatcher;
27  import org.bremersee.security.core.userdetails.LdaptiveUserDetailsService;
28  import org.bremersee.security.core.userdetails.ReactiveLdaptiveUserDetailsService;
29  import org.ldaptive.ConnectionFactory;
30  import org.springframework.beans.factory.ObjectProvider;
31  import org.springframework.boot.autoconfigure.AutoConfigureAfter;
32  import org.springframework.boot.autoconfigure.AutoConfigureBefore;
33  import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
34  import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
35  import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
36  import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;
37  import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication.Type;
38  import org.springframework.boot.context.event.ApplicationReadyEvent;
39  import org.springframework.boot.context.properties.EnableConfigurationProperties;
40  import org.springframework.context.annotation.Bean;
41  import org.springframework.context.annotation.Configuration;
42  import org.springframework.context.event.EventListener;
43  import org.springframework.security.core.userdetails.ReactiveUserDetailsService;
44  import org.springframework.security.core.userdetails.UserDetailsService;
45  import org.springframework.security.crypto.password.PasswordEncoder;
46  import org.springframework.util.Assert;
47  import org.springframework.util.ClassUtils;
48  
49  /**
50   * The ldaptive user details auto configuration.
51   *
52   * @author Christian Bremer
53   */
54  @Configuration
55  @ConditionalOnWebApplication(type = Type.ANY)
56  @AutoConfigureBefore(InMemoryUserDetailsAutoConfiguration.class)
57  @AutoConfigureAfter(LdaptiveAutoConfiguration.class)
58  @ConditionalOnClass({
59      ConnectionFactory.class,
60      LdaptiveOperations.class
61  })
62  @ConditionalOnProperty(
63      prefix = "bremersee.ldaptive",
64      name = {"enabled", "authentication-enabled"},
65      havingValue = "true")
66  @EnableConfigurationProperties(LdaptiveProperties.class)
67  @Slf4j
68  public class LdaptiveUserDetailsAutoConfiguration {
69  
70    private final UserDetailsProperties properties;
71  
72    private final LdaptiveOperations ldaptiveOperations;
73  
74    /**
75     * Instantiates a new ldaptive user details auto configuration.
76     *
77     * @param properties the properties
78     * @param ldaptiveOperationsProvider the ldaptive operations provider
79     */
80    public LdaptiveUserDetailsAutoConfiguration(
81        LdaptiveProperties properties,
82        ObjectProvider<LdaptiveOperations> ldaptiveOperationsProvider) {
83      this.properties = properties.getUserDetails();
84      this.ldaptiveOperations = ldaptiveOperationsProvider.getIfAvailable();
85      Assert.notNull(this.ldaptiveOperations, "Ldap operations must not be present.");
86    }
87  
88    /**
89     * Init.
90     */
91    @EventListener(ApplicationReadyEvent.class)
92    public void init() {
93      log.info("\n"
94              + "*********************************************************************************\n"
95              + "* {}\n"
96              + "*********************************************************************************\n"
97              + "* properties = {}\n"
98              + "*********************************************************************************",
99          ClassUtils.getUserClass(getClass()).getSimpleName(),
100         properties);
101     Assert.hasText(properties.getUserBaseDn(), "User base dn must be present.");
102     Assert.hasText(properties.getUserFindOneFilter(), "User find one filter must be present.");
103   }
104 
105   /**
106    * Ldaptive user details service.
107    *
108    * @return the ldaptive user details service
109    */
110   @ConditionalOnWebApplication(type = Type.SERVLET)
111   @ConditionalOnMissingBean(value = {UserDetailsService.class})
112   @Bean
113   public LdaptiveUserDetailsService ldaptiveUserDetailsService() {
114     return new LdaptiveUserDetailsService(
115         ldaptiveOperations,
116         properties.getUserBaseDn(),
117         properties.getUserFindOneFilter(),
118         properties.getUserFindOneSearchScope(),
119         properties.getUserAccountControlAttributeName(),
120         properties.getAuthorities(),
121         properties.getAuthorityAttributeName(),
122         properties.isAuthorityDn(),
123         properties.getAuthorityMap(),
124         properties.getAuthorityPrefix());
125   }
126 
127   /**
128    * Reactive ldaptive user details service.
129    *
130    * @param reactiveLdaptiveOperationsProvider the reactive ldaptive operations provider
131    * @return the reactive ldaptive user details service
132    */
133   @ConditionalOnWebApplication(type = Type.REACTIVE)
134   @ConditionalOnMissingBean(value = {ReactiveUserDetailsService.class})
135   @Bean
136   public ReactiveLdaptiveUserDetailsService reactiveLdaptiveUserDetailsService(
137       ObjectProvider<ReactiveLdaptiveOperations> reactiveLdaptiveOperationsProvider) {
138     ReactiveLdaptiveOperations reactiveLdaptiveOperations = reactiveLdaptiveOperationsProvider.getIfAvailable();
139     Assert.notNull(reactiveLdaptiveOperations, "Reactive ldap operations must not be present.");
140     return new ReactiveLdaptiveUserDetailsService(
141         reactiveLdaptiveOperations,
142         properties.getUserBaseDn(),
143         properties.getUserFindOneFilter(),
144         properties.getUserFindOneSearchScope(),
145         properties.getUserAccountControlAttributeName(),
146         properties.getAuthorities(),
147         properties.getAuthorityAttributeName(),
148         properties.isAuthorityDn(),
149         properties.getAuthorityMap(),
150         properties.getAuthorityPrefix());
151   }
152 
153   /**
154    * Ldaptive password matcher.
155    *
156    * @return the ldaptive password matcher
157    */
158   @ConditionalOnMissingBean(value = {PasswordEncoder.class})
159   @Bean
160   public LdaptivePasswordMatcher passwordEncoder() {
161     LdaptivePasswordMatcher matcher = new LdaptivePasswordMatcher(
162         ldaptiveOperations,
163         properties.getUserBaseDn(),
164         properties.getUserFindOneFilter());
165     matcher.setUserPasswordAttributeName(properties.getUserAccountControlAttributeName());
166     matcher.setUserFindOneSearchScope(properties.getUserFindOneSearchScope());
167     matcher.setUserPasswordAttributeName(properties.getUserPasswordAttributeName());
168     matcher.setDelegate(new LdaptivePasswordEncoder(
169         properties.getUserPasswordLabel(),
170         properties.getUserPasswordAlgorithm()));
171     return matcher;
172   }
173 
174 }