CAS统一身份认证:集成MySQL用户验证

Posted 程序员猴小萌

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了CAS统一身份认证:集成MySQL用户验证相关的知识,希望对你有一定的参考价值。

本文主要介绍CAS统一身份认证服务器JDBC密码管理,并以FreeBSD环境下的mysql数据库为例实现CAS 6.6版的数据库用户验证。主要包括以下几个方面:

  1. JDBC密码管理
  2. MySQL数据库准备
  3. 用户验证属性配置文件
  4. 配置MySQL数据库支持
  5. 构建和测试CAS服务

本文使用的软件版本:

  1. FreeBSD 13.0
  2. OpenJDK 11.0.2
  3. gradle 7.3.3
  4. MySQL 8.0

1 JDBC密码管理

CAS统一认证的用户名密码可存储于的数据库中,通过JDBC访问数据库,获取用户名和密码。同时还可以设置或者自定义密码的加密方法,以满足多标准化的密码加密需求。

通过CAS Overlay模板增加数据库支持非常简单,只需要准备好数据库,然后能过配置gradle任务就可以生成新的支持数据库密码存储的cas.war。

CAS统一身份认证(二):Overlay配置管理中介绍了使用Overlay模板进行配置管理过程,并对配置文件的覆盖策略作了说明。由于集成数据库支持需求导入数据库的JDBC驱动等库文件及相关属性配置,使用外部独立配置文件时需要自己去下载一系列相关的jar包,且不能自动应对CAS服务版本的升级,所以选用Overlay模板方式来集成JDBC数据库,通过gradle任务自动完成最终Web应用包的构建。

当然,在cas.war包构建完成后,在生产环境中依然可以使用CAS统一身份认证(三):外部独立配置节中介绍了外部文件独立配置方法来对CAS生产系统进行配置。

2 MySQL数据库准备

在开始构建数据库支持的CAS认证服务之前,首先要准备好数据库。

(1)用户账号表

CAS会从数据库中查询用户名、密码、电子邮件、电话号码4个字段,可以使用以下SQL语句创建。

create table cas_user (
  id int  NOT NULL AUTO_INCREMENT,
  username varchar(255),
  password varchar(255),
  email varchar(255),
  phone varchar(255),
  PRIMARY KEY(id)
);

(2)安全问题表

当需要找回密码时,CAS会从数据库中查询用户名、密码问题和答案3个字段,可以使用以下SQL语句创建。

create table cas_password_question (
  id int NOT NULL AUTO_INCREMENT,
  userid varchar(255),
  question varchar(255),
  answer varchar(255),
  PRIMARY KEY (id)
);

如果使用项目自备的数据库的话,一定要包含这几个字段,否则就要使用上面的SQL语句创建对应的数据表以后才能使用。

最后别忘了在cas_user数据表添加至少一条用户记录,要不然登录时就没用户可用了。

 

3 集成数据库驱动

增加数据库支持功能需要使用Overlay模板方式重新构建cas.war。首先打开build.gradle文件,在dependencies段中添加JDBC和MySQL支持如下:

implementation "org.apereo.cas:cas-server-support-pm-jdbc:$project.'cas.version'"
implementation "org.apereo.cas:cas-server-support-jdbc-drivers:$project.'cas.version'"
implementation "mysql:mysql-connector-java:8.0.28"

添加依赖时可以同时指定了包的版本,当然也可以不用考虑依赖包的版本号,依赖管理插件会自动解决这些版本号。修改后的build.gradle文件片断如下,最后三行为增加的JDBC和MySQL依赖。

dependencies 
    /**
     * CAS dependencies and modules may be listed here.
     *
     * There is no need to specify the version number for each dependency
     * since versions are all resolved and controlled by the dependency management
     * plugin via the CAS bom.
     **/
    implementation "org.apereo.cas:cas-server-support-jdbc:$project.'cas.version'"
    implementation "org.apereo.cas:cas-server-support-jdbc-drivers:$project.'cas.version'"
    implementation "mysql:mysql-connector-java"

4 用户验证属性配置文件

进入overlay-template-master文件夹,在src/main/resources目录下创建application.properties文件,这个文件中的配置会覆盖预编译包中的application.properties文件中配置的属性。

这一步也可以先不着急配置,建议在cas.war包构建完成之后,使用CAS统一身份认证(三):外部独立配置节中介绍的外部文件独立配置方法,在生产环境的/etc/cas/config文件夹下创建一个application-standalone.properties文件,然后在这个文件中对CAS生产系统进行外部独立配置。这也是本文正在使用的外部文件独立配置方法。

5 去除静态用户验证验证

创建/etc/cas/config/application-standalone.properties文件,输入以下配置去除静态用户验证

##
# CAS Authentication Credentials
#
cas.authn.accept.enabled=false
#cas.authn.accept.users=casuser::cas2022
#cas.authn.accept.name=Static Credentials

6 配置MySQL数据库支持

在application-standalone.properties文件中继续输入以下配置,增加MySQL数据库支持。

cas.authn.jdbc.query[0].driver-class=com.mysql.cj.jdbc.Driver
cas.authn.jdbc.query[0].url=jdbc:mysql://localhost:3306/cas?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8
cas.authn.jdbc.query[0].user=root
cas.authn.jdbc.query[0].password=123456
cas.authn.jdbc.query[0].sql=select * from cas.cas_user where username=?
cas.authn.jdbc.query[0].field-password=password

cas.authn.jdbc.query[0].password-encoder.encoding-algorithm=
cas.authn.jdbc.query[0].password-encoder.type=NONE

#cas.authn.jdbc.bind[0].principal-transformation.groovy.location=

 其中的数据库名称、数据库用户名、数据库用户密码、用户信息SQL查询语句、密码字段名称、是否使用口令加密算法等可以根据自己的实际情况和需求来选择。

7 重新构建CAS应用包

使用以下命令重新构建CAS应用包

rm src/main/resources/application.properties
./gradlew --info clean build

注意这里首先删除了编译目录中的application.properties配置文件,如果你更喜欢在构建时将配置文件包含进Web应用包的话,在这里就不用删除了。

使用gradlew脚本进行构建时,会自动下载缺失的依赖并导入到项目中,如下图所示就是对新添加的三个依赖进行自动下载和包含。

Cached resource https://oss.sonatype.org/content/repositories/snapshots/org/apereo/cas/cas-server-support-jdbc-drivers/6.6.0-SNAPSHOT/cas-server-support-jdbc-drivers-6.6.0-20220903.093135-114.jar is up-to-date (lastModified: Sat Sep 03 09:49:28 GMT 2022).
Cached resource https://oss.sonatype.org/content/repositories/snapshots/org/apereo/cas/cas-server-support-jdbc/6.6.0-SNAPSHOT/cas-server-support-jdbc-6.6.0-20220903.093135-114.jar is up-to-date (lastModified: Sat Sep 03 09:49:26 GMT 2022).
Downloading https://repo.maven.apache.org/maven2/mysql/mysql-connector-java/8.0.28/mysql-connector-java-8.0.28.jar to /root/.gradle/.tmp/gradle_download7591157756739201348bin

构建完成后,即可在build/libs目录下生成cas.jar应用包。

8. 测试CAS服务

执行java -jar build/libs/cas.jar,启动CAS服务,运行结果如下:

 打开浏览器,在地址栏中输入以下地址即可打开CAS登录页面

https://localhost:8443/cas/login

在登录页面中输入原来的静态用户和密码casuser:Mellon发现已经不能登录了,会出现“认证信息无效”的错误提示。输入存储在数据库中的用户名casuser:123456可以登录成功。

 至此,集成MySQL的统一身份认证过程就成功实现了,可以在此基础上继续进行密码加密、用户信息修改,以及用户的锁定、失效、禁止登录等功能。

最后,别忘了使用https://localhost:8443/cas/logout进行注销。

Jeesite单点登录集成cas另加自定义登录验证

Jeesite单点登录集成Cas另加自定义登录验证

JeeSite是基于多个优秀的开源项目,高度整合封装而成的高效,高性能,强安全性的 开源 Java EE快速开发平台.

Cas主要是用来解决多应用之间统一登陆认证,无需用户在同一公司多应用之间重复登陆。例如阿里巴巴中淘宝、天猫,在多个应用系统中,用户只需要登录一次就可以访问所有相互信任的应用系统.


Cas基础

服务端

服务端cas-server-webapp-4.0.0.war,服务器端程序一般不用我们完成,但需要做一点小小的修改,cas的服务器端程序由spring+Spring web flow+cas写成。全部使用spring配置文件。

默认登录用户名密码

这里写图片描述


去除https

修改第一处: cas/WEB-INF/deployerConfigContext.xml

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

增加参数p:requireSecure=”false”,是否需要安全验证,即HTTPS,false为不采用。修改后为:

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

修改第二处: cas/WEB-INF/spring-configuration/ticketGrantingTicketCookieGenerator.xml

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

参数p:cookieSecure=”true”,同理为HTTPS验证相关,TRUE为采用HTTPS验证,FALSE为不采用https验证。

参数p:cookieMaxAge=”-1”,简单说是COOKIE的最大生命周期,-1为无生命周期,即只在当前打开的IE窗口有效,IE关闭或重新打开其它窗口,仍会要求验证。可以根据需要修改为大于0的数字,比如3600等,意思是在3600秒内,打开任意IE窗口,都不需要验证。
这里把 cookieSecure修改为false就行了

修改客户端应用的web.xml

增加如下filter和mapping(jeesite不修改此处,有其它方式处理)

 <filter>
   <filter-name>CAS Authentication Filter</filter-name>
   <filter-class>
    org.jasig.cas.client.authentication.AuthenticationFilter
   </filter-class>
   <init-param>
    <param-name>casServerLoginUrl</param-name>
    <param-value>
    http://localhost:8080/cas/login
    </param-value>
   </init-param>
   <init-param>
    <param-name>renew</param-name>
    <param-value>false</param-value>
   </init-param>
   <init-param>
    <param-name>gateway</param-name>
    <param-value>false</param-value>
   </init-param>
   <init-param>
    <param-name>serverName</param-name>
    <param-value>http://localhost:8080</param-value>
   </init-param>
</filter>

<filter>
   <filter-name>CAS Validation Filter</filter-name>
   <filter-class>
    org.jasig.cas.client.validation.Cas20ProxyReceivingTicketValidationFilter
   </filter-class>
   <init-param>
    <param-name>casServerUrlPrefix</param-name>
    <param-value>http://localhost:8080/cas</param-value>
   </init-param>
   <init-param>
    <param-name>serverName</param-name>
    <param-value>http://localhost:8080</param-value>
   </init-param>
   <init-param>
    <param-name>useSession</param-name>
    <param-value>true</param-value>
   </init-param>
   <init-param>
    <param-name>redirectAfterValidation</param-name>
    <param-value>true</param-value>
   </init-param>
</filter>

<filter>
   <filter-name>CAS HttpServletRequest Wrapper Filter</filter-name>
   <filter-class>
    org.jasig.cas.client.util.HttpServletRequestWrapperFilter
   </filter-class>
</filter>

<filter>
   <filter-name>CAS Assertion Thread Local Filter</filter-name>
   <filter-class>
    org.jasig.cas.client.util.AssertionThreadLocalFilter
   </filter-class>
</filter>
<filter>
        <filter-name>loginFilter</filter-name>
        <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
        <init-param>
            <param-name>targetFilterLifecycle</param-name>
            <param-value>true</param-value>
        </init-param>
    </filter>
<filter-mapping>
   <filter-name>CAS Authentication Filter</filter-name>
   <url-pattern>/*</url-pattern>
</filter-mapping>

<filter-mapping>
   <filter-name>CAS Validation Filter</filter-name>
   <url-pattern>/*</url-pattern>
</filter-mapping>

<filter-mapping>
   <filter-name>CAS HttpServletRequest Wrapper Filter</filter-name>
   <url-pattern>/*</url-pattern>
</filter-mapping>

<filter-mapping>
   <filter-name>CAS Assertion Thread Local Filter</filter-name>
   <url-pattern>/*</url-pattern>
</filter-mapping>

Jeesite单点登录集成cas

需要写个自己MyCasRealm.java

package com.thinkgem.jeesite.modules.sys.security;

import java.util.Collection;
import java.util.List;
import org.apache.commons.lang3.StringUtils;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.AuthenticationInfo;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.authc.SimpleAuthenticationInfo;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.authz.SimpleAuthorizationInfo;
import org.apache.shiro.cas.CasAuthenticationException;
import org.apache.shiro.cas.CasRealm;
import org.apache.shiro.cas.CasToken;
import org.apache.shiro.session.Session;
import org.apache.shiro.subject.PrincipalCollection;
import org.apache.shiro.subject.SimplePrincipalCollection;
import org.jasig.cas.client.authentication.AttributePrincipal;
import org.jasig.cas.client.validation.Assertion;
import org.jasig.cas.client.validation.TicketValidationException;
import org.jasig.cas.client.validation.TicketValidator;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.thinkgem.jeesite.common.config.Global;
import com.thinkgem.jeesite.common.utils.SpringContextHolder;
import com.thinkgem.jeesite.common.web.Servlets;
import com.thinkgem.jeesite.modules.sys.entity.Menu;
import com.thinkgem.jeesite.modules.sys.entity.Role;
import com.thinkgem.jeesite.modules.sys.entity.User;
import com.thinkgem.jeesite.modules.sys.security.SystemAuthorizingRealm.Principal;
import com.thinkgem.jeesite.modules.sys.service.SystemService;
import com.thinkgem.jeesite.modules.sys.utils.LogUtils;
import com.thinkgem.jeesite.modules.sys.utils.UserUtils;

public class MyCasRealm extends CasRealm {
    private Logger logger = LoggerFactory.getLogger(getClass());
    private SystemService systemService;

    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
//      return super.doGetAuthenticationInfo(token);
        CasToken casToken = (CasToken) token;
        if (token == null) {
            return null;
        }
        //获取ticket
        String ticket = (String)casToken.getCredentials();
        if (!org.apache.shiro.util.StringUtils.hasText(ticket)) {
            return null;
        }

        TicketValidator ticketValidator = ensureTicketValidator();

        try {
            //回传ticket到服务端验证,验证通过就进入下一行,可以获取登录后的相关信息,否则直接抛异常,即验证不通过
            Assertion casAssertion = ticketValidator.validate(ticket, getCasService());
            AttributePrincipal casPrincipal = casAssertion.getPrincipal();
            String userId = casPrincipal.getName();
            User user = getSystemService().getUserByLoginName(userId);
            if (user != null) {
                Principal p = new  Principal(user, false);
                PrincipalCollection principalCollection = new SimplePrincipalCollection(p, getName());
                return new SimpleAuthenticationInfo(principalCollection, ticket);
            } else {
                return null;
            }

        } catch (TicketValidationException e) { 
            throw new CasAuthenticationException("Unable to validate ticket [" + ticket + "]", e);
        }

    }

    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
        Principal principal = (Principal) getAvailablePrincipal(principals);
        // 获取当前已登录的用户
        if (!Global.TRUE.equals(Global.getConfig("user.multiAccountLogin"))){
            Collection<Session> sessions = getSystemService().getSessionDao().getActiveSessions(true, principal, UserUtils.getSession());
            if (sessions.size() > 0){
                // 如果是登录进来的,则踢出已在线用户
                if (UserUtils.getSubject().isAuthenticated()){
                    for (Session session : sessions){
                        getSystemService().getSessionDao().delete(session);
                    }
                }
                // 记住我进来的,并且当前用户已登录,则退出当前用户提示信息。
                else{
                    UserUtils.getSubject().logout();
                    throw new AuthenticationException("msg:账号已在其它地方登录,请重新登录。");
                }
            }
        }
        User user = getSystemService().getUserByLoginName(principal.getLoginName());
        if (user != null) {
            SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
            List<Menu> list = UserUtils.getMenuList();
            for (Menu menu : list){
                if (StringUtils.isNotBlank(menu.getPermission())){
                    // 添加基于Permission的权限信息
                    for (String permission : StringUtils.split(menu.getPermission(),",")){
                        info.addStringPermission(permission);
                    }
                }
            }
            // 添加用户权限
            info.addStringPermission("user");
            // 添加用户角色信息
            for (Role role : user.getRoleList()){
                info.addRole(role.getEnname());
            }
            // 更新登录IP和时间
            getSystemService().updateUserLoginInfo(user);
            // 记录登录日志
            LogUtils.saveLog(Servlets.getRequest(), "系统登录");
            return info;
        } else {
            return null;
        }
    }

    /**
     * 获取系统业务对象
     */
    public SystemService getSystemService() {
        if (systemService == null){
            systemService = SpringContextHolder.getBean(SystemService.class);
        }
        return systemService;
    }


}

修改spring-context-shiro.xml配置文件

原来的:

<property name="loginUrl" value="${adminPath}/login" />

改为:

<property name="loginUrl" value="${cas.server.url}?service=${cas.project.url}${adminPath}/cas" />

其实就是把注释放开

新增配置bean : casRealm

<bean id="casRealm" class="com.thinkgem.jeesite.modules.sys.security.MyCasRealm">
        <property name="casServerUrlPrefix" value="${cas.server.url}"/>
        <!-- 客户端的回调地址设置,必须和下面的shiro-cas过滤器拦截的地址一致 -->
        <property name="casService" value="${cas.project.url}${adminPath}/cas"/>
</bean>

修改Shiro安全管理配置的realm属性

原来Shiro安全管理配置的realm属性:

<property name="realm" ref="systemAuthorizingRealm" />

改为:<property name="realm" ref="casRealm" />


Jeesite其它

其他的基本没有什么修改的,要改的话就是:jeesite.properties里面的cas.project.url和cas.server.url

这里写图片描述

Cas通过查询数据库验证用户名、密码正确性(密码非复杂加密,可如:MD5,SHA-1等)

jar包准备

MySQL jdbc驱动:mysql-connector-Java-5.1.13-bin.jar
Cas jdbc支持:cas-server-support-jdbc-4.0.0.jar

编辑:WEB-INF\\deployerConfigContext.xml,加入数据源:

这里写图片描述

这里写图片描述

Cas服务端二次开发

把cas-server-webapp-4.0.0.war转换成Eclipse项目

这里写图片描述

自定义登录验证(加密规则)

复制jeesite的3个java文件

作用是在Cas服务端实现jeesite的密码加密方式

这里写图片描述

jar包

这里写图片描述

开发自己的MyQueryDatabaseAuthenticationHandler.java


package com.jinfonet.developer.portal;

import java.security.GeneralSecurityException;

import org.jasig.cas.adaptors.jdbc.AbstractJdbcUsernamePasswordAuthenticationHandler;
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;

import com.thinkgem.jeesite.common.security.Digests;
import com.thinkgem.jeesite.common.utils.Encodes;

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

/**
 * 20170309gch
 */
public class MyQueryDatabaseAuthenticationHandler extends AbstractJdbcUsernamePasswordAuthenticationHandler {
    public static final String HASH_ALGORITHM = "SHA-1";
    public static final int HASH_INTERATIONS = 1024;
    public static final int SALT_SIZE = 8;
    @NotNull
    private String sql;

    /** {@inheritDoc} */
    @Override
    protected final HandlerResult authenticateUsernamePasswordInternal(final UsernamePasswordCredential credential)
            throws GeneralSecurityException, PreventedException {

        final String username = credential.getUsername();
        final String encryptedPassword = this.getPasswordEncoder().encode(credential.getPassword());
        try {
            final String dbPassword = getJdbcTemplate().queryForObject(this.sql, String.class, username);

//          String plain = Encodes.unescapeHtml(credential.getPassword());
//          byte[] salt = Digests.generateSalt(SALT_SIZE);
//          byte[] hashPassword = Digests.sha1(plain.getBytes(), salt, HASH_INTERATIONS);
//          String s=Encodes.encodeHex(salt)+Encodes.encodeHex(hashPassword);
//          boolean ss= dbPassword.equals(s);

            String plainPassword="123456";
            plainPassword=credential.getPassword();
            String password="9ec51dc31c730f4b6a719842d1c5a6d2034e653f4fbdafa45ed60104";
            password=dbPassword;
            String plain1 = Encodes.unescapeHtml(plainPassword);
            byte[] salt1 = Encodes.decodeHex(password.substring(0,16));
            byte[] hashPassword1 = Digests.sha1(plain1.getBytes(), salt1, HASH_INTERATIONS);
            boolean ss1= password.equals(Encodes.encodeHex(salt1)+Encodes.encodeHex(hashPassword1));
            if (!ss1) {
//            if (!dbPassword.equals(encryptedPassword)) {
                throw new FailedLoginException("Password does not match value on record.");
            }
        } 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);
    }

    /**
     * @param sql The sql to set.
     */
    public void setSql(final String sql) {
        this.sql = sql;
    }
}

修改deployerConfigContext.xml文件

这里写图片描述

至此完成Jeesite单点登录集成Cas和自定义登录验证


jeesite加密研究

这里写图片描述

如果直接给密码散列,黑客可以通过查散列值字典(例如MD5密码破解网站),得到某用户的密码。加上salt后就会难上很多,即便是你获得了其中的salt和最终密文,破解也是相当麻烦的。


盐(Salt),在密码学中,是指通过在密码任意固定位置插入特定的字符串,让散列后的结果和使用原始密码的散列结果不相符,这种过程称之为“加盐”。
安全因素
通常情况下,当字段经过散列处理(如MD5),会生成一段散列值,而散列后的值一般是无法通过特定算法得到原始字段的。但是某些情况,比如一个大型的彩虹表,通过在表中搜索该MD5值,很有可能在极短的时间内找到该散列值对应的真实字段内容。

加盐后的散列值,可以极大的降低由于用户数据被盗而带来的密码泄漏风险,即使通过彩虹表寻找到了散列后的数值所对应的原始内容,但是由于经过了加盐,插入的字符串扰乱了真正的密码,使得获得真实密码的概率大大降低。

实现原理
加盐的实现过程通常是在需要散列的字段的特定位置增加特定的字符,打乱原始的字符串,使其生成的散列结果产生变化。比如,用户使用了一个密码:

x7faqgjw
经过MD5散列后,可以得出结果:

455e0e5c2bc109deae749e7ce0cdd397
但是由于用户密码位数不足,短密码的散列结果很容易被彩虹表破解,因此,在用户的密码末尾添加特定字符串(粗体下划线为加盐的字段):

x7faqgjwabcdefghijklmnopqrstuvwxyz

因此,加盐后的密码位数更长了,散列的结果也发生了变化:

4a1690d5eb6c126ef68606dda68c2f79
以上就是加盐过程的简单描述,在实际使用过程中,还需要通过特定位数插入、倒序或多种方法对原始密码进行固定的加盐处理,使得散列的结果更加不容易被破解或轻易得到原始密码,比如(绿色字体为加盐字符串):

这里写图片描述

其它方式实现单点登录

使用Cookie解决单点登录 技术点:
1、设置Cookie的路径为setPath(“/”) .即Tomcat的目录下都有效
2、设置Cookie的域setDomain(“.gch.com”);即bbs.gch.com,或是mail.gch.com有效。即跨域。
3、设置Cookie的时间。即使用户不选择在几天内自动登录,也应该保存Cookie以保存在当前浏览器没有关闭的情况下有效。
4、使用Filter自动登录。

网上查询到的,未测试,个人觉得可行。

其他
`package com.jinfonet.developer.portal;

import java.io.UnsupportedEncodingException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;

import javax.validation.constraints.NotNull;

import org.apache.log4j.chainsaw.Main;
//import org.inspektr.common.ioc.annotation.NotNull;
import org.jasig.cas.authentication.handler.PasswordEncoder;
import org.springframework.util.StringUtils;

import com.thinkgem.jeesite.common.security.Digests;
import com.thinkgem.jeesite.common.utils.Encodes;

import sun.misc.BASE64Encoder;

/**
*
*/
public class JeeSitePasswordEncoder implements PasswordEncoder {

public static final String HASH_ALGORITHM = "SHA-1";
public static final int HASH_INTERATIONS = 1024;
public static final int SALT_SIZE = 8;

private static final char[] HEX_DIGITS = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd',
        'e', 'f' };
@NotNull
private final String encodingAlgorithm;
private String characterEncoding;

public JeeSitePasswordEncoder(final String encodingAlgorithm) {
    this.encodingAlgorithm = encodingAlgorithm;
}

public String encode(final String password) {
    if (password == null) {
        return null;
    }

    try {
        MessageDigest messageDigest = MessageDigest
            .getInstance(this.encodingAlgorithm);

        if (StringUtils.hasText(this.characterEncoding)) {
            messageDigest.update(password.getBytes(this.characterEncoding));
        } else {
            messageDigest.update(password.getBytes());
        }


        final byte[] digest = messageDigest.digest();

        return getFormattedText(digest);
    } catch (final NoSuchAlgorithmException e) {
        throw new SecurityException(e);
    } catch (final UnsupportedEncodingException e) {
        throw new RuntimeException(e);
    }
}

 /**
 * Takes the raw bytes from the digest and formats them correct.
 *
 * @param bytes the raw bytes from the digest.
 * @return the formatted bytes.
 */
private String getFormattedText(final byte[] bytes) {
    final StringBuilder buf = new StringBuilder(bytes.length * 2);

    for (int j = 0; j < bytes.length; j++) {
        buf.append(HEX_DIGITS[(bytes[j] >> 4) & 0x0f]);
        buf.append(HEX_DIGITS[bytes[j] & 0x0f]);
    }
    return buf.toString();
}

public final void setCharacterEncoding(final String characterEncoding) {
    this.characterEncoding = characterEncoding;
}

/**
 * 生成安全的密码,生成随机的16位salt并经过1024次 sha-1 hash
 */
public static String entryptPassword(String plainPassword) {
    System.out.println("原密码:"+plainPassword);
    String plain = Encodes.unescapeHtml(plainPassword);
    byte[] salt = Digests.generateSalt(SALT_SIZE);
    byte[] hashPassword = Digests.sha1(plain.getBytes(), salt, HASH_INTERATIONS);
    String mm=Encodes.encodeHex(salt)+Encodes.encodeHex(hashPassword);
    System.out.println("加密后:"+mm);
    return mm;
}

/*
public static void main(String[] args) {
String s=”1”;
System.out.println(s);
System.out.println(entryptPassword(s));
}*/

}`

以上是关于CAS统一身份认证:集成MySQL用户验证的主要内容,如果未能解决你的问题,请参考以下文章

将 CAS 与外部 SAML 身份提供者集成

[转]统一身份认证(CAS)简单说明与设计方案

SSO统一身份认证——CAS Client客户端创建(九)

CAS统一登录认证:与ldap连接

使用 Yii 框架进行 CAS 身份验证

具有 JDBC 身份验证和 TOTP 的 CAS Server 3.51