<dependency>
<groupId>org.apereo.cas</groupId>
<artifactId>cas-server-support-ldap-core</artifactId>
<version>${cas.version}</version>
</dependency>
#########################ldap authentication#######################################
#认证方式
#cas.authn.ldap[0].type=AD|DIRECT|ANONYMOUS|AUTHENTICATED
cas.authn.ldap[0].type=AUTHENTICATED
#LDAP服务地址,如果支持SSL,地址为 ldaps://127.0.0.1:689
cas.authn.ldap[0].ldapUrl=ldap://127.0.0.1:389
#是否使用SSL
cas.authn.ldap[0].useSsl=false
cas.authn.ldap[0].useStartTls=false
cas.authn.ldap[0].connectTimeout=5000
#LDAP中基础DN
cas.authn.ldap[0].baseDn=ou=people,dc=xxx,dc=cn
#用户名匹配规则,简单的可以只写成uid={user}
cas.authn.ldap[0].searchFilter=(&(|(cn={user})(uid={user})(xjtuIDNumber={user})(mobile={user})(mail={user}))(xjtuAccountlocked=FALSE))
cas.authn.ldap[0].subtreeSearch=true
#cas.authn.ldap[0].usePasswordPolicy=false
#LDAP的DN
cas.authn.ldap[0].bindDn=cn=xxxadmin,dc=xxx,dc=cn
#LDAP的密码
cas.authn.ldap[0].bindCredential=111
cas.authn.ldap[0].principalAttributeId=uid
cas.authn.ldap[0].principalAttributePassword=userPassword
##默认加密策略,通过encodingAlgorithm来指定算法,默认NONE不加密
#原来这里是在密码验证之前对密码再做一次encode,如果配置为NONE,则什么都不做,
#直接使用默认的SHA加密方式(AbstractCompareAuthenticationHandler里面配置)加密密码后与LDAP里面的密码比对,
#如果配置为DEFAULT则先用配置的#encodingAlgorithm加密一次,再使用默认的SHA加密方式加密后与LDAP里面的密码比对
cas.authn.ldap[0].passwordEncoder.type=NONE
cas.authn.ldap[0].passwordEncoder.characterEncoding=UTF-8
cas.authn.ldap[0].passwordEncoder.encodingAlgorithm=SHA
#登入成功后可以查看到的信息
cas.authn.ldap[0].principalAttributeList=uid:accountID,sn:userNo,cn:userAccount,displayName:userName,description,departmentNumber:depNO,mail:email
cas.authn.ldap[0].allowMultiplePrincipalAttributeValues=true
cas.authn.ldap[0].allowMissingPrincipalAttributeValue=true
package gds.application.cas.config.principal;
import com.google.common.collect.Multimap;
import gds.application.cas.authentication.adaptors.CustomLdapAuthenticationHandler;
import gds.application.cas.model.usercenter.service.UserService;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.apereo.cas.authentication.AuthenticationEventExecutionPlanConfigurer;
import org.apereo.cas.authentication.AuthenticationHandler;
import org.apereo.cas.authentication.AuthenticationPasswordPolicyHandlingStrategy;
import org.apereo.cas.authentication.CoreAuthenticationUtils;
import org.apereo.cas.authentication.principal.PrincipalFactory;
import org.apereo.cas.authentication.principal.PrincipalFactoryUtils;
import org.apereo.cas.authentication.principal.PrincipalNameTransformerUtils;
import org.apereo.cas.authentication.principal.PrincipalResolver;
import org.apereo.cas.authentication.support.DefaultLdapAccountStateHandler;
import org.apereo.cas.authentication.support.password.DefaultPasswordPolicyHandlingStrategy;
import org.apereo.cas.authentication.support.password.GroovyPasswordPolicyHandlingStrategy;
import org.apereo.cas.authentication.support.OptionalWarningLdapAccountStateHandler;
import org.apereo.cas.authentication.support.RejectResultCodeLdapPasswordPolicyHandlingStrategy;
import org.apereo.cas.authentication.support.password.PasswordEncoderUtils;
import org.apereo.cas.authentication.support.password.PasswordPolicyConfiguration;
import org.apereo.cas.configuration.CasConfigurationProperties;
import org.apereo.cas.configuration.model.support.ldap.LdapAuthenticationProperties;
import org.apereo.cas.configuration.model.support.ldap.LdapPasswordPolicyProperties;
import org.apereo.cas.services.ServicesManager;
import org.apereo.cas.util.CollectionUtils;
import org.apereo.cas.util.LdapUtils;
import org.ldaptive.auth.AuthenticationResponse;
import org.ldaptive.auth.AuthenticationResponseHandler;
import org.ldaptive.auth.Authenticator;
import org.ldaptive.auth.ext.ActiveDirectoryAuthenticationResponseHandler;
import org.ldaptive.auth.ext.EDirectoryAuthenticationResponseHandler;
import org.ldaptive.auth.ext.FreeIPAAuthenticationResponseHandler;
import org.ldaptive.auth.ext.PasswordExpirationAuthenticationResponseHandler;
import org.ldaptive.auth.ext.PasswordPolicyAuthenticationResponseHandler;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.Resource;
import java.time.Period;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.function.Predicate;
@Configuration("customldapAuthenticationConfiguration")
@EnableConfigurationProperties(CasConfigurationProperties.class)
@Slf4j
public class CustomLdapAuthenticationConfiguration {
private static final Logger LOGGER = LoggerFactory.getLogger(CustomLdapAuthenticationConfiguration.class);
@Autowired
private CasConfigurationProperties casProperties;
@Autowired
@Qualifier("personDirectoryPrincipalResolver")
private PrincipalResolver personDirectoryPrincipalResolver;
@Autowired
@Qualifier("servicesManager")
private ServicesManager servicesManager;
@Autowired
private UserService userService;
@ConditionalOnMissingBean(name = "ldapPrincipalFactory")
@Bean
public PrincipalFactory ldapPrincipalFactory() {
return PrincipalFactoryUtils.newPrincipalFactory();
}
@Bean
public Collection<AuthenticationHandler> ldapAuthenticationHandlers() {
final Collection<AuthenticationHandler> handlers = new HashSet<>();
casProperties.getAuthn().getLdap()
.stream()
.filter(ldapInstanceConfigurationPredicate())
.forEach(l -> {
final Multimap<String, Object> multiMapAttributes =
CoreAuthenticationUtils.transformPrincipalAttributesListIntoMultiMap(l.getPrincipalAttributeList());
LOGGER.debug("Created and mapped principal attributes [{}] for [{}]...", multiMapAttributes, l.getLdapUrl());
LOGGER.debug("Creating LDAP authenticator for [{}] and baseDn [{}]", l.getLdapUrl(), l.getBaseDn());
final Authenticator authenticator = LdapUtils.newLdaptiveAuthenticator(l);
LOGGER.debug("Ldap authenticator configured with return attributes [{}] for [{}] and baseDn [{}]",
multiMapAttributes.keySet(), l.getLdapUrl(), l.getBaseDn());
LOGGER.debug("Creating LDAP password policy handling strategy for [{}]", l.getLdapUrl());
final AuthenticationPasswordPolicyHandlingStrategy strategy = createLdapPasswordPolicyHandlingStrategy(l);
LOGGER.debug("Creating LDAP authentication handler for [{}]", l.getLdapUrl());
final CustomLdapAuthenticationHandler handler = new CustomLdapAuthenticationHandler(l.getName(),
servicesManager, userService, ldapPrincipalFactory(), casProperties.getCustom(), l.getOrder(), authenticator, strategy);
handler.setCollectDnAttribute(l.isCollectDnAttribute());
final List<String> additionalAttributes = l.getAdditionalAttributes();
if (StringUtils.isNotBlank(l.getPrincipalAttributeId())) {
additionalAttributes.add(l.getPrincipalAttributeId());
}
if (StringUtils.isNotBlank(l.getPrincipalDnAttributeName())) {
handler.setPrincipalDnAttributeName(l.getPrincipalDnAttributeName());
}
handler.setAllowMultiplePrincipalAttributeValues(l.isAllowMultiplePrincipalAttributeValues());
handler.setAllowMissingPrincipalAttributeValue(l.isAllowMissingPrincipalAttributeValue());
handler.setPasswordEncoder(PasswordEncoderUtils.newPasswordEncoder(l.getPasswordEncoder()));
handler.setPrincipalNameTransformer(PrincipalNameTransformerUtils.newPrincipalNameTransformer(l.getPrincipalTransformation()));
if (StringUtils.isNotBlank(l.getCredentialCriteria())) {
LOGGER.debug("Ldap authentication for [{}] is filtering credentials by [{}]",
l.getLdapUrl(), l.getCredentialCriteria());
handler.setCredentialSelectionPredicate(CoreAuthenticationUtils.newCredentialSelectionPredicate(l.getCredentialCriteria()));
}
if (StringUtils.isBlank(l.getPrincipalAttributeId())) {
LOGGER.debug("No principal id attribute is found for LDAP authentication via [{}]", l.getLdapUrl());
} else {
handler.setPrincipalIdAttribute(l.getPrincipalAttributeId());
LOGGER.debug("Using principal id attribute [{}] for LDAP authentication via [{}]", l.getPrincipalAttributeId(), l.getLdapUrl());
}
final LdapPasswordPolicyProperties passwordPolicy = l.getPasswordPolicy();
if (passwordPolicy.isEnabled()) {
LOGGER.debug("Password policy is enabled for [{}]. Constructing password policy configuration", l.getLdapUrl());
final PasswordPolicyConfiguration cfg = createLdapPasswordPolicyConfiguration(passwordPolicy, authenticator, multiMapAttributes);
handler.setPasswordPolicyConfiguration(cfg);
}
final Map<String, Object> attributes = CollectionUtils.wrap(multiMapAttributes);
handler.setPrincipalAttributeMap(attributes);
LOGGER.debug("Initializing LDAP authentication handler for [{}]", l.getLdapUrl());
handler.initialize();
handlers.add(handler);
});
return handlers;
}
private static Predicate<LdapAuthenticationProperties> ldapInstanceConfigurationPredicate() {
return l -> {
if (l.getType() == null) {
LOGGER.warn("Skipping LDAP authentication entry since no type is defined");
return false;
}
if (StringUtils.isBlank(l.getLdapUrl())) {
LOGGER.warn("Skipping LDAP authentication entry since no LDAP url is defined");
return false;
}
return true;
};
}
private AuthenticationPasswordPolicyHandlingStrategy<AuthenticationResponse, PasswordPolicyConfiguration>
createLdapPasswordPolicyHandlingStrategy(final LdapAuthenticationProperties l) {
if (l.getPasswordPolicy().getStrategy() == LdapPasswordPolicyProperties.PasswordPolicyHandlingOptions.REJECT_RESULT_CODE) {
LOGGER.debug("Created LDAP password policy handling strategy based on blacklisted authentication result codes");
return new RejectResultCodeLdapPasswordPolicyHandlingStrategy();
}
final Resource location = l.getPasswordPolicy().getGroovy().getLocation();
if (l.getPasswordPolicy().getStrategy() == LdapPasswordPolicyProperties.PasswordPolicyHandlingOptions.GROOVY && location != null) {
LOGGER.debug("Created LDAP password policy handling strategy based on Groovy script [{}]", location);
return new GroovyPasswordPolicyHandlingStrategy(location);
}
LOGGER.debug("Created default LDAP password policy handling strategy");
return new DefaultPasswordPolicyHandlingStrategy();
}
private PasswordPolicyConfiguration createLdapPasswordPolicyConfiguration(final LdapPasswordPolicyProperties passwordPolicy,
final Authenticator authenticator,
final Multimap<String, Object> attributes) {
final PasswordPolicyConfiguration cfg = new PasswordPolicyConfiguration(passwordPolicy);
final Set<AuthenticationResponseHandler> handlers = new HashSet<>();
final String customPolicyClass = passwordPolicy.getCustomPolicyClass();
if (StringUtils.isNotBlank(customPolicyClass)) {
try {
LOGGER.debug("Configuration indicates use of a custom password policy handler [{}]", customPolicyClass);
final Class<AuthenticationResponseHandler> clazz = (Class<AuthenticationResponseHandler>) Class.forName(customPolicyClass);
handlers.add(clazz.getDeclaredConstructor().newInstance());
} catch (final Exception e) {
LOGGER.warn("Unable to construct an instance of the password policy handler", e);
}
}
LOGGER.debug("Password policy authentication response handler is set to accommodate directory type: [{}]", passwordPolicy.getType());
switch (passwordPolicy.getType()) {
case AD:
handlers.add(new ActiveDirectoryAuthenticationResponseHandler(Period.ofDays(cfg.getPasswordWarningNumberOfDays())));
Arrays.stream(ActiveDirectoryAuthenticationResponseHandler.ATTRIBUTES).forEach(a -> {
LOGGER.debug("Configuring authentication to retrieve password policy attribute [{}]", a);
attributes.put(a, a);
});
break;
case FreeIPA:
Arrays.stream(FreeIPAAuthenticationResponseHandler.ATTRIBUTES).forEach(a -> {
LOGGER.debug("Configuring authentication to retrieve password policy attribute [{}]", a);
attributes.put(a, a);
});
handlers.add(new FreeIPAAuthenticationResponseHandler(
Period.ofDays(cfg.getPasswordWarningNumberOfDays()), cfg.getLoginFailures()));
break;
case EDirectory:
Arrays.stream(EDirectoryAuthenticationResponseHandler.ATTRIBUTES).forEach(a -> {
LOGGER.debug("Configuring authentication to retrieve password policy attribute [{}]", a);
attributes.put(a, a);
});
handlers.add(new EDirectoryAuthenticationResponseHandler(Period.ofDays(cfg.getPasswordWarningNumberOfDays())));
break;
default:
handlers.add(new PasswordPolicyAuthenticationResponseHandler());
handlers.add(new PasswordExpirationAuthenticationResponseHandler());
break;
}
authenticator.setAuthenticationResponseHandlers((AuthenticationResponseHandler[]) handlers.toArray(new AuthenticationResponseHandler[0]));
LOGGER.debug("LDAP authentication response handlers configured are: [{}]", handlers);
if (!passwordPolicy.isAccountStateHandlingEnabled()) {
cfg.setAccountStateHandler((response, configuration) -> new ArrayList<>(0));
LOGGER.debug("Handling LDAP account states is disabled via CAS configuration");
} else if (StringUtils.isNotBlank(passwordPolicy.getWarningAttributeName()) && StringUtils.isNotBlank(passwordPolicy.getWarningAttributeValue())) {
final OptionalWarningLdapAccountStateHandler accountHandler = new OptionalWarningLdapAccountStateHandler();
accountHandler.setDisplayWarningOnMatch(passwordPolicy.isDisplayWarningOnMatch());
accountHandler.setWarnAttributeName(passwordPolicy.getWarningAttributeName());
accountHandler.setWarningAttributeValue(passwordPolicy.getWarningAttributeValue());
accountHandler.setAttributesToErrorMap(passwordPolicy.getPolicyAttributes());
cfg.setAccountStateHandler(accountHandler);
LOGGER.debug("Configuring an warning account state handler for LDAP authentication for warning attribute [{}] and value [{}]",
passwordPolicy.getWarningAttributeName(), passwordPolicy.getWarningAttributeValue());
} else {
final DefaultLdapAccountStateHandler accountHandler = new DefaultLdapAccountStateHandler();
accountHandler.setAttributesToErrorMap(passwordPolicy.getPolicyAttributes());
cfg.setAccountStateHandler(accountHandler);
LOGGER.debug("Configuring the default account state handler for LDAP authentication");
}
return cfg;
}
/**
* 注册验证器
* @param plan
*/
@ConditionalOnMissingBean(name = "ldapAuthenticationEventExecutionPlanConfigurer")
@Bean
public AuthenticationEventExecutionPlanConfigurer ldapAuthenticationEventExecutionPlanConfigurer() {
return plan -> ldapAuthenticationHandlers().forEach(handler -> {
LOGGER.info("Registering LDAP authentication for [{}]", handler.getName());
plan.registerAuthenticationHandlerWithPrincipalResolver(handler, personDirectoryPrincipalResolver);
});
}
}
(2)编写抽象身份验证处理程序,方便后续集成其它认证方式,将公共的代码写在一个地方。可自定义数据库认证等等
package gds.application.cas.authentication.adaptors;
import gds.application.cas.common.constants.CustomWebConstants;
import gds.application.cas.exception.AccountUnusedLockedException;
import gds.application.cas.exception.WeakPasswordWarnException;
import gds.application.cas.model.usercenter.service.UserService;
import lombok.Getter;
import lombok.Setter;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.apereo.cas.authentication.AuthenticationHandlerExecutionResult;
import org.apereo.cas.authentication.PreventedException;
import org.apereo.cas.authentication.UsernamePasswordCredential;
import org.apereo.cas.authentication.handler.support.AbstractUsernamePasswordAuthenticationHandler;
import org.apereo.cas.authentication.principal.PrincipalFactory;
import org.apereo.cas.configuration.model.support.custom.CasCustomProperties;
import org.apereo.cas.services.ServicesManager;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import javax.security.auth.login.AccountNotFoundException;
import javax.servlet.http.HttpServletRequest;
import java.security.GeneralSecurityException;
import java.util.HashMap;
import java.util.Map;
@Slf4j
@Setter
@Getter
public abstract class AbstractCustomAuthenticationHandler extends AbstractUsernamePasswordAuthenticationHandler {
/**
* Mapping of LDAP attribute name to principal attribute name.
*/
protected Map<String, Object> principalAttributeMap = new HashMap<>();
public final UserService userService;
public final CasCustomProperties casCustomProperties;
public AbstractCustomAuthenticationHandler(String name, ServicesManager servicesManager, UserService userService, PrincipalFactory principalFactory, CasCustomProperties casCustomProperties, Integer order) {
super(name, servicesManager, principalFactory, order);
this.userService = userService;
this.casCustomProperties = casCustomProperties;
}
@Override
protected AuthenticationHandlerExecutionResult authenticateUsernamePasswordInternal(UsernamePasswordCredential credential, String originalPassword) throws GeneralSecurityException, PreventedException {
final UsernamePasswordCredential originalUserPass = (UsernamePasswordCredential) credential;
final UsernamePasswordCredential userPass = new UsernamePasswordCredential(originalUserPass.getUsername(), originalUserPass.getPassword());
HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
//这里可以自定义在检验密码之前做一些操作
return beforeAuthenticationUsernamePasswordInternal(userPass, originalUserPass.getPassword());
}
protected abstract AuthenticationHandlerExecutionResult beforeAuthenticationUsernamePasswordInternal(UsernamePasswordCredential credential,
String originalPassword) throws GeneralSecurityException, PreventedException;
}
(3)编写身份验证处理程序
package gds.application.cas.authentication.adaptors;
import gds.application.cas.common.constants.CustomWebConstants;
import gds.application.cas.model.usercenter.service.UserService;
import lombok.Setter;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.apereo.cas.authentication.*;
import org.apereo.cas.authentication.principal.Principal;
import org.apereo.cas.authentication.principal.PrincipalFactory;
import org.apereo.cas.configuration.model.support.custom.CasCustomProperties;
import org.apereo.cas.services.ServicesManager;
import org.apereo.cas.util.CollectionUtils;
import org.ldaptive.LdapAttribute;
import org.ldaptive.LdapEntry;
import org.ldaptive.LdapException;
import org.ldaptive.ReturnAttributes;
import org.ldaptive.auth.AuthenticationRequest;
import org.ldaptive.auth.AuthenticationResponse;
import org.ldaptive.auth.AuthenticationResultCode;
import org.ldaptive.auth.Authenticator;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import javax.security.auth.login.AccountNotFoundException;
import javax.security.auth.login.FailedLoginException;
import javax.security.auth.login.LoginException;
import javax.servlet.http.HttpServletRequest;
import java.security.GeneralSecurityException;
import java.util.Collection;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
/**
* LDAP authentication handler that uses the ldaptive {@code Authenticator} component underneath.
* This handler provides simple attribute resolution machinery by reading attributes from the entry
* corresponding to the DN of the bound user (in the bound security context) upon successful authentication.
*
* @author Marvin S. Addison
* @since 4.0.0
*/
@Slf4j
@Setter
public class CustomLdapAuthenticationHandler extends AbstractCustomAuthenticationHandler {
private static final Logger LOGGER = LoggerFactory.getLogger(CustomLdapAuthenticationHandler.class);
/**
* Performs LDAP authentication given username/password.
**/
private final Authenticator authenticator;
/**
* Name of attribute to be used for resolved principal.
*/
private String principalIdAttribute;
/**
* Flag indicating whether multiple values are allowed fo principalIdAttribute.
*/
private boolean allowMultiplePrincipalAttributeValues;
/**
* Flag to indicate whether CAS should block authentication
* if a specific/configured principal id attribute is not found.
*/
private boolean allowMissingPrincipalAttributeValue = true;
/**
* Set of LDAP attributes fetch from an entry as part of the authentication process.
*/
private String[] authenticatedEntryAttributes = ReturnAttributes.NONE.value();
private boolean collectDnAttribute;
/**
* Name of attribute to be used for principal's DN.
*/
private String principalDnAttributeName = "principalLdapDn";
/**
* Creates a new authentication handler that delegates to the given authenticator.
*
* @param name the name
* @param servicesManager the services manager
* @param principalFactory the principal factory
* @param order the order
* @param authenticator Ldaptive authenticator component.
* @param strategy the strategy
*/
public CustomLdapAuthenticationHandler(final String name, final ServicesManager servicesManager, UserService userService, final PrincipalFactory principalFactory, CasCustomProperties casCustomProperties, final Integer order, final Authenticator authenticator,
final AuthenticationPasswordPolicyHandlingStrategy strategy) {
super(name, servicesManager, userService, principalFactory, casCustomProperties, order);
this.authenticator = authenticator;
this.passwordPolicyHandlingStrategy = strategy;
}
@Override
protected AuthenticationHandlerExecutionResult beforeAuthenticationUsernamePasswordInternal(final UsernamePasswordCredential upc,
final String originalPassword) throws GeneralSecurityException, PreventedException {
final AuthenticationResponse response;
HttpServletRequest httpRequest = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
final String username = upc.getUsername();
final String password = upc.getPassword();
try {
final AuthenticationRequest request = new AuthenticationRequest(username,
new org.ldaptive.Credential(password), authenticatedEntryAttributes);
response = authenticator.authenticate(request);
} catch (final LdapException e) {
LOGGER.trace(e.getMessage(), e);
throw new PreventedException("Unexpected LDAP error", e);
}
if (AuthenticationResultCode.DN_RESOLUTION_FAILURE == response.getAuthenticationResultCode()) {
throw new AccountNotFoundException(username + " not found.");
}
try {
if (!passwordPolicyHandlingStrategy.supports(response)) {
throw new FailedLoginException("用户名或密码错误!");
}
final List<MessageDescriptor> messageList = passwordPolicyHandlingStrategy.handle(response, getPasswordPolicyConfiguration());
if (response.getResult()) {
// 在此可增加账号安全检测流程
final Principal principal = createPrincipal(username, response.getLdapEntry());
return createHandlerResult(upc, principal, messageList);
}
throw new FailedLoginException("用户名或密码错误!");
} catch (FailedLoginException e){
LOGGER.error("登录失败[{}]",e.getMessage());
throw e;
}
}
/**
* Creates a CAS principal with attributes if the LDAP entry contains principal attributes.
*
* @param username Username that was successfully authenticated which is used for principal ID when principal id is not specified.
* @param ldapEntry LDAP entry that may contain principal attributes.
* @return Principal if the LDAP entry contains at least a principal ID attribute value, null otherwise.
* @throws LoginException On security policy errors related to principal creation.
*/
protected Principal createPrincipal(final String username, final LdapEntry ldapEntry) throws LoginException {
LOGGER.debug("Creating LDAP principal for [{}] based on [{}] and attributes [{}]", username, ldapEntry.getDn(),
ldapEntry.getAttributeNames());
final String id = getLdapPrincipalIdentifier(username, ldapEntry);
LOGGER.debug("LDAP principal identifier created is [{}]", id);
final Map<String, Object> attributeMap = collectAttributesForLdapEntry(ldapEntry, id);
LOGGER.debug("Created LDAP principal for id [{}] and [{}] attributes", id, attributeMap.size());
return this.principalFactory.createPrincipal(id, attributeMap);
}
/**
* Collect attributes for ldap entry.
*
* @param ldapEntry the ldap entry
* @param username the username
* @return the map
*/
protected Map<String, Object> collectAttributesForLdapEntry(final LdapEntry ldapEntry, final String username) {
final Map<String, Object> attributeMap = new LinkedHashMap<>(this.principalAttributeMap.size());
LOGGER.debug("The following attributes are requested to be retrieved and mapped: [{}]", attributeMap.keySet());
this.principalAttributeMap.forEach((key, attributeNames) -> {
final LdapAttribute attr = ldapEntry.getAttribute(key);
if (attr != null) {
LOGGER.debug("Found principal attribute: [{}]", attr);
final Collection<String> names = (Collection<String>) attributeNames;
if (names.isEmpty()) {
LOGGER.debug("Principal attribute [{}] is collected as [{}]", attr, key);
attributeMap.put(key, CollectionUtils.wrap(attr.getStringValues()));
} else {
names.forEach(s -> {
LOGGER.debug("Principal attribute [{}] is virtually remapped/renamed to [{}]", attr, s);
attributeMap.put(s, CollectionUtils.wrap(attr.getStringValues()));
});
}
} else {
LOGGER.warn("Requested LDAP attribute [{}] could not be found on the resolved LDAP entry for [{}]", key, ldapEntry.getDn());
}
});
if (this.collectDnAttribute) {
LOGGER.debug("Recording principal DN attribute as [{}]", this.principalDnAttributeName);
attributeMap.put(this.principalDnAttributeName, ldapEntry.getDn());
}
return attributeMap;
}
/**
* Gets ldap principal identifier. If the principal id attribute is defined, it's retrieved.
* If no attribute value is found, a warning is generated and the provided username is used instead.
* If no attribute is defined, username is used instead.
*
* @param username the username
* @param ldapEntry the ldap entry
* @return the ldap principal identifier
* @throws LoginException in case the principal id cannot be determined.
*/
protected String getLdapPrincipalIdentifier(final String username, final LdapEntry ldapEntry) throws LoginException {
if (StringUtils.isNotBlank(this.principalIdAttribute)) {
final LdapAttribute principalAttr = ldapEntry.getAttribute(this.principalIdAttribute);
if (principalAttr == null || principalAttr.size() == 0) {
if (this.allowMissingPrincipalAttributeValue) {
LOGGER.warn("The principal id attribute [{}] is not found. CAS cannot construct the final authenticated principal "
+ "if it's unable to locate the attribute that is designated as the principal id. "
+ "Attributes available on the LDAP entry are [{}]. Since principal id attribute is not available, CAS will "
+ "fall back to construct the principal based on the provided user id: [{}]",
this.principalIdAttribute, ldapEntry.getAttributes(), username);
return username;
}
LOGGER.error("The principal id attribute [{}] is not found. CAS is configured to disallow missing principal attributes",
this.principalIdAttribute);
throw new LoginException("Principal id attribute is not found for " + principalAttr);
}
final String value = principalAttr.getStringValue();
if (principalAttr.size() > 1) {
if (!this.allowMultiplePrincipalAttributeValues) {
throw new LoginException("Multiple principal values are not allowed: " + principalAttr);
}
LOGGER.warn("Found multiple values for principal id attribute: [{}]. Using first value=[{}].", principalAttr, value);
}
LOGGER.debug("Retrieved principal id attribute [{}]", value);
return value;
}
LOGGER.debug("Principal id attribute is not defined. Using the default provided user id [{}]", username);
return username;
}
/**
* Initialize the handler, setup the authentication entry attributes.
*/
public void initialize() {
/*
* Use a set to ensure we ignore duplicates.
*/
final Set<String> attributes = new HashSet<>();
LOGGER.debug("Initializing LDAP attribute configuration...");
if (StringUtils.isNotBlank(this.principalIdAttribute)) {
LOGGER.debug("Configured to retrieve principal id attribute [{}]", this.principalIdAttribute);
attributes.add(this.principalIdAttribute);
}
if (this.principalAttributeMap != null && !this.principalAttributeMap.isEmpty()) {
final Set<String> attrs = this.principalAttributeMap.keySet();
attributes.addAll(attrs);
LOGGER.debug("Configured to retrieve principal attribute collection of [{}]", attrs);
}
if (authenticator.getReturnAttributes() != null) {
final List<String> authenticatorAttributes = CollectionUtils.wrapList(authenticator.getReturnAttributes());
if (!authenticatorAttributes.isEmpty()) {
LOGGER.debug("Filtering authentication entry attributes [{}] based on authenticator attributes [{}]", authenticatedEntryAttributes, authenticatorAttributes);
attributes.removeIf(authenticatorAttributes::contains);
}
}
this.authenticatedEntryAttributes = attributes.toArray(new String[0]);
LOGGER.debug("LDAP authentication entry attributes for the authentication request are [{}]", (Object[]) this.authenticatedEntryAttributes);
}
}
(4)spring.factories 中配置
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
gds.application.cas.config.spring.SpringConfig,\
gds.application.cas.config.principal.CustomLdapAuthenticationConfiguration
(5)至此自定义验证器完成。
如果CAS是https协议的话,需要生成证书;
生成证书可参考 https://blog.csdn.net/qq_21359467/article/details/102731032
系统中如果同时存在数据库认证和ldap认证方式,两种将都会被加载,只要有一种认证通过就行