第一部分:cas服务器端连接mysql配置

第一部分 cas服务器端连接mysql配置

  因公司要求使用cas服务器,公司原来有一个使用ajax方式进行单点登录的cas服务器,在项目上线后,发现问题太多,改为使用正常方式的cas服务器。
  客户端项目用到了两种权限管理框架,分别是shiro和spring security。
  整个过程分四部分记录修改过程,第一部分记录cas服务器连接mysql配置;第二部分记录cas服务器增加登录验证码;第三部分记录shiro的配置;第四部分记录spring security的配置。

  cas服务器端下载地址:http://developer.jasig.org/cas/
  cas客户端下载地址:http://developer.jasig.org/cas-clients/
  本文使用版本cas-server-4.0.0-release

  试过了最新的4.1.3,使用jetty:run和tomcat都没有本法正常启动,所以使用了此版本。本版本在修改默认语言为中文时发现了一个bug,参考cas-server-4.1.3进行了修复。

(一)下载cas源码,去掉多去的项目

  cas-server包含很多项目,基础只需要cas-server-core、cas-server-webapp、cas-server-webapp-support三个项目。因为需要连接数据库验证用户名和密码,保留了cas-server-support-jdbc项目。
  因为cas使用了log4j,项目运行路径时包含中文路径会报错,但是不影响使用。
  可以使用eclipse的run on server启动cas,要修改Server工程中server.xml中Context节点,保持docBase和path一致,即可启动成功。

<Context docBase="cas-server-webapp" path="/cas-server-webapp" reloadable="true"
    source="org.eclipse.jst.j2ee.server:cas-server-webapp"/>

  引入工程后,子项目的pom文件会出现警告,可以在cas-server的pom文件中,把警告提到的插件移动到pluginManagement的plugins节点下。并去掉com.mycila.maven-license-plugin插件。

(二)去掉https

  deployerConfigContext.xml:找到id为proxyAuthenticationHandler的bean,增加p:requireSecure="false"

<bean id="proxyAuthenticationHandler" 
class="org.jasig.cas.authentication.handler.support.HttpBasedServiceCredentialsAuthenticationHandler" 
p:httpClient-ref="httpClient" p:requireSecure="false" />

ticketGrantingTicketCookieGenerator.xml:
  warnCookieGenerator.xml:p:cookieSecure="true"改为p:cookieSecure="false"

<bean id="ticketGrantingTicketCookieGenerator" 
class="org.jasig.cas.web.support.CookieRetrievingCookieGenerator"
        p:cookieSecure="false"
        p:cookieMaxAge="-1"
        p:cookieName="CASTGC"
        p:cookiePath="/cas" />
<bean id="warnCookieGenerator" 
class="org.jasig.cas.web.support.CookieRetrievingCookieGenerator"
        p:cookieSecure="false"
        p:cookieMaxAge="-1"
        p:cookieName="CASPRIVACY"
        p:cookiePath="/cas" />

(三)修改默认语言为中文

  cas-server-4.0.0中有一个bug,需要修改org.jasig.cas.web.viewCasReloadableMessageBundle类中的getMessageInternal()方法

final String filename = this.basenames[i] + "_" + locale.getLanguage();

改为

final String filename = this.basenames[i] + "_" + locale;

  messages_zh_CN.properties中缺少了一些中文提示,因此从cas-server-4.1.3中复制了一个覆盖原文件。
cas-server-4.1.3下载地址:https://github.com/Jasig/cas/tree/v4.1.3

  现在cas-server可以部署到tomcat中并正常启动,在deployerConfigContext.xml的id为deployerConfigContext.xml的bean中保存了默认的用户名和密码。

<bean id="primaryAuthenticationHandler" 
class="org.jasig.cas.authentication.AcceptUsersAuthenticationHandler">
        <property name="users">
            <map>
                <entry key="casuser" value="Mellon"/>
            </map>
        </property>
    </bean>

(四)启用cas登出时根据service跳转页面的功能

  因为客户端项目在登出后需要跳转到登录页面,需要开启cas登出后跳转页面的功能。
  cas-servlet.xml:修改id为logoutAction的bean,p:followServiceRedirects="${cas.logout.followServiceRedirects:false}"改为true。

<bean id="logoutAction" class="org.jasig.cas.web.flow.LogoutAction" 
    p:servicesManager-ref="servicesManager" 
    p:followServiceRedirects="${cas.logout.followServiceRedirects:true}"/>

(五)增加连接数据库验证用户名和密码的功能

1、修改pom文件,增加jdbc支持

  修改cas-server工程的pom文件,注释掉没有用到的modules。本文连接的数据库是mysql,使用druid连接池。pom增加如下依赖:

<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <version>${version.mysql}</version>
</dependency>
<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>druid</artifactId>
    <version>${version.druid}</version>
</dependency>

  properties节点

<version.druid>1.0.16</version.druid>
<version.mysql>5.1.34</version.mysql>

  修改cas-server-support-jdbc工程的pom文件,增加依赖:

<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-jdbc</artifactId>
</dependency>
<!-- 可选,项目需要使用salt加盐算法计算密码才引用 -->
<dependency>
    <groupId>org.apache.shiro</groupId>
    <artifactId>shiro-core</artifactId>
</dependency>

  修改cas-server-webapp工程的pom文件,增加依赖:

<dependency>
    <groupId>org.jasig.cas</groupId>
    <artifactId>cas-server-support-jdbc</artifactId>
    <version>${project.version}</version>
</dependency>
<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
</dependency>
<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>druid</artifactId>
</dependency>
2、增加用户名密码验证处理

  cas-server-support-jdbc工程中自带4个验证处理类,可以通过配置的方法来使用。这四个不满足本项目的需求,因此新增了一个自定义验证处理类。
  deployerConfigContext.xml:去掉默认的primaryAuthenticationHandler,增加自定义处理类,并增加druid连接池。

<!-- 自定义验证处理类 -->
<bean id="primaryAuthenticationHandler" 
    class="org.jasig.cas.adaptors.jdbc.QueryAndSaltDatabaseAuthenticationHandler">
    <constructor-arg name="dataSource" ref="dataSource" />
    <constructor-arg name="sql" value="${jdbc.selectSQL}" />
    <!-- 加密算法 -->
    <constructor-arg name="algorithmName" value="${algorithmName}"/>
    <property name="numberOfIterations" value="${numberOfIterations}" />
    <!-- 数据库中密码字段 -->
    <property name="passwordFieldName" value="${passwordFieldName}" />
    <!-- 数据库中salt字段 -->
    <property name="saltFieldName" value="${saltFieldName}" />
</bean>

<!-- 数据源配置, 使用druid连接池 -->
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource" 
    init-method="init" destroy-method="close">
    <property name="driverClassName" value="${jdbc.driver}" />
    <property name="url" value="${jdbc.url}" />
    <property name="username" value="${jdbc.username}" />
    <property name="password" value="${jdbc.password}" />
    <!-- 配置监控统计拦截的filters -->
    <property name="filters" value="${druid.filters}" />
    <property name="connectionProperties" value="${druid.connectionProperties}" />
    <!-- 配置初始化大小、最小、最大 -->
    <property name="initialSize" value="${druid.initialSize}"/>
    <property name="minIdle" value="${druid.minIdle}"/>
    <property name="maxActive" value="${druid.maxActive}"/>

    <!-- 配置获取连接等待超时的时间 -->
    <property name="maxWait" value="${druid.maxWait}"/>
    <!-- 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒 -->
    <property name="timeBetweenEvictionRunsMillis" value="${druid.timeBetweenEvictionRunsMillis}" />

    <!-- 配置一个连接在池中最小生存的时间,单位是毫秒 -->
    <property name="minEvictableIdleTimeMillis" value="${druid.minEvictableIdleTimeMillis}" />

    <property name="validationQuery" value="${druid.validationQuery}" />
    <property name="testWhileIdle" value="${druid.testWhileIdle}" />
    <property name="testOnBorrow" value="${druid.testOnBorrow}" />
    <property name="testOnReturn" value="${druid.testOnReturn}" />

    <!-- 打开PSCache,并且指定每个连接上PSCache的大小  如果用Oracle,则把poolPreparedStatements配置为true,mysql可以配置为false。-->
    <property name="poolPreparedStatements" value="${druid.poolPreparedStatements}" />
    <property name="maxPoolPreparedStatementPerConnectionSize" value="${druid.maxPoolPreparedStatementPerConnectionSize}" />
</bean>
3、自定义验证处理类:QueryAndSaltDatabaseAuthenticationHandler

  参考cas-server-4.1.3中QueryAndEncodeDatabaseAuthenticationHandler类进行编写,修改authenticateUsernamePasswordInternal()和digestEncodedPassword()。

/*
 * Licensed to Jasig under one or more contributor license
 * agreements. See the NOTICE file distributed with this work
 * for additional information regarding copyright ownership.
 * Jasig licenses this file to you 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 the following location:
 *
 *   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.jasig.cas.adaptors.jdbc;

import java.security.GeneralSecurityException;
import java.util.Date;
import java.util.Map;

import javax.security.auth.login.AccountNotFoundException;
import javax.security.auth.login.FailedLoginException;
import javax.sql.DataSource;
import javax.validation.constraints.NotNull;

import org.apache.shiro.crypto.hash.ConfigurableHashService;
import org.apache.shiro.crypto.hash.DefaultHashService;
import org.apache.shiro.crypto.hash.HashRequest;
import org.jasig.cas.authentication.AccountDisabledException;
import org.jasig.cas.authentication.HandlerResult;
import org.jasig.cas.authentication.PreventedException;
import org.jasig.cas.authentication.UsernamePasswordCredential;
import org.jasig.cas.authentication.principal.SimplePrincipal;
import org.springframework.dao.DataAccessException;
import org.springframework.dao.IncorrectResultSizeDataAccessException;

/**
 * Class that if provided a query that returns a password (parameter of query
 * must be username) will compare that password to a translated version of the
 * password provided by the user. If they match, then authentication succeeds.
 * Default password translator is plaintext translator.
 *
 * @author Scott Battaglia
 * @author Dmitriy Kopylenko
 * @author Marvin S. Addison
 *
 * @since 3.0
 */
public class QueryAndSaltDatabaseAuthenticationHandler extends AbstractJdbcUsernamePasswordAuthenticationHandler {

    private static final String DEFAULT_PASSWORD_FIELD = "password";
    private static final String DEFAULT_SALT_FIELD = "salt";
    private static final long DEFAULT_ITERATIONS = 1024;

    /**
     * The Algorithm name.
     */
    @NotNull
    protected final String algorithmName;

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

    /**
     * The Sql statement to execute.
     */
    @NotNull
    protected final String updateSql;

    /**
     * The Password field name.
     */
    @NotNull
    protected String passwordFieldName = DEFAULT_PASSWORD_FIELD;

    /**
     * The Salt field name.
     */
    @NotNull
    protected String saltFieldName = DEFAULT_SALT_FIELD;

    /**
     * The number of iterations. Defaults to 0.
     */
    protected Long numberOfIterations = DEFAULT_ITERATIONS;


    public QueryAndSaltDatabaseAuthenticationHandler(final DataSource dataSource,
                                                       final String sql,
                                                       final String updateSql,
                                                       final String algorithmName) {
        super();
        setDataSource(dataSource);
        this.sql = sql;
        this.updateSql = updateSql;

        this.algorithmName = algorithmName;
    }

    /** {@inheritDoc} */
    @Override
    protected final HandlerResult authenticateUsernamePasswordInternal(final UsernamePasswordCredential credential)
            throws GeneralSecurityException, PreventedException {
        final String username = credential.getUsername();
        final String encodedPsw = this.getPasswordEncoder().encode(credential.getPassword());
        try {
            final Map<String, Object> values = getJdbcTemplate().queryForMap(this.sql, username);
            //检查用户状态
            if(!values.containsKey("status")){
                throw new AccountDisabledException();
            }
            final String status = values.get("status").toString();
            if ("00".equals(status)) {
                //禁用
                throw new AccountDisabledException();
            }else if("02".equals(status)){
                //删除
                throw new AccountNotFoundException();
            }
            //效验密码
            final String digestedPassword = digestEncodedPassword(encodedPsw, values);
            if (!values.get(this.passwordFieldName).equals(digestedPassword)) {
                throw new FailedLoginException("Password does not match value on record.");
            }
            int flag = getJdbcTemplate().update(updateSql, new Date(), username);
            logger.debug("更新{}最后登录时间:{}", username, flag);
        } catch (final IncorrectResultSizeDataAccessException e) {
            if (e.getActualSize() == 0) {
                throw new AccountNotFoundException(username + " not found with SQL query");
            } else {
                throw new FailedLoginException("Multiple records found for " + username);
            }
        } catch (final DataAccessException e) {
            throw new PreventedException("SQL exception while executing query for " + username, e);
        }
        return createHandlerResult(credential, new SimplePrincipal(username), null);
    }

    /**
     * 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();
        //配置
        hashService.setHashAlgorithmName(this.algorithmName);
        hashService.setHashIterations(numberOfIterations.intValue());
        if (!values.containsKey(this.saltFieldName)) {
            throw new RuntimeException("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();
    }

    /**
     * Sets password field name. Default is {@link #DEFAULT_PASSWORD_FIELD}.
     *
     * @param passwordFieldName the password field name
     */
    public final void setPasswordFieldName(final String passwordFieldName) {
        this.passwordFieldName = passwordFieldName;
    }

    /**
     * Sets salt field name. Default is {@link #DEFAULT_SALT_FIELD}.
     *
     * @param saltFieldName the password field name
     */
    public final void setSaltFieldName(final String saltFieldName) {
        this.saltFieldName = saltFieldName;
    }

    /**
     * Sets number of iterations. Default is 0.
     *
     * @param numberOfIterations the number of iterations
     */
    public final void setNumberOfIterations(final Long numberOfIterations) {
        this.numberOfIterations = numberOfIterations;
    }
}
4、jdbc.properties

  在propertyFileConfigurer.xml修改property-placeholder节点。

<context:property-placeholder location="/WEB-INF/*.properties"/>

  在WEB-INF目录下新增jdbc.properties

jdbc.driver=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://192.168.5.127:3306/ucenter?useUnicode=true&characterEncoding=utf-8
jdbc.username=root
jdbc.password=XlYr6hqtsD3YulwHugnpy/FgYywQT2lzuekedA8TtP8kj62MHqa6txQIMIygqD5DVjTv+q5i7V6+yCfqfrE6LA==
druid.filters=config
druid.connectionProperties=config.decrypt=true;config.decrypt.key=MFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBAKRTQGQ4hl08ZckgQKLLvt3oEFfwrl3Jdt23j4Qc9ZMVooK7sgZ/c7GK4fEXI/vTXFVPjV/utcvPbKpF2LhTJCcCAwEAAQ==

#druid connection pool settings
druid.initialSize=10
druid.minIdle=10
druid.maxActive=50
druid.maxWait=60000
druid.timeBetweenEvictionRunsMillis=60000
druid.minEvictableIdleTimeMillis=300000
druid.validationQuery=SELECT 'x'
druid.testWhileIdle=true
druid.testOnBorrow=false
druid.testOnReturn=false
druid.poolPreparedStatements=true
druid.maxPoolPreparedStatementPerConnectionSize=20

jdbc.selectSQL=SELECT `password`,`salt`,`status` FROM ucs_user WHERE username = ?
algorithmName=SHA-1
numberOfIterations=1024
passwordFieldName=password
saltFieldName=salt
  • 1
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值