CAS DB身份验证:自定义密码编码器

时间:2018-12-29 15:01:57

标签: java authentication customization cas

我需要配置我的CAS服务器(v5.3.6)以使用使用编码方法的数据库身份验证。不幸的是,CAS默认的哈希服务(Apache Shiro的deos)无法满足我的需求,因此我必须自定义密码编码器。
答案here似乎很适合我的情况。按照建议,我将文件QueryAndEncodeDatabaseAuthenticationHandler.java和AbstractJdbcUsernamePasswordAuthenticationHandler.java放在maven覆盖中的路径src / main / java / org / apereo / cas / adaptors / jdbc下,并尝试对其进行编译(mvn干净软件包)。这个想法是根据我的需要在类QueryAndEncodeDatabaseAuthenticationHandler中自定义函数digestEncodedPassword。但是在尝试之前,我只是尝试编译两个未修改的Java CAS文件。这样做时,我遇到了几个编译错误:

[INFO] Scanning for projects...
[INFO]                                                                         
[INFO] ------------------------------------------------------------------------
[INFO] Building cas 1.0
[INFO] ------------------------------------------------------------------------
Downloading: https://build.shibboleth.net/nexus/content/repositories/releases/com/nimbusds/lang-tag/maven-metadata.xml
[WARNING] Could not transfer metadata com.nimbusds:lang-tag/maven-metadata.xml from/to shib-release (https://build.shibboleth.net/nexus/content/repositories/releases): sun.security.validator.ValidatorException: PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target
[INFO] 
[INFO] --- maven-clean-plugin:2.5:clean (default-clean) @ cas ---
[INFO] Deleting /opt/cas5_3_6/target
[INFO] 
[INFO] --- maven-resources-plugin:2.6:resources (default-resources) @ cas ---
[INFO] Using 'UTF-8' encoding to copy filtered resources.
[INFO] Copying 1 resource
[INFO] 
[INFO] --- maven-compiler-plugin:2.5.1:compile (default-compile) @ cas ---
[INFO] Compiling 2 source files to /opt/cas5_3_6/target/classes
[INFO] -------------------------------------------------------------
[ERROR] COMPILATION ERROR : 
[INFO] -------------------------------------------------------------
[ERROR] /opt/cas5_3_6/src/main/java/org/apereo/cas/adaptors/jdbc/AbstractJdbcUsernamePasswordAuthenticationHandler.java:[4,52] error: package org.apereo.cas.authentication.handler.support does not exist
[ERROR] /opt/cas5_3_6/src/main/java/org/apereo/cas/adaptors/jdbc/AbstractJdbcUsernamePasswordAuthenticationHandler.java:[5,46] error: package org.apereo.cas.authentication.principal does not exist
[ERROR] /opt/cas5_3_6/src/main/java/org/apereo/cas/adaptors/jdbc/AbstractJdbcUsernamePasswordAuthenticationHandler.java:[19,80] error: cannot find symbol
[ERROR]   symbol: class AbstractUsernamePasswordAuthenticationHandler
/opt/cas5_3_6/src/main/java/org/apereo/cas/adaptors/jdbc/AbstractJdbcUsernamePasswordAuthenticationHandler.java:[25,125] error: cannot find symbol
[ERROR]   symbol:   class PrincipalFactory
  location: class AbstractJdbcUsernamePasswordAuthenticationHandler
/opt/cas5_3_6/src/main/java/org/apereo/cas/adaptors/jdbc/QueryAndEncodeDatabaseAuthenticationHandler.java:[10,36] error: cannot find symbol
[ERROR]   symbol:   class AuthenticationHandlerExecutionResult
  location: package org.apereo.cas.authentication
/opt/cas5_3_6/src/main/java/org/apereo/cas/adaptors/jdbc/QueryAndEncodeDatabaseAuthenticationHandler.java:[11,36] error: cannot find symbol
[ERROR]   symbol:   class PreventedException
  location: package org.apereo.cas.authentication
/opt/cas5_3_6/src/main/java/org/apereo/cas/adaptors/jdbc/QueryAndEncodeDatabaseAuthenticationHandler.java:[12,36] error: cannot find symbol
[ERROR]   symbol:   class UsernamePasswordCredential
  location: package org.apereo.cas.authentication
/opt/cas5_3_6/src/main/java/org/apereo/cas/adaptors/jdbc/QueryAndEncodeDatabaseAuthenticationHandler.java:[13,47] error: package org.apereo.cas.authentication.exceptions does not exist
[ERROR] /opt/cas5_3_6/src/main/java/org/apereo/cas/adaptors/jdbc/QueryAndEncodeDatabaseAuthenticationHandler.java:[14,47] error: package org.apereo.cas.authentication.exceptions does not exist
[ERROR] /opt/cas5_3_6/src/main/java/org/apereo/cas/adaptors/jdbc/QueryAndEncodeDatabaseAuthenticationHandler.java:[15,46] error: package org.apereo.cas.authentication.principal does not exist
[ERROR] /opt/cas5_3_6/src/main/java/org/apereo/cas/adaptors/jdbc/QueryAndEncodeDatabaseAuthenticationHandler.java:[104,119] error: cannot find symbol
[ERROR]   symbol:   class PrincipalFactory
  location: class QueryAndEncodeDatabaseAuthenticationHandler
/opt/cas5_3_6/src/main/java/org/apereo/cas/adaptors/jdbc/QueryAndEncodeDatabaseAuthenticationHandler.java:[123,94] error: cannot find symbol
[ERROR]   symbol:   class UsernamePasswordCredential
  location: class QueryAndEncodeDatabaseAuthenticationHandler
/opt/cas5_3_6/src/main/java/org/apereo/cas/adaptors/jdbc/QueryAndEncodeDatabaseAuthenticationHandler.java:[123,14] error: cannot find symbol
[ERROR]   symbol:   class AuthenticationHandlerExecutionResult
  location: class QueryAndEncodeDatabaseAuthenticationHandler
/opt/cas5_3_6/src/main/java/org/apereo/cas/adaptors/jdbc/QueryAndEncodeDatabaseAuthenticationHandler.java:[125,41] error: cannot find symbol
[INFO] 14 errors 
[INFO] -------------------------------------------------------------
[INFO] ------------------------------------------------------------------------
[INFO] BUILD FAILURE
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 2.887 s
[INFO] Finished at: 2018-12-29T14:30:58+00:00
[INFO] Final Memory: 52M/407M
[INFO] ------------------------------------------------------------------------
[ERROR] Failed to execute goal org.apache.maven.plugins:maven-compiler-plugin:2.5.1:compile (default-compile) on project cas: Compilation failure: Compilation failure:
[ERROR] /opt/cas5_3_6/src/main/java/org/apereo/cas/adaptors/jdbc/AbstractJdbcUsernamePasswordAuthenticationHandler.java:[4,52] error: package org.apereo.cas.authentication.handler.support does not exist
[ERROR] /opt/cas5_3_6/src/main/java/org/apereo/cas/adaptors/jdbc/AbstractJdbcUsernamePasswordAuthenticationHandler.java:[5,46] error: package org.apereo.cas.authentication.principal does not exist
[ERROR] /opt/cas5_3_6/src/main/java/org/apereo/cas/adaptors/jdbc/AbstractJdbcUsernamePasswordAuthenticationHandler.java:[19,80] error: cannot find symbol
[ERROR] symbol: class AbstractUsernamePasswordAuthenticationHandler
[ERROR] /opt/cas5_3_6/src/main/java/org/apereo/cas/adaptors/jdbc/AbstractJdbcUsernamePasswordAuthenticationHandler.java:[25,125] error: cannot find symbol
[ERROR] symbol:   class PrincipalFactory
[ERROR] location: class AbstractJdbcUsernamePasswordAuthenticationHandler
[ERROR] /opt/cas5_3_6/src/main/java/org/apereo/cas/adaptors/jdbc/QueryAndEncodeDatabaseAuthenticationHandler.java:[10,36] error: cannot find symbol
[ERROR] symbol:   class AuthenticationHandlerExecutionResult
[ERROR] location: package org.apereo.cas.authentication
[ERROR] /opt/cas5_3_6/src/main/java/org/apereo/cas/adaptors/jdbc/QueryAndEncodeDatabaseAuthenticationHandler.java:[11,36] error: cannot find symbol
[ERROR] symbol:   class PreventedException
[ERROR] location: package org.apereo.cas.authentication
[ERROR] /opt/cas5_3_6/src/main/java/org/apereo/cas/adaptors/jdbc/QueryAndEncodeDatabaseAuthenticationHandler.java:[12,36] error: cannot find symbol
[ERROR] symbol:   class UsernamePasswordCredential
[ERROR] location: package org.apereo.cas.authentication
[ERROR] /opt/cas5_3_6/src/main/java/org/apereo/cas/adaptors/jdbc/QueryAndEncodeDatabaseAuthenticationHandler.java:[13,47] error: package org.apereo.cas.authentication.exceptions does not exist
[ERROR] /opt/cas5_3_6/src/main/java/org/apereo/cas/adaptors/jdbc/QueryAndEncodeDatabaseAuthenticationHandler.java:[14,47] error: package org.apereo.cas.authentication.exceptions does not exist
[ERROR] /opt/cas5_3_6/src/main/java/org/apereo/cas/adaptors/jdbc/QueryAndEncodeDatabaseAuthenticationHandler.java:[15,46] error: package org.apereo.cas.authentication.principal does not exist
[ERROR] /opt/cas5_3_6/src/main/java/org/apereo/cas/adaptors/jdbc/QueryAndEncodeDatabaseAuthenticationHandler.java:[104,119] error: cannot find symbol
[ERROR] symbol:   class PrincipalFactory
[ERROR] location: class QueryAndEncodeDatabaseAuthenticationHandler
[ERROR] /opt/cas5_3_6/src/main/java/org/apereo/cas/adaptors/jdbc/QueryAndEncodeDatabaseAuthenticationHandler.java:[123,94] error: cannot find symbol
[ERROR] symbol:   class UsernamePasswordCredential
[ERROR] location: class QueryAndEncodeDatabaseAuthenticationHandler
[ERROR] /opt/cas5_3_6/src/main/java/org/apereo/cas/adaptors/jdbc/QueryAndEncodeDatabaseAuthenticationHandler.java:[123,14] error: cannot find symbol
[ERROR] symbol:   class AuthenticationHandlerExecutionResult
[ERROR] location: class QueryAndEncodeDatabaseAuthenticationHandler
[ERROR] /opt/cas5_3_6/src/main/java/org/apereo/cas/adaptors/jdbc/QueryAndEncodeDatabaseAuthenticationHandler.java:[125,41] error: cannot find symbol
[ERROR] -> [Help 1]
[ERROR] 
[ERROR] To see the full stack trace of the errors, re-run Maven with the -e switch.
[ERROR] Re-run Maven using the -X switch to enable full debug logging.
[ERROR] 
[ERROR] For more information about the errors and possible solutions, please read the following articles:
[ERROR] [Help 1] http://cwiki.apache.org/confluence/display/MAVEN/MojoFailureException

所以看来我的安装中缺少几个类。无论如何,我试图在po.xml文件中包括所有必需的依赖项...

关于我所缺少的任何想法吗???非常感谢您的帮助!

我的pom.xml文件:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 
http://maven.apache.org/xsd/maven-4.0.0.xsd ">
    <modelVersion>4.0.0</modelVersion>
    <groupId>org.mycompany</groupId>
    <artifactId>cas</artifactId>
    <packaging>war</packaging>
    <version>1.0</version>

    <build>
        <plugins>
            <plugin>
                 <artifactId>maven-war-plugin</artifactId>
                 <version>3.2.2</version>
                             <configuration>
                                 <warName>cas</warName>
                  <failOnMissingWebXml>false</failOnMissingWebXml> 
                                   <source>${java.source.version}</source>
                           <target>${java.target.version}</target>
                             </configuration>
                        </plugin>
        </plugins>
    </build>

    <dependencies>
        <dependency>
            <groupId>org.apereo.cas</groupId>
            <artifactId>cas-server-webapp</artifactId>
            <version>${cas.version}</version>
            <type>war</type>
            <scope>runtime</scope>
        </dependency>

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

        <dependency>
                <groupId>org.apereo.cas</groupId>
                <artifactId>cas-server-support-json-service-registry</artifactId>
                <version>${cas.version}</version>
            </dependency>

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

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

        <dependency>
                <groupId>org.projectlombok</groupId>
                <artifactId>lombok</artifactId>
                <version>0.10.1</version>
                <scope>provided</scope>
        </dependency>

        <dependency>
                <groupId>org.apereo.cas</groupId>
                <artifactId>cas-server-core-authentication</artifactId>
            <version>${cas.version}</version>
                <scope>test</scope>
        </dependency>

        <dependency>
                <groupId>org.apereo.cas</groupId>
                <artifactId>cas-server-core-authentication-api</artifactId>
            <version>${cas.version}</version>
                <scope>runtime</scope>
        </dependency>

        <dependency>
                <groupId>org.apereo.cas</groupId>
                <artifactId>cas-server-core-api-authentication</artifactId>
            <version>${cas.version}</version>
                <scope>runtime</scope>
        </dependency>

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

            <dependency>
                    <groupId>org.apereo.cas</groupId>
                    <artifactId>cas-server-core-webflow</artifactId>
                    <version>${cas.version}</version>
            </dependency>
    </dependencies>
    <properties>
        <cas.version>5.3.6</cas.version>
            <springboot.version>1.5.16.RELEASE</springboot.version>
            <maven.compiler.source>1.8</maven.compiler.source>
            <maven.compiler.target>1.8</maven.compiler.target>
            <app.server>-tomcat</app.server>
            <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    </properties>
</project>

我的QueryAndEncodeDatabaseAuthenticationHandler.java文件(尚未修改!):

package org.apereo.cas.adaptors.jdbc;

import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.BooleanUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.shiro.crypto.hash.ConfigurableHashService;
import org.apache.shiro.crypto.hash.DefaultHashService;
import org.apache.shiro.crypto.hash.HashRequest;
import org.apache.shiro.util.ByteSource;
import org.apereo.cas.authentication.AuthenticationHandlerExecutionResult;
import org.apereo.cas.authentication.PreventedException;
import org.apereo.cas.authentication.UsernamePasswordCredential;
import org.apereo.cas.authentication.exceptions.AccountDisabledException;
import org.apereo.cas.authentication.exceptions.AccountPasswordMustChangeException;
import org.apereo.cas.authentication.principal.PrincipalFactory;
import org.apereo.cas.services.ServicesManager;
import org.springframework.dao.DataAccessException;
import org.springframework.dao.IncorrectResultSizeDataAccessException;

import javax.security.auth.login.AccountNotFoundException;
import javax.security.auth.login.FailedLoginException;
import javax.sql.DataSource;
import java.security.GeneralSecurityException;
import java.util.ArrayList;
import java.util.Map;

/**
 * A JDBC querying handler that will pull back the password and
 * the private salt value for a user and validate the encoded
 * password using the public salt value. Assumes everything
 * is inside the same database table. Supports settings for
 * number of iterations as well as private salt.
 * <p>
 * This handler uses the hashing method defined by Apache Shiro's
 * {@link org.apache.shiro.crypto.hash.DefaultHashService}. Refer to the Javadocs
 * to learn more about the behavior. If the hashing behavior and/or configuration
 * of private and public salts does nto meet your needs, a extension can be developed
 * to specify alternative methods of encoding and digestion of the encoded password.
 * </p>
 *
 * @author Misagh Moayyed
 * @author Charles Hasegawa
 * @since 4.1.0
 */
@Slf4j
public class QueryAndEncodeDatabaseAuthenticationHandler extends AbstractJdbcUsernamePasswordAuthenticationHandler {

    /**
     * The Algorithm name.
     */
    protected String algorithmName;

    /**
     * The Sql statement to execute.
     */
    protected String sql;

    /**
     * The Password field name.
     */
    protected String passwordFieldName = "password";

    /**
     * The Salt field name.
     */
    protected String saltFieldName = "salt";

    /**
     * The Expired field name.
     */
    protected String expiredFieldName;

    /**
     * The Expired field name.
     */
    protected String disabledFieldName;

    /**
     * The Number of iterations field name.
     */
    protected String numberOfIterationsFieldName;

    /**
     * The number of iterations. Defaults to 0.
     */
    protected long numberOfIterations;

    /**
     * Static/private salt to be combined with the dynamic salt retrieved
     * from the database. Optional.
     * <p>If using this implementation as part of a password hashing strategy,
     * it might be desirable to configure a private salt.
     * A hash and the salt used to compute it are often stored together.
     * If an attacker is ever able to access the hash (e.g. during password cracking)
     * and it has the full salt value, the attacker has all of the input necessary
     * to try to brute-force crack the hash (source + complete salt).</p>
     * <p>However, if part of the salt is not available to the attacker (because it is not stored with the hash),
     * it is much harder to crack the hash value since the attacker does not have the complete inputs necessary.
     * The privateSalt property exists to satisfy this private-and-not-shared part of the salt.</p>
     * <p>If you configure this attribute, you can obtain this additional very important safety feature.</p>
     */
    protected String staticSalt;

    public QueryAndEncodeDatabaseAuthenticationHandler(final String name, final ServicesManager servicesManager, final PrincipalFactory principalFactory,
                                                       final Integer order, final DataSource dataSource,
                                                       final String algorithmName, final String sql, final String passwordFieldName,
                                                       final String saltFieldName, final String expiredFieldName, final String disabledFieldName,
                                                       final String numberOfIterationsFieldName, final long numberOfIterations,
                                                       final String staticSalt) {
        super(name, servicesManager, principalFactory, order, dataSource);
        this.algorithmName = algorithmName;
        this.sql = sql;
        this.passwordFieldName = passwordFieldName;
        this.saltFieldName = saltFieldName;
        this.expiredFieldName = expiredFieldName;
        this.disabledFieldName = disabledFieldName;
        this.numberOfIterationsFieldName = numberOfIterationsFieldName;
        this.numberOfIterations = numberOfIterations;
        this.staticSalt = staticSalt;
    }

    @Override
    protected AuthenticationHandlerExecutionResult authenticateUsernamePasswordInternal(final UsernamePasswordCredential transformedCredential,
                                                                                        final String originalPassword)
        throws GeneralSecurityException, PreventedException {

        if (StringUtils.isBlank(this.sql) || StringUtils.isBlank(this.algorithmName) || getJdbcTemplate() == null) {
            throw new GeneralSecurityException("Authentication handler is not configured correctly");
        }

        final String username = transformedCredential.getUsername();
        try {
            final Map<String, Object> values = getJdbcTemplate().queryForMap(this.sql, username);
            final String digestedPassword = digestEncodedPassword(transformedCredential.getPassword(), values);

            if (!values.get(this.passwordFieldName).equals(digestedPassword)) {
                throw new FailedLoginException("Password does not match value on record.");
            }
            if (StringUtils.isNotBlank(this.expiredFieldName) && values.containsKey(this.expiredFieldName)) {
                final String dbExpired = values.get(this.expiredFieldName).toString();
                if (BooleanUtils.toBoolean(dbExpired) || "1".equals(dbExpired)) {
                    throw new AccountPasswordMustChangeException("Password has expired");
                }
            }
            if (StringUtils.isNotBlank(this.disabledFieldName) && values.containsKey(this.disabledFieldName)) {
                final String dbDisabled = values.get(this.disabledFieldName).toString();
                if (BooleanUtils.toBoolean(dbDisabled) || "1".equals(dbDisabled)) {
                    throw new AccountDisabledException("Account has been disabled");
                }
            }
            return createHandlerResult(transformedCredential, this.principalFactory.createPrincipal(username), new ArrayList<>(0));

        } catch (final IncorrectResultSizeDataAccessException e) {
            if (e.getActualSize() == 0) {
                throw new AccountNotFoundException(username + " not found with SQL query");
            }
            throw new FailedLoginException("Multiple records found for " + username);
        } catch (final DataAccessException e) {
            throw new PreventedException("SQL exception while executing query for " + username, e);
        }
    }

    /**
     * Digest encoded password.
     *
     * @param encodedPassword the encoded password
     * @param values          the values retrieved from database
     * @return the digested password
     */
    protected String digestEncodedPassword(final String encodedPassword, final Map<String, Object> values) {
        final ConfigurableHashService hashService = new DefaultHashService();
        if (StringUtils.isNotBlank(this.staticSalt)) {
            hashService.setPrivateSalt(ByteSource.Util.bytes(this.staticSalt));
        }
        hashService.setHashAlgorithmName(this.algorithmName);

        Long numOfIterations = this.numberOfIterations;
        if (values.containsKey(this.numberOfIterationsFieldName)) {
            final String longAsStr = values.get(this.numberOfIterationsFieldName).toString();
            numOfIterations = Long.valueOf(longAsStr);
        }

        hashService.setHashIterations(numOfIterations.intValue());
        if (!values.containsKey(this.saltFieldName)) {
            throw new IllegalArgumentException("Specified field name for salt does not exist in the results");
        }

        final String dynaSalt = values.get(this.saltFieldName).toString();
        final HashRequest request = new HashRequest.Builder()
            .setSalt(dynaSalt)
            .setSource(encodedPassword)
            .build();
        return hashService.computeHash(request).toHex();
    }
}

最后是我的AbstractJdbcUsernamePasswordAuthenticationHandler.java(同样未修改):

package org.apereo.cas.adaptors.jdbc;

import lombok.extern.slf4j.Slf4j;
import org.apereo.cas.authentication.handler.support.AbstractUsernamePasswordAuthenticationHandler;
import org.apereo.cas.authentication.principal.PrincipalFactory;
import org.apereo.cas.services.ServicesManager;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate;

import javax.sql.DataSource;

/**
 * Abstract class for database authentication handlers.
 *
 * @author Scott Battaglia
 * @since 3.0.0.3
 */
@Slf4j
public abstract class AbstractJdbcUsernamePasswordAuthenticationHandler extends AbstractUsernamePasswordAuthenticationHandler {

    private final JdbcTemplate jdbcTemplate;
    private final NamedParameterJdbcTemplate namedParameterJdbcTemplate;
    private final DataSource dataSource;

    public AbstractJdbcUsernamePasswordAuthenticationHandler(final String name, final ServicesManager servicesManager, final PrincipalFactory principalFactory,
                                                             final Integer order, final DataSource dataSource) {
        super(name, servicesManager, principalFactory, order);
        this.dataSource = dataSource;
        this.jdbcTemplate = new JdbcTemplate(dataSource);
        this.namedParameterJdbcTemplate = new NamedParameterJdbcTemplate(this.jdbcTemplate);
    }

    /**
     * Method to return the jdbcTemplate.
     *
     * @return a fully created JdbcTemplate.
     */
    protected JdbcTemplate getJdbcTemplate() {
        return this.jdbcTemplate;
    }

    protected NamedParameterJdbcTemplate getNamedJdbcTemplate() {
        return this.namedParameterJdbcTemplate;
    }

    protected DataSource getDataSource() {
        return this.dataSource;
    }
}

1 个答案:

答案 0 :(得分:0)

最后,通过修改上述pom.xml文件,我设法解决了该问题。特别是我只是将两个依赖性cas-server-core-authentication-api和cas-server-core-api-authentication移到了依赖性列表的开头(我不知道列出依赖性的顺序可以起到角色),我从他们两个中删除了该属性。通过这两个修改,Maven编译就可以了!

因此,根据我的需要自定义默认密码编码器非常容易。