9 changed files with 1035 additions and 0 deletions
@ -0,0 +1,185 @@
@@ -0,0 +1,185 @@
|
||||
/* |
||||
* Copyright 2002-2022 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 org.springframework.security.config.ldap; |
||||
|
||||
import org.junit.jupiter.api.Test; |
||||
import org.junit.jupiter.api.extension.ExtendWith; |
||||
|
||||
import org.springframework.beans.factory.UnsatisfiedDependencyException; |
||||
import org.springframework.beans.factory.annotation.Autowired; |
||||
import org.springframework.context.annotation.Bean; |
||||
import org.springframework.ldap.core.support.LdapContextSource; |
||||
import org.springframework.security.authentication.AuthenticationManager; |
||||
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; |
||||
import org.springframework.security.config.test.SpringTestContext; |
||||
import org.springframework.security.config.test.SpringTestContextExtension; |
||||
import org.springframework.security.crypto.password.NoOpPasswordEncoder; |
||||
import org.springframework.test.web.servlet.MockMvc; |
||||
|
||||
import static org.assertj.core.api.Assertions.assertThatExceptionOfType; |
||||
import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestBuilders.formLogin; |
||||
import static org.springframework.security.test.web.servlet.response.SecurityMockMvcResultMatchers.authenticated; |
||||
|
||||
@ExtendWith(SpringTestContextExtension.class) |
||||
public class EmbeddedLdapServerContextSourceFactoryBeanITests { |
||||
|
||||
public final SpringTestContext spring = new SpringTestContext(this); |
||||
|
||||
@Autowired |
||||
private MockMvc mockMvc; |
||||
|
||||
@Test |
||||
public void contextSourceFactoryBeanWhenEmbeddedServerThenAuthenticates() throws Exception { |
||||
this.spring.register(FromEmbeddedLdapServerConfig.class).autowire(); |
||||
|
||||
this.mockMvc.perform(formLogin().user("bob").password("bobspassword")) |
||||
.andExpect(authenticated().withUsername("bob")); |
||||
} |
||||
|
||||
@Test |
||||
public void contextSourceFactoryBeanWhenPortZeroThenAuthenticates() throws Exception { |
||||
this.spring.register(PortZeroConfig.class).autowire(); |
||||
|
||||
this.mockMvc.perform(formLogin().user("bob").password("bobspassword")) |
||||
.andExpect(authenticated().withUsername("bob")); |
||||
} |
||||
|
||||
@Test |
||||
public void contextSourceFactoryBeanWhenCustomLdifAndRootThenAuthenticates() throws Exception { |
||||
this.spring.register(CustomLdifAndRootConfig.class).autowire(); |
||||
|
||||
this.mockMvc.perform(formLogin().user("pg").password("password")).andExpect(authenticated().withUsername("pg")); |
||||
} |
||||
|
||||
@Test |
||||
public void contextSourceFactoryBeanWhenCustomManagerDnThenAuthenticates() throws Exception { |
||||
this.spring.register(CustomManagerDnConfig.class).autowire(); |
||||
|
||||
this.mockMvc.perform(formLogin().user("bob").password("bobspassword")) |
||||
.andExpect(authenticated().withUsername("bob")); |
||||
} |
||||
|
||||
@Test |
||||
public void contextSourceFactoryBeanWhenManagerDnAndNoPasswordThenException() { |
||||
assertThatExceptionOfType(UnsatisfiedDependencyException.class) |
||||
.isThrownBy(() -> this.spring.register(CustomManagerDnNoPasswordConfig.class).autowire()) |
||||
.withRootCauseInstanceOf(IllegalStateException.class) |
||||
.withMessageContaining("managerPassword is required if managerDn is supplied"); |
||||
} |
||||
|
||||
@EnableWebSecurity |
||||
static class FromEmbeddedLdapServerConfig { |
||||
|
||||
@Bean |
||||
EmbeddedLdapServerContextSourceFactoryBean contextSourceFactoryBean() { |
||||
return EmbeddedLdapServerContextSourceFactoryBean.fromEmbeddedLdapServer(); |
||||
} |
||||
|
||||
@Bean |
||||
AuthenticationManager authenticationManager(LdapContextSource contextSource) { |
||||
LdapBindAuthenticationManagerFactory factory = new LdapBindAuthenticationManagerFactory(contextSource); |
||||
factory.setUserDnPatterns("uid={0},ou=people"); |
||||
return factory.createAuthenticationManager(); |
||||
} |
||||
|
||||
} |
||||
|
||||
@EnableWebSecurity |
||||
static class PortZeroConfig { |
||||
|
||||
@Bean |
||||
EmbeddedLdapServerContextSourceFactoryBean contextSourceFactoryBean() { |
||||
EmbeddedLdapServerContextSourceFactoryBean factoryBean = EmbeddedLdapServerContextSourceFactoryBean |
||||
.fromEmbeddedLdapServer(); |
||||
factoryBean.setPort(0); |
||||
return factoryBean; |
||||
} |
||||
|
||||
@Bean |
||||
AuthenticationManager authenticationManager(LdapContextSource contextSource) { |
||||
LdapBindAuthenticationManagerFactory factory = new LdapBindAuthenticationManagerFactory(contextSource); |
||||
factory.setUserDnPatterns("uid={0},ou=people"); |
||||
return factory.createAuthenticationManager(); |
||||
} |
||||
|
||||
} |
||||
|
||||
@EnableWebSecurity |
||||
static class CustomLdifAndRootConfig { |
||||
|
||||
@Bean |
||||
EmbeddedLdapServerContextSourceFactoryBean contextSourceFactoryBean() { |
||||
EmbeddedLdapServerContextSourceFactoryBean factoryBean = EmbeddedLdapServerContextSourceFactoryBean |
||||
.fromEmbeddedLdapServer(); |
||||
factoryBean.setLdif("classpath*:test-server2.xldif"); |
||||
factoryBean.setRoot("dc=monkeymachine,dc=co,dc=uk"); |
||||
return factoryBean; |
||||
} |
||||
|
||||
@Bean |
||||
AuthenticationManager authenticationManager(LdapContextSource contextSource) { |
||||
LdapBindAuthenticationManagerFactory factory = new LdapBindAuthenticationManagerFactory(contextSource); |
||||
factory.setUserDnPatterns("uid={0},ou=gorillas"); |
||||
return factory.createAuthenticationManager(); |
||||
} |
||||
|
||||
} |
||||
|
||||
@EnableWebSecurity |
||||
static class CustomManagerDnConfig { |
||||
|
||||
@Bean |
||||
EmbeddedLdapServerContextSourceFactoryBean contextSourceFactoryBean() { |
||||
EmbeddedLdapServerContextSourceFactoryBean factoryBean = EmbeddedLdapServerContextSourceFactoryBean |
||||
.fromEmbeddedLdapServer(); |
||||
factoryBean.setManagerDn("uid=admin,ou=system"); |
||||
factoryBean.setManagerPassword("secret"); |
||||
return factoryBean; |
||||
} |
||||
|
||||
@Bean |
||||
AuthenticationManager authenticationManager(LdapContextSource contextSource) { |
||||
LdapPasswordComparisonAuthenticationManagerFactory factory = new LdapPasswordComparisonAuthenticationManagerFactory( |
||||
contextSource, NoOpPasswordEncoder.getInstance()); |
||||
factory.setUserDnPatterns("uid={0},ou=people"); |
||||
return factory.createAuthenticationManager(); |
||||
} |
||||
|
||||
} |
||||
|
||||
@EnableWebSecurity |
||||
static class CustomManagerDnNoPasswordConfig { |
||||
|
||||
@Bean |
||||
EmbeddedLdapServerContextSourceFactoryBean contextSourceFactoryBean() { |
||||
EmbeddedLdapServerContextSourceFactoryBean factoryBean = EmbeddedLdapServerContextSourceFactoryBean |
||||
.fromEmbeddedLdapServer(); |
||||
factoryBean.setManagerDn("uid=admin,ou=system"); |
||||
return factoryBean; |
||||
} |
||||
|
||||
@Bean |
||||
AuthenticationManager authenticationManager(LdapContextSource contextSource) { |
||||
LdapPasswordComparisonAuthenticationManagerFactory factory = new LdapPasswordComparisonAuthenticationManagerFactory( |
||||
contextSource, NoOpPasswordEncoder.getInstance()); |
||||
factory.setUserDnPatterns("uid={0},ou=people"); |
||||
return factory.createAuthenticationManager(); |
||||
} |
||||
|
||||
} |
||||
|
||||
} |
||||
@ -0,0 +1,241 @@
@@ -0,0 +1,241 @@
|
||||
/* |
||||
* Copyright 2002-2022 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 org.springframework.security.config.ldap; |
||||
|
||||
import java.util.Collection; |
||||
import java.util.Collections; |
||||
import java.util.HashSet; |
||||
import java.util.Set; |
||||
|
||||
import org.junit.jupiter.api.Test; |
||||
import org.junit.jupiter.api.extension.ExtendWith; |
||||
|
||||
import org.springframework.beans.factory.DisposableBean; |
||||
import org.springframework.beans.factory.annotation.Autowired; |
||||
import org.springframework.context.annotation.Bean; |
||||
import org.springframework.ldap.core.DirContextAdapter; |
||||
import org.springframework.ldap.core.DirContextOperations; |
||||
import org.springframework.ldap.core.support.BaseLdapPathContextSource; |
||||
import org.springframework.ldap.core.support.LdapContextSource; |
||||
import org.springframework.security.authentication.AuthenticationManager; |
||||
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; |
||||
import org.springframework.security.config.test.SpringTestContext; |
||||
import org.springframework.security.config.test.SpringTestContextExtension; |
||||
import org.springframework.security.core.GrantedAuthority; |
||||
import org.springframework.security.core.authority.AuthorityUtils; |
||||
import org.springframework.security.core.authority.SimpleGrantedAuthority; |
||||
import org.springframework.security.core.authority.mapping.GrantedAuthoritiesMapper; |
||||
import org.springframework.security.core.userdetails.User; |
||||
import org.springframework.security.core.userdetails.UserDetails; |
||||
import org.springframework.security.ldap.DefaultSpringSecurityContextSource; |
||||
import org.springframework.security.ldap.server.ApacheDSContainer; |
||||
import org.springframework.security.ldap.userdetails.DefaultLdapAuthoritiesPopulator; |
||||
import org.springframework.security.ldap.userdetails.LdapAuthoritiesPopulator; |
||||
import org.springframework.security.ldap.userdetails.UserDetailsContextMapper; |
||||
import org.springframework.test.web.servlet.MockMvc; |
||||
|
||||
import static org.mockito.Mockito.mock; |
||||
import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestBuilders.formLogin; |
||||
import static org.springframework.security.test.web.servlet.response.SecurityMockMvcResultMatchers.authenticated; |
||||
|
||||
@ExtendWith(SpringTestContextExtension.class) |
||||
public class LdapBindAuthenticationManagerFactoryITests { |
||||
|
||||
public final SpringTestContext spring = new SpringTestContext(this); |
||||
|
||||
@Autowired |
||||
private MockMvc mockMvc; |
||||
|
||||
@Test |
||||
public void authenticationManagerFactoryWhenFromContextSourceThenAuthenticates() throws Exception { |
||||
this.spring.register(FromContextSourceConfig.class).autowire(); |
||||
|
||||
this.mockMvc.perform(formLogin().user("bob").password("bobspassword")) |
||||
.andExpect(authenticated().withUsername("bob")); |
||||
} |
||||
|
||||
@Test |
||||
public void ldapAuthenticationProviderCustomLdapAuthoritiesPopulator() throws Exception { |
||||
CustomAuthoritiesPopulatorConfig.LAP = new DefaultLdapAuthoritiesPopulator(mock(LdapContextSource.class), |
||||
null) { |
||||
@Override |
||||
protected Set<GrantedAuthority> getAdditionalRoles(DirContextOperations user, String username) { |
||||
return new HashSet<>(AuthorityUtils.createAuthorityList("ROLE_EXTRA")); |
||||
} |
||||
}; |
||||
|
||||
this.spring.register(CustomAuthoritiesPopulatorConfig.class).autowire(); |
||||
|
||||
this.mockMvc.perform(formLogin().user("bob").password("bobspassword")).andExpect( |
||||
authenticated().withAuthorities(Collections.singleton(new SimpleGrantedAuthority("ROLE_EXTRA")))); |
||||
} |
||||
|
||||
@Test |
||||
public void authenticationManagerFactoryWhenCustomAuthoritiesMapperThenUsed() throws Exception { |
||||
CustomAuthoritiesMapperConfig.AUTHORITIES_MAPPER = ((authorities) -> AuthorityUtils |
||||
.createAuthorityList("ROLE_CUSTOM")); |
||||
|
||||
this.spring.register(CustomAuthoritiesMapperConfig.class).autowire(); |
||||
|
||||
this.mockMvc.perform(formLogin().user("bob").password("bobspassword")).andExpect( |
||||
authenticated().withAuthorities(Collections.singleton(new SimpleGrantedAuthority("ROLE_CUSTOM")))); |
||||
} |
||||
|
||||
@Test |
||||
public void authenticationManagerFactoryWhenCustomUserDetailsContextMapperThenUsed() throws Exception { |
||||
CustomUserDetailsContextMapperConfig.CONTEXT_MAPPER = new UserDetailsContextMapper() { |
||||
@Override |
||||
public UserDetails mapUserFromContext(DirContextOperations ctx, String username, |
||||
Collection<? extends GrantedAuthority> authorities) { |
||||
return User.withUsername("other").password("password").roles("USER").build(); |
||||
} |
||||
|
||||
@Override |
||||
public void mapUserToContext(UserDetails user, DirContextAdapter ctx) { |
||||
} |
||||
}; |
||||
|
||||
this.spring.register(CustomUserDetailsContextMapperConfig.class).autowire(); |
||||
|
||||
this.mockMvc.perform(formLogin().user("bob").password("bobspassword")) |
||||
.andExpect(authenticated().withUsername("other")); |
||||
} |
||||
|
||||
@Test |
||||
public void authenticationManagerFactoryWhenCustomUserDnPatternsThenUsed() throws Exception { |
||||
this.spring.register(CustomUserDnPatternsConfig.class).autowire(); |
||||
|
||||
this.mockMvc.perform(formLogin().user("bob").password("bobspassword")) |
||||
.andExpect(authenticated().withUsername("bob")); |
||||
} |
||||
|
||||
@Test |
||||
public void authenticationManagerFactoryWhenCustomUserSearchThenUsed() throws Exception { |
||||
this.spring.register(CustomUserSearchConfig.class).autowire(); |
||||
|
||||
this.mockMvc.perform(formLogin().user("bob").password("bobspassword")) |
||||
.andExpect(authenticated().withUsername("bob")); |
||||
} |
||||
|
||||
@EnableWebSecurity |
||||
static class FromContextSourceConfig extends BaseLdapServerConfig { |
||||
|
||||
@Bean |
||||
AuthenticationManager authenticationManager(BaseLdapPathContextSource contextSource) { |
||||
LdapBindAuthenticationManagerFactory factory = new LdapBindAuthenticationManagerFactory(contextSource); |
||||
factory.setUserDnPatterns("uid={0},ou=people"); |
||||
return factory.createAuthenticationManager(); |
||||
} |
||||
|
||||
} |
||||
|
||||
@EnableWebSecurity |
||||
static class CustomAuthoritiesMapperConfig extends BaseLdapServerConfig { |
||||
|
||||
static GrantedAuthoritiesMapper AUTHORITIES_MAPPER; |
||||
|
||||
@Bean |
||||
AuthenticationManager authenticationManager(BaseLdapPathContextSource contextSource) { |
||||
LdapBindAuthenticationManagerFactory factory = new LdapBindAuthenticationManagerFactory(contextSource); |
||||
factory.setUserDnPatterns("uid={0},ou=people"); |
||||
factory.setAuthoritiesMapper(AUTHORITIES_MAPPER); |
||||
return factory.createAuthenticationManager(); |
||||
} |
||||
|
||||
} |
||||
|
||||
@EnableWebSecurity |
||||
static class CustomAuthoritiesPopulatorConfig extends BaseLdapServerConfig { |
||||
|
||||
static LdapAuthoritiesPopulator LAP; |
||||
|
||||
@Bean |
||||
AuthenticationManager authenticationManager(BaseLdapPathContextSource contextSource) { |
||||
LdapBindAuthenticationManagerFactory factory = new LdapBindAuthenticationManagerFactory(contextSource); |
||||
factory.setUserDnPatterns("uid={0},ou=people"); |
||||
factory.setLdapAuthoritiesPopulator(LAP); |
||||
return factory.createAuthenticationManager(); |
||||
} |
||||
|
||||
} |
||||
|
||||
@EnableWebSecurity |
||||
static class CustomUserDetailsContextMapperConfig extends BaseLdapServerConfig { |
||||
|
||||
static UserDetailsContextMapper CONTEXT_MAPPER; |
||||
|
||||
@Bean |
||||
AuthenticationManager authenticationManager(BaseLdapPathContextSource contextSource) { |
||||
LdapBindAuthenticationManagerFactory factory = new LdapBindAuthenticationManagerFactory(contextSource); |
||||
factory.setUserDnPatterns("uid={0},ou=people"); |
||||
factory.setUserDetailsContextMapper(CONTEXT_MAPPER); |
||||
return factory.createAuthenticationManager(); |
||||
} |
||||
|
||||
} |
||||
|
||||
@EnableWebSecurity |
||||
static class CustomUserDnPatternsConfig extends BaseLdapServerConfig { |
||||
|
||||
@Bean |
||||
AuthenticationManager authenticationManager(BaseLdapPathContextSource contextSource) { |
||||
LdapBindAuthenticationManagerFactory factory = new LdapBindAuthenticationManagerFactory(contextSource); |
||||
factory.setUserDnPatterns("uid={0},ou=people"); |
||||
return factory.createAuthenticationManager(); |
||||
} |
||||
|
||||
} |
||||
|
||||
@EnableWebSecurity |
||||
static class CustomUserSearchConfig extends BaseLdapServerConfig { |
||||
|
||||
@Bean |
||||
AuthenticationManager authenticationManager(BaseLdapPathContextSource contextSource) { |
||||
LdapBindAuthenticationManagerFactory factory = new LdapBindAuthenticationManagerFactory(contextSource); |
||||
factory.setUserSearchFilter("uid={0}"); |
||||
factory.setUserSearchBase("ou=people"); |
||||
return factory.createAuthenticationManager(); |
||||
} |
||||
|
||||
} |
||||
|
||||
@EnableWebSecurity |
||||
abstract static class BaseLdapServerConfig implements DisposableBean { |
||||
|
||||
private ApacheDSContainer container; |
||||
|
||||
@Bean |
||||
ApacheDSContainer ldapServer() throws Exception { |
||||
this.container = new ApacheDSContainer("dc=springframework,dc=org", "classpath:/test-server.ldif"); |
||||
this.container.setPort(0); |
||||
return this.container; |
||||
} |
||||
|
||||
@Bean |
||||
BaseLdapPathContextSource contextSource(ApacheDSContainer container) { |
||||
int port = container.getLocalPort(); |
||||
return new DefaultSpringSecurityContextSource("ldap://localhost:" + port + "/dc=springframework,dc=org"); |
||||
} |
||||
|
||||
@Override |
||||
public void destroy() { |
||||
this.container.stop(); |
||||
} |
||||
|
||||
} |
||||
|
||||
} |
||||
@ -0,0 +1,114 @@
@@ -0,0 +1,114 @@
|
||||
/* |
||||
* Copyright 2002-2022 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 org.springframework.security.config.ldap; |
||||
|
||||
import org.junit.jupiter.api.Test; |
||||
import org.junit.jupiter.api.extension.ExtendWith; |
||||
|
||||
import org.springframework.beans.factory.DisposableBean; |
||||
import org.springframework.beans.factory.annotation.Autowired; |
||||
import org.springframework.context.annotation.Bean; |
||||
import org.springframework.ldap.core.support.BaseLdapPathContextSource; |
||||
import org.springframework.security.authentication.AuthenticationManager; |
||||
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; |
||||
import org.springframework.security.config.test.SpringTestContext; |
||||
import org.springframework.security.config.test.SpringTestContextExtension; |
||||
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; |
||||
import org.springframework.security.crypto.password.NoOpPasswordEncoder; |
||||
import org.springframework.security.ldap.DefaultSpringSecurityContextSource; |
||||
import org.springframework.security.ldap.server.ApacheDSContainer; |
||||
import org.springframework.test.web.servlet.MockMvc; |
||||
|
||||
import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestBuilders.formLogin; |
||||
import static org.springframework.security.test.web.servlet.response.SecurityMockMvcResultMatchers.authenticated; |
||||
|
||||
@ExtendWith(SpringTestContextExtension.class) |
||||
public class LdapPasswordComparisonAuthenticationManagerFactoryITests { |
||||
|
||||
public final SpringTestContext spring = new SpringTestContext(this); |
||||
|
||||
@Autowired |
||||
private MockMvc mockMvc; |
||||
|
||||
@Test |
||||
public void authenticationManagerFactoryWhenCustomPasswordEncoderThenUsed() throws Exception { |
||||
this.spring.register(CustomPasswordEncoderConfig.class).autowire(); |
||||
|
||||
this.mockMvc.perform(formLogin().user("bcrypt").password("password")) |
||||
.andExpect(authenticated().withUsername("bcrypt")); |
||||
} |
||||
|
||||
@Test |
||||
public void authenticationManagerFactoryWhenCustomPasswordAttributeThenUsed() throws Exception { |
||||
this.spring.register(CustomPasswordAttributeConfig.class).autowire(); |
||||
|
||||
this.mockMvc.perform(formLogin().user("bob").password("bob")).andExpect(authenticated().withUsername("bob")); |
||||
} |
||||
|
||||
@EnableWebSecurity |
||||
static class CustomPasswordEncoderConfig extends BaseLdapServerConfig { |
||||
|
||||
@Bean |
||||
AuthenticationManager authenticationManager(BaseLdapPathContextSource contextSource) { |
||||
LdapPasswordComparisonAuthenticationManagerFactory factory = new LdapPasswordComparisonAuthenticationManagerFactory( |
||||
contextSource, new BCryptPasswordEncoder()); |
||||
factory.setUserDnPatterns("uid={0},ou=people"); |
||||
return factory.createAuthenticationManager(); |
||||
} |
||||
|
||||
} |
||||
|
||||
@EnableWebSecurity |
||||
static class CustomPasswordAttributeConfig extends BaseLdapServerConfig { |
||||
|
||||
@Bean |
||||
AuthenticationManager authenticationManager(BaseLdapPathContextSource contextSource) { |
||||
LdapPasswordComparisonAuthenticationManagerFactory factory = new LdapPasswordComparisonAuthenticationManagerFactory( |
||||
contextSource, NoOpPasswordEncoder.getInstance()); |
||||
factory.setPasswordAttribute("uid"); |
||||
factory.setUserDnPatterns("uid={0},ou=people"); |
||||
return factory.createAuthenticationManager(); |
||||
} |
||||
|
||||
} |
||||
|
||||
@EnableWebSecurity |
||||
abstract static class BaseLdapServerConfig implements DisposableBean { |
||||
|
||||
private ApacheDSContainer container; |
||||
|
||||
@Bean |
||||
ApacheDSContainer ldapServer() throws Exception { |
||||
this.container = new ApacheDSContainer("dc=springframework,dc=org", "classpath:/test-server.ldif"); |
||||
this.container.setPort(0); |
||||
return this.container; |
||||
} |
||||
|
||||
@Bean |
||||
BaseLdapPathContextSource contextSource(ApacheDSContainer container) { |
||||
int port = container.getLocalPort(); |
||||
return new DefaultSpringSecurityContextSource("ldap://localhost:" + port + "/dc=springframework,dc=org"); |
||||
} |
||||
|
||||
@Override |
||||
public void destroy() { |
||||
this.container.stop(); |
||||
} |
||||
|
||||
} |
||||
|
||||
} |
||||
@ -0,0 +1,183 @@
@@ -0,0 +1,183 @@
|
||||
/* |
||||
* Copyright 2002-2022 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 org.springframework.security.config.ldap; |
||||
|
||||
import org.springframework.ldap.core.support.BaseLdapPathContextSource; |
||||
import org.springframework.security.authentication.AuthenticationManager; |
||||
import org.springframework.security.authentication.ProviderManager; |
||||
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; |
||||
import org.springframework.security.core.Authentication; |
||||
import org.springframework.security.core.authority.mapping.GrantedAuthoritiesMapper; |
||||
import org.springframework.security.core.userdetails.UserDetails; |
||||
import org.springframework.security.ldap.authentication.AbstractLdapAuthenticator; |
||||
import org.springframework.security.ldap.authentication.LdapAuthenticationProvider; |
||||
import org.springframework.security.ldap.search.FilterBasedLdapUserSearch; |
||||
import org.springframework.security.ldap.userdetails.LdapAuthoritiesPopulator; |
||||
import org.springframework.security.ldap.userdetails.UserDetailsContextMapper; |
||||
|
||||
/** |
||||
* Creates an {@link AuthenticationManager} that can perform LDAP authentication. |
||||
* |
||||
* @author Eleftheria Stein |
||||
* @since 5.7 |
||||
*/ |
||||
public abstract class AbstractLdapAuthenticationManagerFactory<T extends AbstractLdapAuthenticator> { |
||||
|
||||
AbstractLdapAuthenticationManagerFactory(BaseLdapPathContextSource contextSource) { |
||||
this.contextSource = contextSource; |
||||
} |
||||
|
||||
private BaseLdapPathContextSource contextSource; |
||||
|
||||
private String[] userDnPatterns; |
||||
|
||||
private LdapAuthoritiesPopulator ldapAuthoritiesPopulator; |
||||
|
||||
private GrantedAuthoritiesMapper authoritiesMapper; |
||||
|
||||
private UserDetailsContextMapper userDetailsContextMapper; |
||||
|
||||
private String userSearchFilter; |
||||
|
||||
private String userSearchBase = ""; |
||||
|
||||
/** |
||||
* Sets the {@link BaseLdapPathContextSource} used to perform LDAP authentication. |
||||
* @param contextSource the {@link BaseLdapPathContextSource} used to perform LDAP |
||||
* authentication |
||||
*/ |
||||
public void setContextSource(BaseLdapPathContextSource contextSource) { |
||||
this.contextSource = contextSource; |
||||
} |
||||
|
||||
/** |
||||
* Gets the {@link BaseLdapPathContextSource} used to perform LDAP authentication. |
||||
* @return the {@link BaseLdapPathContextSource} used to perform LDAP authentication |
||||
*/ |
||||
protected final BaseLdapPathContextSource getContextSource() { |
||||
return this.contextSource; |
||||
} |
||||
|
||||
/** |
||||
* Sets the {@link LdapAuthoritiesPopulator} used to obtain a list of granted |
||||
* authorities for an LDAP user. |
||||
* @param ldapAuthoritiesPopulator the {@link LdapAuthoritiesPopulator} to use |
||||
*/ |
||||
public void setLdapAuthoritiesPopulator(LdapAuthoritiesPopulator ldapAuthoritiesPopulator) { |
||||
this.ldapAuthoritiesPopulator = ldapAuthoritiesPopulator; |
||||
} |
||||
|
||||
/** |
||||
* Sets the {@link GrantedAuthoritiesMapper} used for converting the authorities |
||||
* loaded from storage to a new set of authorities which will be associated to the |
||||
* {@link UsernamePasswordAuthenticationToken}. |
||||
* @param authoritiesMapper the {@link GrantedAuthoritiesMapper} used for mapping the |
||||
* user's authorities |
||||
*/ |
||||
public void setAuthoritiesMapper(GrantedAuthoritiesMapper authoritiesMapper) { |
||||
this.authoritiesMapper = authoritiesMapper; |
||||
} |
||||
|
||||
/** |
||||
* Sets a custom strategy to be used for creating the {@link UserDetails} which will |
||||
* be stored as the principal in the {@link Authentication}. |
||||
* @param userDetailsContextMapper the strategy instance |
||||
*/ |
||||
public void setUserDetailsContextMapper(UserDetailsContextMapper userDetailsContextMapper) { |
||||
this.userDetailsContextMapper = userDetailsContextMapper; |
||||
} |
||||
|
||||
/** |
||||
* If your users are at a fixed location in the directory (i.e. you can work out the |
||||
* DN directly from the username without doing a directory search), you can use this |
||||
* attribute to map directly to the DN. It maps directly to the userDnPatterns |
||||
* property of AbstractLdapAuthenticator. The value is a specific pattern used to |
||||
* build the user's DN, for example "uid={0},ou=people". The key "{0}" must be present |
||||
* and will be substituted with the username. |
||||
* @param userDnPatterns the LDAP patterns for finding the usernames |
||||
*/ |
||||
public void setUserDnPatterns(String... userDnPatterns) { |
||||
this.userDnPatterns = userDnPatterns; |
||||
} |
||||
|
||||
/** |
||||
* The LDAP filter used to search for users (optional). For example "(uid={0})". The |
||||
* substituted parameter is the user's login name. |
||||
* @param userSearchFilter the LDAP filter used to search for users |
||||
*/ |
||||
public void setUserSearchFilter(String userSearchFilter) { |
||||
this.userSearchFilter = userSearchFilter; |
||||
} |
||||
|
||||
/** |
||||
* Search base for user searches. Defaults to "". Only used with |
||||
* {@link #setUserSearchFilter(String)}. |
||||
* @param userSearchBase search base for user searches |
||||
*/ |
||||
public void setUserSearchBase(String userSearchBase) { |
||||
this.userSearchBase = userSearchBase; |
||||
} |
||||
|
||||
/** |
||||
* Returns the configured {@link AuthenticationManager} that can be used to perform |
||||
* LDAP authentication. |
||||
* @return the configured {@link AuthenticationManager} |
||||
*/ |
||||
public final AuthenticationManager createAuthenticationManager() { |
||||
LdapAuthenticationProvider ldapAuthenticationProvider = getProvider(); |
||||
return new ProviderManager(ldapAuthenticationProvider); |
||||
} |
||||
|
||||
private LdapAuthenticationProvider getProvider() { |
||||
AbstractLdapAuthenticator authenticator = getAuthenticator(); |
||||
LdapAuthenticationProvider provider; |
||||
if (this.ldapAuthoritiesPopulator != null) { |
||||
provider = new LdapAuthenticationProvider(authenticator, this.ldapAuthoritiesPopulator); |
||||
} |
||||
else { |
||||
provider = new LdapAuthenticationProvider(authenticator); |
||||
} |
||||
if (this.authoritiesMapper != null) { |
||||
provider.setAuthoritiesMapper(this.authoritiesMapper); |
||||
} |
||||
if (this.userDetailsContextMapper != null) { |
||||
provider.setUserDetailsContextMapper(this.userDetailsContextMapper); |
||||
} |
||||
return provider; |
||||
} |
||||
|
||||
private AbstractLdapAuthenticator getAuthenticator() { |
||||
AbstractLdapAuthenticator authenticator = createDefaultLdapAuthenticator(); |
||||
if (this.userSearchFilter != null) { |
||||
authenticator.setUserSearch( |
||||
new FilterBasedLdapUserSearch(this.userSearchBase, this.userSearchFilter, this.contextSource)); |
||||
} |
||||
if (this.userDnPatterns != null && this.userDnPatterns.length > 0) { |
||||
authenticator.setUserDnPatterns(this.userDnPatterns); |
||||
} |
||||
authenticator.afterPropertiesSet(); |
||||
return authenticator; |
||||
} |
||||
|
||||
/** |
||||
* Allows subclasses to supply the default {@link AbstractLdapAuthenticator}. |
||||
* @return the {@link AbstractLdapAuthenticator} that will be configured for LDAP |
||||
* authentication |
||||
*/ |
||||
protected abstract T createDefaultLdapAuthenticator(); |
||||
|
||||
} |
||||
@ -0,0 +1,185 @@
@@ -0,0 +1,185 @@
|
||||
/* |
||||
* Copyright 2002-2022 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 org.springframework.security.config.ldap; |
||||
|
||||
import java.io.IOException; |
||||
import java.net.ServerSocket; |
||||
|
||||
import org.springframework.beans.BeansException; |
||||
import org.springframework.beans.factory.DisposableBean; |
||||
import org.springframework.beans.factory.FactoryBean; |
||||
import org.springframework.context.ApplicationContext; |
||||
import org.springframework.context.ApplicationContextAware; |
||||
import org.springframework.context.Lifecycle; |
||||
import org.springframework.security.ldap.DefaultSpringSecurityContextSource; |
||||
import org.springframework.security.ldap.server.EmbeddedLdapServerContainer; |
||||
import org.springframework.security.ldap.server.UnboundIdContainer; |
||||
import org.springframework.util.ClassUtils; |
||||
|
||||
/** |
||||
* Creates a {@link DefaultSpringSecurityContextSource} used to perform LDAP |
||||
* authentication and starts and in-memory LDAP server. |
||||
* |
||||
* @author Eleftheria Stein |
||||
* @since 5.7 |
||||
*/ |
||||
public class EmbeddedLdapServerContextSourceFactoryBean |
||||
implements FactoryBean<DefaultSpringSecurityContextSource>, DisposableBean, ApplicationContextAware { |
||||
|
||||
private static final String UNBOUNDID_CLASSNAME = "com.unboundid.ldap.listener.InMemoryDirectoryServer"; |
||||
|
||||
private static final int DEFAULT_PORT = 33389; |
||||
|
||||
private static final int RANDOM_PORT = 0; |
||||
|
||||
private Integer port; |
||||
|
||||
private String ldif = "classpath*:*.ldif"; |
||||
|
||||
private String root = "dc=springframework,dc=org"; |
||||
|
||||
private ApplicationContext context; |
||||
|
||||
private String managerDn; |
||||
|
||||
private String managerPassword; |
||||
|
||||
private EmbeddedLdapServerContainer container; |
||||
|
||||
/** |
||||
* Create an EmbeddedLdapServerContextSourceFactoryBean that will use an embedded LDAP |
||||
* server to perform LDAP authentication. This requires a dependency on |
||||
* `com.unboundid:unboundid-ldapsdk`. |
||||
* @return the EmbeddedLdapServerContextSourceFactoryBean |
||||
*/ |
||||
public static EmbeddedLdapServerContextSourceFactoryBean fromEmbeddedLdapServer() { |
||||
return new EmbeddedLdapServerContextSourceFactoryBean(); |
||||
} |
||||
|
||||
/** |
||||
* Specifies an LDIF to load at startup for an embedded LDAP server. The default is |
||||
* "classpath*:*.ldif". |
||||
* @param ldif the ldif to load at startup for an embedded LDAP server. |
||||
*/ |
||||
public void setLdif(String ldif) { |
||||
this.ldif = ldif; |
||||
} |
||||
|
||||
/** |
||||
* The port to connect to LDAP to (the default is 33389 or random available port if |
||||
* unavailable). Supplying 0 as the port indicates that a random available port should |
||||
* be selected. |
||||
* @param port the port to connect to |
||||
*/ |
||||
public void setPort(int port) { |
||||
this.port = port; |
||||
} |
||||
|
||||
/** |
||||
* Optional root suffix for the embedded LDAP server. Default is |
||||
* "dc=springframework,dc=org". |
||||
* @param root root suffix for the embedded LDAP server |
||||
*/ |
||||
public void setRoot(String root) { |
||||
this.root = root; |
||||
} |
||||
|
||||
/** |
||||
* Username (DN) of the "manager" user identity (i.e. "uid=admin,ou=system") which |
||||
* will be used to authenticate to an LDAP server. If omitted, anonymous access will |
||||
* be used. |
||||
* @param managerDn the username (DN) of the "manager" user identity used to |
||||
* authenticate to a LDAP server. |
||||
*/ |
||||
public void setManagerDn(String managerDn) { |
||||
this.managerDn = managerDn; |
||||
} |
||||
|
||||
/** |
||||
* The password for the manager DN. This is required if the |
||||
* {@link #setManagerDn(String)} is specified. |
||||
* @param managerPassword password for the manager DN |
||||
*/ |
||||
public void setManagerPassword(String managerPassword) { |
||||
this.managerPassword = managerPassword; |
||||
} |
||||
|
||||
@Override |
||||
public DefaultSpringSecurityContextSource getObject() throws Exception { |
||||
if (!ClassUtils.isPresent(UNBOUNDID_CLASSNAME, getClass().getClassLoader())) { |
||||
throw new IllegalStateException("Embedded LDAP server is not provided"); |
||||
} |
||||
this.container = getContainer(); |
||||
this.port = this.container.getPort(); |
||||
DefaultSpringSecurityContextSource contextSourceFromProviderUrl = new DefaultSpringSecurityContextSource( |
||||
"ldap://127.0.0.1:" + this.port + "/" + this.root); |
||||
if (this.managerDn != null) { |
||||
contextSourceFromProviderUrl.setUserDn(this.managerDn); |
||||
if (this.managerPassword == null) { |
||||
throw new IllegalStateException("managerPassword is required if managerDn is supplied"); |
||||
} |
||||
contextSourceFromProviderUrl.setPassword(this.managerPassword); |
||||
} |
||||
contextSourceFromProviderUrl.afterPropertiesSet(); |
||||
return contextSourceFromProviderUrl; |
||||
} |
||||
|
||||
@Override |
||||
public Class<?> getObjectType() { |
||||
return DefaultSpringSecurityContextSource.class; |
||||
} |
||||
|
||||
@Override |
||||
public void destroy() { |
||||
if (this.container instanceof Lifecycle) { |
||||
((Lifecycle) this.container).stop(); |
||||
} |
||||
} |
||||
|
||||
@Override |
||||
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { |
||||
this.context = applicationContext; |
||||
} |
||||
|
||||
private EmbeddedLdapServerContainer getContainer() { |
||||
if (!ClassUtils.isPresent(UNBOUNDID_CLASSNAME, getClass().getClassLoader())) { |
||||
throw new IllegalStateException("Embedded LDAP server is not provided"); |
||||
} |
||||
UnboundIdContainer unboundIdContainer = new UnboundIdContainer(this.root, this.ldif); |
||||
unboundIdContainer.setApplicationContext(this.context); |
||||
unboundIdContainer.setPort(getEmbeddedServerPort()); |
||||
unboundIdContainer.afterPropertiesSet(); |
||||
return unboundIdContainer; |
||||
} |
||||
|
||||
private int getEmbeddedServerPort() { |
||||
if (this.port == null) { |
||||
this.port = getDefaultEmbeddedServerPort(); |
||||
} |
||||
return this.port; |
||||
} |
||||
|
||||
private int getDefaultEmbeddedServerPort() { |
||||
try (ServerSocket serverSocket = new ServerSocket(DEFAULT_PORT)) { |
||||
return serverSocket.getLocalPort(); |
||||
} |
||||
catch (IOException ex) { |
||||
return RANDOM_PORT; |
||||
} |
||||
} |
||||
|
||||
} |
||||
@ -0,0 +1,41 @@
@@ -0,0 +1,41 @@
|
||||
/* |
||||
* Copyright 2002-2022 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 org.springframework.security.config.ldap; |
||||
|
||||
import org.springframework.ldap.core.support.BaseLdapPathContextSource; |
||||
import org.springframework.security.authentication.AuthenticationManager; |
||||
import org.springframework.security.ldap.authentication.BindAuthenticator; |
||||
|
||||
/** |
||||
* Creates an {@link AuthenticationManager} that can perform LDAP authentication using |
||||
* bind authentication. |
||||
* |
||||
* @author Eleftheria Stein |
||||
* @since 5.7 |
||||
*/ |
||||
public class LdapBindAuthenticationManagerFactory extends AbstractLdapAuthenticationManagerFactory<BindAuthenticator> { |
||||
|
||||
public LdapBindAuthenticationManagerFactory(BaseLdapPathContextSource contextSource) { |
||||
super(contextSource); |
||||
} |
||||
|
||||
@Override |
||||
protected BindAuthenticator createDefaultLdapAuthenticator() { |
||||
return new BindAuthenticator(getContextSource()); |
||||
} |
||||
|
||||
} |
||||
@ -0,0 +1,75 @@
@@ -0,0 +1,75 @@
|
||||
/* |
||||
* Copyright 2002-2022 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 org.springframework.security.config.ldap; |
||||
|
||||
import org.springframework.ldap.core.support.BaseLdapPathContextSource; |
||||
import org.springframework.security.authentication.AuthenticationManager; |
||||
import org.springframework.security.crypto.password.PasswordEncoder; |
||||
import org.springframework.security.ldap.authentication.PasswordComparisonAuthenticator; |
||||
import org.springframework.util.Assert; |
||||
|
||||
/** |
||||
* Creates an {@link AuthenticationManager} that can perform LDAP authentication using |
||||
* password comparison. |
||||
* |
||||
* @author Eleftheria Stein |
||||
* @since 5.7 |
||||
*/ |
||||
public class LdapPasswordComparisonAuthenticationManagerFactory |
||||
extends AbstractLdapAuthenticationManagerFactory<PasswordComparisonAuthenticator> { |
||||
|
||||
private PasswordEncoder passwordEncoder; |
||||
|
||||
private String passwordAttribute; |
||||
|
||||
public LdapPasswordComparisonAuthenticationManagerFactory(BaseLdapPathContextSource contextSource, |
||||
PasswordEncoder passwordEncoder) { |
||||
super(contextSource); |
||||
setPasswordEncoder(passwordEncoder); |
||||
} |
||||
|
||||
/** |
||||
* Specifies the {@link PasswordEncoder} to be used when authenticating with password |
||||
* comparison. |
||||
* @param passwordEncoder the {@link PasswordEncoder} to use |
||||
*/ |
||||
public void setPasswordEncoder(PasswordEncoder passwordEncoder) { |
||||
Assert.notNull(passwordEncoder, "passwordEncoder must not be null."); |
||||
this.passwordEncoder = passwordEncoder; |
||||
} |
||||
|
||||
/** |
||||
* The attribute in the directory which contains the user password. Only used when |
||||
* authenticating with password comparison. Defaults to "userPassword". |
||||
* @param passwordAttribute the attribute in the directory which contains the user |
||||
* password |
||||
*/ |
||||
public void setPasswordAttribute(String passwordAttribute) { |
||||
this.passwordAttribute = passwordAttribute; |
||||
} |
||||
|
||||
@Override |
||||
protected PasswordComparisonAuthenticator createDefaultLdapAuthenticator() { |
||||
PasswordComparisonAuthenticator ldapAuthenticator = new PasswordComparisonAuthenticator(getContextSource()); |
||||
if (this.passwordAttribute != null) { |
||||
ldapAuthenticator.setPasswordAttributeName(this.passwordAttribute); |
||||
} |
||||
ldapAuthenticator.setPasswordEncoder(this.passwordEncoder); |
||||
return ldapAuthenticator; |
||||
} |
||||
|
||||
} |
||||
Loading…
Reference in new issue