How override the default BCryptPasswordEncoder created through PasswordEncoderFactories? - spring-security

I know that in Spring Security would arise the following:
There was an unexpected error (type=Internal Server Error, status=500).
There is no PasswordEncoder mapped for the id "null"
java.lang.IllegalArgumentException: There is no PasswordEncoder mapped for the id "null"
And the solution is define a PasswordEncoder. For simplicity is possible define the following:
#Bean
PasswordEncoder encoder() {
return PasswordEncoderFactories.createDelegatingPasswordEncoder();
}
Now, behind the scene the createDelegatingPasswordEncoder() method is defined how (it so far until currently for Spring Security 5.4.2) (See the PasswordEncoderFactories class for more details):
#SuppressWarnings("deprecation")
public static PasswordEncoder createDelegatingPasswordEncoder() {
String encodingId = "bcrypt";
Map<String, PasswordEncoder> encoders = new HashMap<>();
encoders.put(encodingId, new BCryptPasswordEncoder());
encoders.put("ldap", new org.springframework.security.crypto.password.LdapShaPasswordEncoder());
encoders.put("MD4", new org.springframework.security.crypto.password.Md4PasswordEncoder());
encoders.put("MD5", new org.springframework.security.crypto.password.MessageDigestPasswordEncoder("MD5"));
encoders.put("noop", org.springframework.security.crypto.password.NoOpPasswordEncoder.getInstance());
encoders.put("pbkdf2", new Pbkdf2PasswordEncoder());
encoders.put("scrypt", new SCryptPasswordEncoder());
encoders.put("SHA-1", new org.springframework.security.crypto.password.MessageDigestPasswordEncoder("SHA-1"));
encoders.put("SHA-256",
new org.springframework.security.crypto.password.MessageDigestPasswordEncoder("SHA-256"));
encoders.put("sha256", new org.springframework.security.crypto.password.StandardPasswordEncoder());
encoders.put("argon2", new Argon2PasswordEncoder());
return new DelegatingPasswordEncoder(encodingId, encoders);
}
Now about the BCryptPasswordEncoder class, it works with some defaults such as:
version: $2a
strength: 10
What happens if declare the following:
#Bean
PasswordEncoder bcryptEncoder() {
return new BCryptPasswordEncoder(BCryptVersion.$2Y, 12);
}
How I can add or override the default BCryptPasswordEncoder created with the custom BCryptPasswordEncoder into the default settings? I want keep all the other defaults
Note: In the PasswordEncoderFactories class (for the createDelegatingPasswordEncoder method), the DelegatingPasswordEncoder class is used behind the scenes. That class does not offer an approach to override too.

If you are creating a new application, you very likely do not need DelegatingPasswordEncoder and instead should use an adaptive one-way function password encoder, for example BCryptPasswordEncoder.
To do this, you can expose a BCryptPasswordEncoder as a bean.
#Bean
PasswordEncoder bcryptEncoder() {
return new BCryptPasswordEncoder(BCryptVersion.$2Y, 12);
}
Then, when a user registers, you can encode their password using the BCryptPasswordEncoder before saving it to your data store. For example:
UserDetails userDetails = User
.withUsername(username)
.password(bcryptEncoder.encode(password))
.roles("USER")
.build();
If you are migrating an existing application, that is where DelegatingPasswordEncoder is useful.
The DelegatingPasswordEncoder allows for validating passwords in multiple formats. It uses the prefixed ID (e.g {bcrypt}) to look up which PasswordEncoder should be used.
Consider a legacy application that uses plaintext passwords.
To migrate the application, you would prefix the plaintext passwords with {noop} and use a DelegatingPasswordEncoder like the one below:
#Bean
public PasswordEncoder delegatingPasswordEncoder() {
Map<String, PasswordEncoder> encoders = new HashMap<>();
encoders.put("bcrypt", new BCryptPasswordEncoder(BCryptVersion.$2Y, 12));
encoders.put("noop", NoOpPasswordEncoder.getInstance());
return new DelegatingPasswordEncoder("bcrypt", encoders);
}
This DelegatingPasswordEncoder will encode and validate any newly created password using the BCryptPasswordEncoder, while still being able to validate the legacy plaintext passwords using the NoOpPasswordEncoder.
Spring Security provides the PasswordEncoderFactories.createDelegatingPasswordEncoder() method as a convenient default, but it is unlikely that your application uses that many different password encodings.
What is more likely, is that your application uses 2 different encodings, the legacy one (e.g noop) and the modern one (e.g bcrypt), in which case you can use a PasswordEncoder similar to the delegatingPasswordEncoder described above.
The point of these examples is to say that in most situations you do not need the defaults that are set in createDelegatingPasswordEncoder. However, if you still want to use the encoders from createDelegatingPasswordEncoder, except for bcrypt, you can use a PasswordEncoder that looks like this:
#Bean
public PasswordEncoder delegatingPasswordEncoder() {
Map<String, PasswordEncoder> encoders = new HashMap<>();
// Use this encoder for bcrypt
encoders.put("bcrypt", new BCryptPasswordEncoder(BCryptVersion.$2Y, 12));
DelegatingPasswordEncoder delegatingPasswordEncoder =
new DelegatingPasswordEncoder("bcrypt", encoders);
PasswordEncoder defaultDelegatingPasswordEncoder =
PasswordEncoderFactories.createDelegatingPasswordEncoder();
// If a password ID does not match "bcrypt", use defaultDelegatingPasswordEncoder
delegatingPasswordEncoder.setDefaultPasswordEncoderForMatches(defaultDelegatingPasswordEncoder);
return delegatingPasswordEncoder;
}

Related

Single resource server with multiple authorisation servers, one for each tenant

I am working on a Spring Boot application, which is basically a resource server. As of now, my application has one tenant, which gets authenticated with an authorization server, external to my application.
In order to achieve the same, as of now, I have made the following changes in my application:
config changes are as following:
spring.security.oauth2.client.registration.tenant1.client-id=abcd
spring.security.oauth2.client.registration.tenant1.client-authentication-method=basic
spring.security.oauth2.client.registration.tenant1.authorization-grant-type=authorization_code
myapp.oauth2.path=https://external.authorization.server/services/oauth2/
spring.security.oauth2.client.provider.tenant1.token-uri=${myapp.oauth2.path}token
spring.security.oauth2.client.provider.tenant1.authorization-uri=${myapp.oauth2.path}authorize
spring.security.oauth2.client.provider.tenant1.user-info-uri=${myapp.oauth2.path}userinfo
spring.security.oauth2.client.provider.tenant1.user-name-attribute=name
As of now, I am fetching client secrets from Vault, so I had to define the OAuth2 configuration as follows:
#EnableConfigurationProperties(OAuth2ClientProperties.class)
#Conditional(ClientsConfiguredCondition.class)
#Configuration
public class OAuth2Configuration {
static final String OAUTH2_CLIENT_SECRET_KEY = "oauth2_client_secret";
private static final Logger log = LoggerFactory.getLogger(OAuth2Configuration.class);
private static final String OAUTH2_REGISTRATION_MISSING =
"oAuth2 registration properties are missing";
private final ApplicationSecretProvider applicationSecretProvider;
private final Map<String, ClientAuthenticationMethod> clientAuthenticationMethodMap =
new HashMap<>();
private final String authenticationMethod;
public OAuth2Configuration(
#Value("${spring.security.oauth2.client.registration.tenant1.client-authentication-method}")
final String authenticationMethod,
final ApplicationSecretProvider applicationSecretProvider) {
this.authenticationMethod = authenticationMethod;
this.applicationSecretProvider = applicationSecretProvider;
this.clientAuthenticationMethodMap
.put(ClientAuthenticationMethod.POST.getValue(), ClientAuthenticationMethod.POST);
this.clientAuthenticationMethodMap
.put(ClientAuthenticationMethod.BASIC.getValue(), ClientAuthenticationMethod.BASIC);
this.clientAuthenticationMethodMap
.put(ClientAuthenticationMethod.NONE.getValue(), ClientAuthenticationMethod.NONE);
}
#Bean
public InMemoryClientRegistrationRepository getClientRegistrationRepository(
OAuth2ClientProperties properties) {
List<ClientRegistration> registrations = new ArrayList<>(
OAuth2ClientPropertiesRegistrationAdapter.getClientRegistrations(properties).values());
//We will have only one client registered for oAuth
if (CollectionUtils.isEmpty(registrations)) {
log.error(OAUTH2_REGISTRATION_MISSING);
throw new IllegalStateException(OAUTH2_REGISTRATION_MISSING);
}
ClientRegistration registration = registrations.get(0);
ClientRegistration.Builder builder = ClientRegistration.withClientRegistration(registration);
ClientAuthenticationMethod clientAuthenticationMethod =
getClientAuthenticationMethod(authenticationMethod);
ClientRegistration completeRegistration = builder
.clientSecret(applicationSecretProvider.getSecretForKey(OAUTH2_CLIENT_SECRET_KEY))
.clientAuthenticationMethod(clientAuthenticationMethod)
.build();
return new InMemoryClientRegistrationRepository(completeRegistration);
}
protected ClientAuthenticationMethod getClientAuthenticationMethod(String grantType) {
ClientAuthenticationMethod retValue = clientAuthenticationMethodMap.get(grantType);
if (retValue == null) {
return ClientAuthenticationMethod.NONE;
}
return retValue;
}
}
Then I extended DefaultOAuth2UserService in order to save user details in my application as follows:
#Component
public class CustomOAuth2UserService extends DefaultOAuth2UserService {
private UserRepository userRepository;
private AuthorityRepository authRepository;
#Autowired
public void setUserRepository(UserRepository userRepository) {
this.userRepository = userRepository;
}
#Autowired
public void setAuthorityRepository(AuthorityRepository
authorityRepository) {
this.authorityRepository = authorityRepository;
}
#Override
public OAuth2User loadUser(OAuth2UserRequest userRequest) {
DefaultOAuth2User oAuth2User = (DefaultOAuth2User) super.loadUser(userRequest);
Collection<GrantedAuthority> authorities = new HashSet<>(oAuth2User.getAuthorities());
Map<String, Object> attributes = oAuth2User.getAttributes();
...
return new DefaultOAuth2User(authorities, oAuth2User.getAttributes(), userNameAttributeName);
}
}
Security configuration is as follows:
#EnableWebSecurity
#Import(SecurityProblemSupport.class)
#ConditionalOnProperty(
value = "myapp.authentication.type",
havingValue = "oauth",
matchIfMissing = true
)
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
private final CustomOAuth2UserService customoAuth2UserService;
public SecurityConfiguration(CustomOAuth2UserService customoAuth2UserService) {
this.customoAuth2UserService = customoAuth2UserService;
}
public void configure(HttpSecurity http) throws Exception {
http
.csrf()
.authorizeRequests()
.antMatchers("/login**").permitAll()
.antMatchers("/manage/**").permitAll()
.antMatchers("/api/auth-info").permitAll()
.antMatchers("/api/**").authenticated()
.antMatchers("/management/health").permitAll()
.antMatchers("/management/info").permitAll()
.antMatchers("/management/prometheus").permitAll()
.antMatchers("/management/**").hasAuthority(AuthoritiesConstants.ADMIN)
.anyRequest().authenticated()
//.and().oauth2ResourceServer().jwt()
.and()
//.and()
.oauth2Login()
.redirectionEndpoint()
.baseUri("/oauth2**")
.and()
.failureUrl("/api/redirectToHome")
.userInfoEndpoint().userService(customoAuth2UserService);
http.cors().disable();
}
}
Now, I would like to onboard multiple tenants using OAuth2 as well. Say I want to onboard another tenant tenant2. In order to achieve this, I think, I need to do the following changes in the existing code base as follows:
adding config entries in the properties file as above:
spring.security.oauth2.client.registration.tenant2.client-id=efgh
spring.security.oauth2.client.registration.tenant2.client-authentication-method=basic
spring.security.oauth2.client.registration.tenant2.authorization-grant-type=authorization_code
spring.security.oauth2.client.provider.tenant2.token-uri=${myapp.oauth2.path}token
spring.security.oauth2.client.provider.tenant2.authorization-uri=${myapp.oauth2.path}authorize
spring.security.oauth2.client.provider.tenant2.user-info-uri=${myapp.oauth2.path}userinfo
spring.security.oauth2.client.provider.tenant2.user-name-attribute=name
I need to do changes in the security configuration class:
SecurityConfiguration and OAuth2 configuration class OAuth2Configuration as well. But I am not able to understand what should I add there in order to make my applications work seamlessly for multiple tenants.
In this context, I found this related post: Dynamically register OIDC client with Spring Security OAuth in a multi-tenant stup, but could not get any concrete idea regarding what changes should I do in the existing code base to make my application work in multi-tenancy set up.
Could anyone please help here?
I think there's a bit of confusion that it might help to clear up.
First, it seems that you are not actually building a resource server, as a resource server would require an access token for authentication. Using .oauth2Login() is for either OAuth 2.0 or OpenID Connect 1.0 login, which is a regular application in most respects except how you log in. You still have a browser session after login is successful, which you would not have in a resource server.
Second, configuring a static number of client registrations isn't really quite the same as building a multi-tenant application. Perhaps you're building up to that later, by demonstrating two clients. When configuring two clients using static configuration properties, nothing is really different from a single configuration, other than that there are two possible registrationIds.
Start by building a simple hello world application, such as the OAuth 2.0 Login Sample. If you add a second client registration to your properties, you'll notice that the auto-generated login page (/login) simply shows two links, one for each client. See docs for more on this.
The default URI for initiating the authorization_code flow is /oauth2/authorization/{registrationId}, which means navigating to /oauth2/authorization/abcd launches the first client's login flow. Navigating to /oauth2/authorization/efgh launches the second client's login flow. There's not really anything else needed to support multiple login clients other than understanding how to initiate login.
If you wish to support a fully multi-tenant login configuration, you would then provide a custom ClientRegistrationRepository, which you have done. The only difference is that you should no longer seek to configure clients through the Spring Boot properties, as that seems to be the point that is confusing in your example. If you want to use properties for some of the configuration, create your own configuration properties for your custom repository implementation. Typically at that point, all of this configuration would come from a database.
I would start with that progression (hello world, two statically configured clients, custom ClientRegistrationRepository) then proceed to add other custom components. It will help illustrate the differences at each point.

How do I pre-load my Spring Boot integration test with a user from my H2 database?

I'm using Spring Boot 2.1. I'm also using the H2 in memory database. I have created this file at src/test/resources/data.sql:
insert into roles (id, name) values ('1be4965cb4f311eaa2b76a0000c30600', 'USER');
insert into roles (id, name) values ('1be6b2acb4f311eaa2b76a0000c30600', 'ADMIN');
insert into privileges (id, name) values ('1be4965cb4f311eaa2b76a0000c30600', 'USER');
insert into privileges (id, name) values ('1be6b2acb4f311eaa2b76a0000c30600', 'SUPER_ADMIN');
insert into roles_privileges (role_id, privilege_id) VALUES ('1be4965cb4f311eaa2b76a0000c30600', '1be4965cb4f311eaa2b76a0000c30600');
insert into roles_privileges (role_id, privilege_id) VALUES ('1be6b2acb4f311eaa2b76a0000c30600', '1be6b2acb4f311eaa2b76a0000c30600');
insert into occasions (id, name) values ('97c625b8b63511ea9d386a0000c30600', 'Birthday');
insert into users (id, email, enabled, first_name, last_name, password, token_expired) values ('aa7625b8b63512929d386a0000c30600', 'me#example.com', true, 'lone', 'ranger', 'password', false);
insert into users_roles (user_id, role_id) values ('aa7625b8b63512929d386a0000c30600', '1be6b2acb4f311eaa2b76a0000c30600');
I would like to create a spring boot integration test to test the following method ...
#RestController
#RequestMapping("/api/cards")
public class CardController {
#Autowired
private CardService cardService;
#PostMapping
#ResponseStatus(code = HttpStatus.CREATED)
public void create(#RequestBody Card card, #AuthenticationPrincipal User loggedInUser) {
card.setAuthor(loggedInUser);
cardService.save(card);
}
and I would like to load the one user in my database, but I'm not quite sure the easiest way to do that. I tried "#WithMockUser" but that isn't loading this user ...
#SpringBootTest(classes = CardmaniaApplication.class,
webEnvironment = WebEnvironment.RANDOM_PORT)
public class CardControllerIntegrationTest {
#LocalServerPort
private int port;
#Autowired
private TestRestTemplate restTemplate;
#Autowired
private ICardRepository cardRepository;
#Autowired
private IUserRepository userRepository;
#Autowired
private IOccasionRepository occasionRepository;
#Autowired
private JwtTokenUtil jwtTokenUtil;
#Value("${jwt.http.request.header}")
private String tokenHeader;
#BeforeEach
void setup() {
UserDetails user = (UserDetails) userRepository.findAll().get(0);
final String token = jwtTokenUtil.generateToken(user);
restTemplate.getRestTemplate().setInterceptors(
Collections.singletonList((request, body, execution) -> {
request.getHeaders()
.add(this.tokenHeader, "Bearer " + token);
return execution.execute(request, body);
}));
}
#Test
#WithMockUser(authorities = {"ADMIN"})
void createCard() throws Exception {
final Card card = new Card();
final User author = userRepository.findAll().get(0);
card.setAuthor(author);
final Occasion occasion = occasionRepository.findAll().get(0);
card.setOccasion(occasion);
byte[] image = new byte[] {1, 1, 1, 1};
card.setImage(image);
ResponseEntity<String> responseEntity = this.restTemplate
.postForEntity("http://localhost:" + port + "/api/cards", card, String.class);
assertEquals(201, responseEntity.getStatusCodeValue());
List<Card> cards = cardRepository.findByOccasion(occasion);
assertThat(cards.size()).isEqualTo(1);
final Card cardEntity = cards.get(0);
assertThat(cardEntity.getImage()).isEqualTo(image);
}
}
I'm fairly new to spring boot and so unfamiliar with the simplest way to pre-load a user into my security principal.
I discovered the answer is to use the "#WithUserDetails" annotation
#Test
#WithUserDetails("me#example.com")
void registrationWorksThroughAllLayers() throws Exception {
This will use your autowired class that implements UserDetailsService and invoke its loadUserByUsername with the annotated value, "me#example.com," in the code above.
This setup is currently conflicting. One the one side you provide a mocked user for the SecurityContext using #WithMockUser and on the other side, you prepare a valid JWT token for actual authentication.
#WithMockUser is usually used in tests to e.g. easily access protected endpoints without creating a request with correct authentication information (like JWT or basic auth).
So you should pick one of the current approaches: Either go for mocking the user OR generate the JWT and access your endpoint.
The reason your #WithMockUser is not working currently, might be related to the default username Spring Security picks. If you want this to match your user in your database, consider configuring it: #WithMockUser(username="YOUR_USERNAME",roles={"USER","ADMIN"})
To debug your application further, you can temporarily add
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
to the endpoint you are testing and debug it to understand which user is now actually part of the SecurityContext.

Security Config - configureGlobal

Playing with Spring Session REST example, I'm curious is it possible to make the following flow:
1. In first time, an user passes its credentials, usename and password.
2. Spring Session will generate a token, puts it into Redis
3. Next time a user asks for some resources it passes its tiken.
My problem is that the following code is hardcoded:
code
#Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
auth.inMemoryAuthentication().withUser("user").password("password").roles("USER");
}
code`
How can I do it to be dynamic?
Thanks in advance.
Spring Session works independent of the security framework you choose, so the answer depends on which security framework you want to use. This means selecting the username / password for your users is totally independent of Spring Session.
Since the question is unrelated to Spring Session, you should consult the documentation of the security framework you choose. In this instance, it is Spring Security so you can consult Spring Security's documentation on authentication.
The first step is to determine how you want to authenticate.
For example, if you want to use JDBC authentication, you can use something like the following.
#Autowired
private DataSource dataSource;
#Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
auth
.jdbcAuthentication()
.dataSource(dataSource)
.withDefaultSchema()
.withUser("user").password("password").roles("USER").and()
.withUser("admin").password("password").roles("USER", "ADMIN");
}
By default jdbcAuthentication expects that:
select username, password, enabled from users where username = ?
will return the username, the password, and if that user is enabled. You can customize this query if you like using properties on jdbcAuthentication(). See the javadoc for details.
NOTE: It is important to understand that withDefaultSchema and withUser are really only intended for in memory databases since it will try to add the schema and the user every time. In a production environment your users and schema should be added using other mechanisms (i.e. liquibase).
The most flexible option is to implement a UserDetailsService. You can then look up users in any way you want. For example:
#Service
public class UserRepositoryUserDetailsService implements UserDetailsService {
private final UserRepository userRepository;
#Autowired
public UserRepositoryUserDetailsService(UserRepository userRepository) {
this.userRepository = userRepository;
}
#Override
public UserDetails loadUserByUsername(String username)
throws UsernameNotFoundException {
MyUser user = userRepository.findByEmail(username);
if(user == null) {
throw new UsernameNotFoundException("Could not find user " + username);
}
List<GrantedAuthority> authorities = convert(user.getRoles());
return new User(user.getUsername(), user.getPassword(), authorities);
}
}
Then you can configure it using:
#Autowired
public void configureGlobal(AuthenticationManagerBuilder auth, UserDetailsService userDetailsService) throws Exception {
auth
.userDetailsService(userDetailsService);
}
You can find a complete example of using a UserDetailsService in my Spring Security 4 talk.

Multiple AuthFactories in Dropwizard

I want to use two different authfactories in dropwizard, Basic and OAuth. Like this :-
BasicAuthFactory<UserA> authFactory = new BasicAuthFactory<UserA>(new IngestionConsoleAuthenticator(),"SUPER SECRET STUFF", UserA.class);
OAuthFactory<UserB> authFactory2 = new OAuthFactory<UserB>(new PSVAppAuthenticator(), "Secret", UserB.class);
environment.jersey().register(AuthFactory.binder(authFactory));
environment.jersey().register(AuthFactory.binder(authFactory2));
The resources with #Auth UserA works fine, but with #Auth UserB are reachable without any authentication. How can i make both work simultaneously?
http://www.dropwizard.io/manual/auth.html#chained-factories
#Override
public void run(ExampleConfiguration configuration,
Environment environment) {
ChainedAuthFactory<User> chainedFactory = new ChainedAuthFactory<>(
new BasicAuthFactory<>(new ExampleBasicAuthenticator(), "SUPER SECRET STUFF", User.class),
new OAuthFactory<>(new ExampleOAuthAuthenticator(), "SUPER SECRET STUFF", User.class));
environment.jersey().register(AuthFactory.binder(chainedFactory));
}
For this to work properly, all chained factories must produce the same
type of principal, here User.
Hint: The dropwizard documentation is good, so please just read the example code there. Example below is from Dropwizard 1.0.0 Documentation
#Override
public void run(ExampleConfiguration configuration,
Environment environment) {
AuthFilter basicCredentialAuthFilter = new BasicCredentialAuthFilter.Builder<>()
.setAuthenticator(new ExampleBasicAuthenticator())
.setAuthorizer(new ExampleAuthorizer())
.setPrefix("Basic")
.buildAuthFilter();
AuthFilter oauthCredentialAuthFilter = new OAuthCredentialAuthFilter.Builder<>()
.setAuthenticator(new ExampleOAuthAuthenticator())
.setAuthorizer(new ExampleAuthorizer())
.setPrefix("Bearer")
.buildAuthFilter();
List<AuthFilter> filters = Lists.newArrayList(basicCredentialAuthFilter, oauthCredentialAuthFilter);
environment.jersey().register(new AuthDynamicFeature(new ChainedAuthFilter(filters)));
environment.jersey().register(RolesAllowedDynamicFeature.class);
//If you want to use #Auth to inject a custom Principal type into your resource
environment.jersey().register(new AuthValueFactoryProvider.Binder<>(User.class));
}

Lookup LDAP entries from inside LdapAuthoritiesPopulator

I'm using Spring Security and spring-security-ldap for authentication in a Spring Boot application.
I want to implement an LdapAuthoritiesPopulator that looks up some entries from the LDAP server to decide the roles of the user. These entries are not under the user, so the userData object provided to getGrantedAuthorities method is not enough. (I have no control over the LDAP server so I cannot add the roles to user entries).
I thought about injecting the ContextSource that Spring Security creates when calling auth.ldapAuthentication().contextSource() (see (1) in the code below) into the LdapAuthoritiesPopulator. But that doesn't work because the LdapAuthoritiesPopulator is created before the WebSecurityConfigurerAdapter, and so the ContextSource does not exist yet.
My WebSecurityConfigurerAdapter contains the following method:
#Override
public void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.ldapAuthentication()
.contextSource() // (1)
.url("ldap://example.org/o=organization")
.and()
.ldapAuthoritiesPopulator(ldapAuthoritiesPopulator)
.userSearchFilter("uid={0}");
}
ldapAuthoritiesPopulator is autowired, the class is currently a dummy implementation that simply adds ROLE_USER:
#Component
public class CustomLdapAuthoritiesPopulator implements LdapAuthoritiesPopulator {
#Override
public Collection<? extends GrantedAuthority> getGrantedAuthorities(
DirContextOperations userData, String username) {
// I want to look up some entries in LDAP here, so I need a ContextSource
// but I can't inject it because it does not exist when this bean is created
Collection<GrantedAuthority> authorities = new ArrayList<GrantedAuthority>();
authorities.add(new SimpleGrantedAuthority("USER"));
return authorities;
}
}
You don't need the roles populated till after the application is running (I suppose). So one thing that would work would be to inject the ApplicationContext instead of the ContextSource and look up the latter lazily. If that works there might be a truck with #Lazy.

Resources