LdaptivePasswordMatcher.java
/*
* Copyright 2021 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
*
* http://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.bremersee.security.core.userdetails;
import java.util.Collections;
import lombok.AccessLevel;
import lombok.Getter;
import lombok.extern.slf4j.Slf4j;
import org.bremersee.data.ldaptive.LdaptiveOperations;
import org.ldaptive.CompareRequest;
import org.ldaptive.FilterTemplate;
import org.ldaptive.LdapEntry;
import org.ldaptive.SearchRequest;
import org.ldaptive.SearchScope;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.util.StringUtils;
/**
* The ldaptive password matcher.
*
* @author Christian Bremer
*/
@Slf4j
public class LdaptivePasswordMatcher implements PasswordEncoder {
@Getter(value = AccessLevel.PROTECTED)
private final LdaptiveOperations ldaptiveOperations;
@Getter(value = AccessLevel.PROTECTED)
private final String userBaseDn;
@Getter(value = AccessLevel.PROTECTED)
private final String userFindOneFilter;
@Getter(value = AccessLevel.PROTECTED)
private SearchScope userFindOneSearchScope = SearchScope.ONELEVEL;
@Getter(value = AccessLevel.PROTECTED)
private String userPasswordAttributeName = "userPassword";
@Getter(value = AccessLevel.PROTECTED)
private PasswordEncoder delegate = new LdaptivePasswordEncoder();
/**
* Instantiates a new ldaptive password matcher.
*
* @param ldaptiveOperations the ldaptive operations
* @param userBaseDn the user base dn
* @param userFindOneFilter the user find one filter
*/
public LdaptivePasswordMatcher(
LdaptiveOperations ldaptiveOperations,
String userBaseDn,
String userFindOneFilter) {
this.ldaptiveOperations = ldaptiveOperations;
this.userBaseDn = userBaseDn;
this.userFindOneFilter = userFindOneFilter;
}
/**
* Sets user find one search scope.
*
* @param userFindOneSearchScope the user find one search scope
*/
public void setUserFindOneSearchScope(SearchScope userFindOneSearchScope) {
if (userFindOneSearchScope != null) {
this.userFindOneSearchScope = userFindOneSearchScope;
}
}
/**
* Sets user password attribute name.
*
* @param userPasswordAttributeName the user password attribute name
*/
public void setUserPasswordAttributeName(String userPasswordAttributeName) {
if (StringUtils.hasText(userPasswordAttributeName)) {
this.userPasswordAttributeName = userPasswordAttributeName;
}
}
/**
* Sets delegate.
*
* @param delegate the delegate
*/
public void setDelegate(PasswordEncoder delegate) {
if (delegate != null) {
this.delegate = delegate;
}
}
@Override
public String encode(CharSequence rawPassword) {
return getDelegate().encode(rawPassword);
}
/**
* Checks whether the given raw password matches the value in the ldap store. Since the password attribute usually
* cannot be retrieved and cannot be stored in the user details, the comparison of the passwords is done by the ldap
* server. For this reason this password encoder implementation expects here the user name as second parameter instead
* of the encoded password from the user details.
*
* @param rawPassword the raw password
* @param userName the user name of the user
* @return {@code true} if the raw password matches the password in the ldap store, otherwise {@code false}
*/
@Override
public boolean matches(CharSequence rawPassword, String userName) {
if (!StringUtils.hasText(userName)) {
log.warn("Ldaptive password matcher: password does not match because there is no user name.");
return false;
}
String raw = rawPassword != null ? rawPassword.toString() : "";
boolean result = getLdaptiveOperations()
.findOne(SearchRequest.builder()
.dn(getUserBaseDn())
.filter(FilterTemplate.builder()
.filter(getUserFindOneFilter())
.parameters(userName)
.build())
.scope(getUserFindOneSearchScope())
.returnAttributes(Collections.emptyList())
.sizeLimit(1)
.build())
.map(LdapEntry::getDn)
.map(dn -> authenticate(dn, raw))
.orElse(false);
if (log.isDebugEnabled()) {
log.debug("Ldaptive password matcher: password matches for user ({})? {}", userName, result);
}
return result;
}
private boolean authenticate(String dn, String rawPassword) {
return getLdaptiveOperations().compare(CompareRequest.builder()
.dn(dn)
.name(getUserPasswordAttributeName())
.value(encode(rawPassword))
.build());
}
}