CAS5.3自定义密码(LADP)认证(三)

自定义登录验证器

重写ldap认证方式,方便增加账号安全检测

1.首先配置pom.xml文件

		<dependency>
            <groupId>org.apereo.cas</groupId>
            <artifactId>cas-server-support-ldap-core</artifactId>
            <version>${cas.version}</version>
        </dependency>

2.配置application.properties文件(这里只是部分内容)

#########################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

3.自定义登录验证器

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认证方式,两种将都会被加载,只要有一种认证通过就行

你可能感兴趣的:(CAS5)