CAS 5.3.x 单点登录实现集群搭建,快速入手(一)!!!!

前言:

该篇教程描述如何搭建CAS5.3.x集群操作,由于官方文档并没有贴出集群搭建方案,所以本博主根据源码剖析解决该问题。

文档并没有深入浅出说明原理,直接贴代码用于快速上手学习。

学习CAS5.x 推荐看 以下两个博主文章

此博主文章:https://blog.csdn.net/u010475041/article/category/7156505

此博主文章:https://blog.csdn.net/yelllowcong

全部基于springboot开发,请存在此基础再学习!

该教程由本博主(Garc)首发,所以转载请说明原处,谢谢!!

该教程分为三个部分

       1.ticket redis 共享

       2.session 共享

扫描二维码关注公众号,回复: 3095062 查看本文章

       3.CAS action 源码覆盖

PS:请按顺序一步一步来

如果发现CAS不能打印info日志,增加一个AsyncLogger指定自己的工程package就好了

以下所有的 configuration 都需要配置spring ,使用spring aop 配置

配置文件目录:src/main/resources/META-INF/spring.factories

org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
  com.hpay.sso.support.auth.config.RedisTicketRegistryConfiguration,\
  com.hpay.sso.support.auth.config.CasSupportActionsConfiguration,\
  com.hpay.sso.support.auth.config.JdbcPasswordManagementConfiguration,\
  com.hpay.sso.config.RedisCacheConfig,\
  com.hpay.sso.config.SpringSessionRedisConfig

一、ticket redis 共享

1.缓存搭建

     maven 依赖

     说明:该2.9版本必须对应服务器3.0版本或以上,否则后面的session共享会导致版本出错!

                <!-- redis -->
                <dependency>
                    <groupId>redis.clients</groupId>
                    <artifactId>jedis</artifactId>
                    <version>2.9.0</version>
                    <exclusions>
                        <exclusion>
                            <groupId>org.apache.commons</groupId>
                            <artifactId>commons-pool2</artifactId>
                        </exclusion>
                    </exclusions>
                </dependency>

                <dependency>
                    <groupId>org.springframework.data</groupId>
                    <artifactId>spring-data-redis</artifactId>
                    <version>1.8.11.RELEASE</version>
                </dependency>

   代码configuration

   将 RedisProperties 交给springboot容器,用于在properties 配置

import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.hpay.sso.support.auth.cache.RiskCacheService;
import com.hpay.sso.support.auth.cache.impl.RiskCacheServiceImpl;
import org.springframework.boot.autoconfigure.data.redis.RedisProperties;
import org.springframework.cache.annotation.CachingConfigurerSupport;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.jedis.JedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.JdkSerializationRedisSerializer;
import redis.clients.jedis.JedisPoolConfig;

/**
 * Created by Garc on 2018/4/20.
 */
@Configuration
@EnableCaching
public class RedisCacheConfig extends CachingConfigurerSupport {

    @Bean
    public RedisProperties redisProperties(){
       return new RedisProperties();
    }

    @Bean
    public JedisConnectionFactory jedisConnectionFactory(){
        JedisConnectionFactory jedisConnectionFactory = new JedisConnectionFactory();
        jedisConnectionFactory.setDatabase(redisProperties().getDatabase());
        jedisConnectionFactory.setPassword(redisProperties().getPassword());
        jedisConnectionFactory.setHostName(redisProperties().getHost());
        jedisConnectionFactory.setPort(redisProperties().getPort());
        JedisPoolConfig jedisPoolConfig = new JedisPoolConfig();
        jedisPoolConfig.setMaxTotal(redisProperties().getPool().getMaxActive());
        jedisPoolConfig.setMaxWaitMillis(redisProperties().getPool().getMaxWait());
        jedisPoolConfig.setMaxIdle(redisProperties().getPool().getMaxIdle());
        jedisPoolConfig.setMinIdle(redisProperties().getPool().getMinIdle());
        jedisConnectionFactory.setPoolConfig(jedisPoolConfig);
        jedisConnectionFactory.setTimeout(redisProperties().getTimeout());
        return jedisConnectionFactory;
    }

    @Bean
    public RedisTemplate redisTemplate() {
        StringRedisTemplate redisTemplate = new StringRedisTemplate(jedisConnectionFactory());
        Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer<>(Object.class);
        ObjectMapper om = new ObjectMapper();
        om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
        om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
        jackson2JsonRedisSerializer.setObjectMapper(om);
        redisTemplate.setHashValueSerializer(new JdkSerializationRedisSerializer());
        redisTemplate.setValueSerializer(jackson2JsonRedisSerializer);
        redisTemplate.afterPropertiesSet();
        return redisTemplate;
    }

    @Bean
    public RiskCacheService riskCacheService(RedisTemplate redisTemplate){
        RiskCacheServiceImpl riskCacheService = new RiskCacheServiceImpl();
        riskCacheService.setRedisTemplate(redisTemplate);
        return riskCacheService;
    }
}

properties 配置

#==============Redis==================
# REDIS (RedisProperties)
# Redis数据库索引(默认为0)
spring.redis.database=${redis.database}
# Redis服务器地址
spring.redis.host=${redis.host}
# Redis服务器连接端口
spring.redis.port=${redis.port}
# Redis服务器连接密码(默认为空)
spring.redis.password=${redis.password}
# 连接池最大连接数(使用负值表示没有限制)
spring.redis.pool.max-active=${redis.maxActive}
# 连接池最大阻塞等待时间(使用负值表示没有限制)
spring.redis.pool.max-wait=${redis.maxWait}
# 连接池中的最大空闲连接
spring.redis.pool.max-idle=${redis.maxIdle}
# 连接池中的最小空闲连接
spring.redis.pool.min-idle=${redis.minIdle}
# 连接超时时间(毫秒)
spring.redis.timeout=${redis.timeout}

 redis api实现代码

import java.util.List;
import java.util.Map;

/**
 * Created by Garc on 2018/5/4.
 * This class encoding of the default is UTF-8
 */
public interface RiskCacheService {

    /**
     * 是否存在key
     * key 格式示例:
     * risk:api:xxx,目录:二级目录:key
     * @param key
     * @return
     */
    boolean hasKey(String key);

    /**
     * 存参数
     * time 单位:秒
     * @param key
     * @param value
     * @param time
     */
    void set(String key, Object value, long time);

    /**
     * 存参数
     * @param key
     * @param value
     */
    void set(String key, Object value);

    /**
     * 获取值
     * @param key
     * @return
     */
    Object get(String key);

    /**
     * 参数增值+1
     * @param key
     * @param val 每次增值数
     */
    void increment(String key, long val);

    /**
     * 同时存入多个key,val数据
     * @param keys
     */
    void multiSet(Map<String, Object> keys);

    /**
     * 同时获取多个key的数据
     * @param keys
     */
    List<Object> multiGet(List<String> keys);

    /**
     * 获取List集合的大小量
     * 返回存储在键中的列表的长度。
     * 如果键不存在,则将其解释为空列表,并返回0。
     * 当key存储的值不是列表时返回null。
     * @param key
     * @return
     */
    Long listSize(String key);

    /**
     * 将所有指定的值插入存储在键的集合的头部。
     * 如果键不存在,则在执行推送操作之前将其创建为空集合。(从左边插入)
     * 返回值为 当前集合的第多少个元素
     * @param key
     * @param val
     * @return
     */
    Long leftPush(String key, Object val);

    /**
     * 删除缓存
     * @param key
     */
    void delete(String key);

    /**
     * 添加一个map集合
     * @param key redis key
     * @param val map集合对象
     */
    void mapPutAll(String key,Map<String,Object> val);

    /**
     * map集合添加参数
     * @param key redis key
     * @param field  map的hashkey
     * @param val   map的val
     */
    void mapPut(String key,String field,Object val);

    /**
     * 删除map内的参数
     * @param key redis key
     * @param field map的hashkey
     */
    void delMapkey(String key,String field);

    /**
     * 判断是否map存在该key
     * @param key redis key
     * @param field map的hashkey
     */
    Boolean hasMapKey(String key,String field);

    /**
     * 根据单个hashKey获取map内的值
     * @param key
     * @param field hashKey
     * @return
     */
    Object getMapVal(String key,String field);

    /**
     * 根据redis key获取map中的所有值
     * @return
     */
    Object getValues(String key);

    /**
     * 根据 redis key 获取整个map集合
     * @param key
     * @return
     */
    Map<String,Object> getMap(String key);
}
import com.hpay.sso.support.auth.cache.RiskCacheService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.redis.core.RedisTemplate;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;

/**
 * Created by Garc on 2018/5/4.
 * redisTemplate.opsForValue();//Operation String
 * redisTemplate.opsForHash();//Operation hash
 * redisTemplate.opsForList();//Operation list
 * redisTemplate.opsForSet();//Operation set
 * redisTemplate.opsForZSet();//Operation orderly set
 */
public class RiskCacheServiceImpl implements RiskCacheService {

    private static final Logger log = LoggerFactory.getLogger(RiskCacheServiceImpl.class);

    private RedisTemplate redisTemplate;

    @Value("${redis.appcode}")
    private String appcode;

    public void setRedisTemplate(RedisTemplate redisTemplate) {
        this.redisTemplate = redisTemplate;
    }

    private String convert(String key){
        return appcode + ":api:" + key;
    }

    private Object convertObject(Object obj){
        if (obj instanceof Map){
            Map<String,Object> keys = new HashMap<>();
            Map<String,Object> params = (HashMap<String,Object>)obj;
            params.forEach((key,val) ->{
                keys.put(convert(key),val);
            });
            return keys;
        }

        if (obj instanceof List){
            List<String> keys = new ArrayList<>();
            List<String> params = (ArrayList<String>)obj;
            params.forEach(key ->{
                keys.add(convert(key));
            });
            return keys;
        }
        return obj;
    }

    @Override
    public boolean hasKey(String key) {
        try {
            return redisTemplate.hasKey(convert(key));
        }catch (Exception e){
            log.error("hasKey缓存异常,key:{}",key,e);
        }
        return false;
    }

    @Override
    public void set(String key, Object value, long time) {
        try {
            redisTemplate.opsForValue().set(convert(key), value,time, TimeUnit.SECONDS);
        }catch (Exception e){
            log.error("set缓存异常,key:{}",key,e);
        }
    }

    @Override
    public void set(String key, Object value) {
        try {
            redisTemplate.opsForValue().set(convert(key), value);
        }catch (Exception e){
            log.error("set缓存异常,key:{}",key,e);
        }
    }

    @Override
    public Object get(String key) {
        try {
            return redisTemplate.opsForValue().get(convert(key));
        }catch (Exception e){
            log.error("get缓存异常,key:{}",key,e);
        }
        return null;
    }

    @Override
    public void increment(String key, long val) {
        try {
            redisTemplate.boundValueOps(convert(key)).increment(val);
        }catch (Exception e){
            log.error("increment缓存异常,key:{}",key,e);
        }
    }

    @Override
    public void multiSet(Map<String, Object> keys) {
        try {
            redisTemplate.opsForValue().multiSet((Map<String, Object>)convertObject(keys));
        }catch (Exception e){
            log.error("multiSet缓存异常,key:{}",keys,e);
        }
    }

    @Override
    public List<Object> multiGet(List<String> keys) {
        try {
           return redisTemplate.opsForValue().multiGet((List<String>)convertObject(keys));
        }catch (Exception e){
            log.error("multiGet缓存异常,key:{}",keys,e);
        }
        return null;
    }

    @Override
    public Long listSize(String key) {
        try {
            return redisTemplate.opsForList().size(convert(key));
        }catch (Exception e){
            log.error("multiSet缓存异常,key:{}",key,e);
        }
        return null;
    }

    @Override
    public Long leftPush(String key, Object val) {
        try {
            return redisTemplate.opsForList().leftPush(convert(key),val);
        }catch (Exception e){
            log.error("multiSet缓存异常,key:{}",key,e);
        }
        return null;
    }

    @Override
    public void delete(String key) {
        try {
            redisTemplate.delete(convert(key));
        }catch (Exception e){
            log.error("delete缓存异常,key:{}",key,e);
        }
    }

    @Override
    public void mapPutAll(String key, Map<String,Object> val) {
        try {
            redisTemplate.opsForHash().putAll(convert(key),val);
        }catch (Exception e){
            log.error("mapPutAll缓存异常,key:{}",key,e);
        }
    }

    @Override
    public void mapPut(String key, String field, Object val) {
        try {
            redisTemplate.opsForHash().put(convert(key),field,val);
        }catch (Exception e){
            log.error("mapPut缓存异常,key:{}",key,e);
        }
    }

    @Override
    public void delMapkey(String key, String field) {
        try {
            redisTemplate.opsForHash().delete(convert(key),field);
        }catch (Exception e){
            log.error("delMapkey缓存异常,key:{}",key,e);
        }
    }

    @Override
    public Boolean hasMapKey(String key, String field) {
        try {
            return redisTemplate.opsForHash().hasKey(convert(key),field);
        }catch (Exception e){
            log.error("hasMapKey缓存异常,key:{}",key,e);
        }
        return false;
    }

    @Override
    public Object getMapVal(String key, String field) {
        try {
            return redisTemplate.opsForHash().get(convert(key),field);
        }catch (Exception e){
            log.error("getMapVal缓存异常,key:{}",key,e);
        }
        return null;
    }

    @Override
    public Object getValues(String key) {
        try {
            return redisTemplate.opsForHash().values(convert(key));
        }catch (Exception e){
            log.error("getValues缓存异常,key:{}",key,e);
        }
        return null;
    }

    @Override
    public Map<String, Object> getMap(String key) {
        try {
            return redisTemplate.opsForHash().entries(convert(key));
        }catch (Exception e){
            log.error("getMap缓存异常,key:{}",key,e);
        }
        return null;
    }

}

2.注册redis ticket

maven 依赖

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

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

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

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

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

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

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

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

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

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

                <!-- servlet -->
                <dependency>
                    <groupId>javax.servlet</groupId>
                    <artifactId>servlet-api</artifactId>
                    <version>2.5</version>
                    <scope>provided</scope>
                </dependency>

redis ticket 注册代码,该代码用于改变CAS本身获取ticket的一种方式

import com.hpay.sso.support.auth.cache.RiskCacheService;
import org.apache.commons.lang.StringUtils;
import org.apereo.cas.CentralAuthenticationService;
import org.apereo.cas.ticket.Ticket;
import org.apereo.cas.ticket.registry.AbstractTicketRegistry;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.annotation.Resource;
import java.util.Collection;
import java.util.List;

/**
 * Created by Garc on 2018/8/20.
 * 注册redis 共享TGT
 */

public class RedisTicketRegistry extends AbstractTicketRegistry {

    private final static String TICKET_KEY="maat:ticket";

    private static final Logger log = LoggerFactory.getLogger(RedisTicketRegistry.class);

    @Resource
    private RiskCacheService riskCacheService;

    @Resource
    private CentralAuthenticationService centralAuthenticationService;

    @Override
    public void addTicket(Ticket ticket) {
        if (ticket == null) {
            throw new NullPointerException("ticket is marked @NonNull but is null");
        } else {
            riskCacheService.mapPut(TICKET_KEY,ticket.getId(),ticket);
            log.info("add redis TGT:{}",getVal(ticket.getId()));
        }
    }

    @Override
    public Ticket getTicket(String ticketId) {

        Ticket ticket = (Ticket) riskCacheService.getMapVal(TICKET_KEY,ticketId);
        if (ticket==null){
            log.info("get redis TGT is null:",getVal(ticketId));
        }
        log.info("get redis TGT:{}",getVal(ticketId));
        return ticket;
    }

    @Override
    public long deleteAll() {
        riskCacheService.delete(TICKET_KEY);
        return 1;
    }

    @Override
    public Collection<Ticket> getTickets() {
        return (List<Ticket>)riskCacheService.getValues(TICKET_KEY);
    }

    @Override
    public Ticket updateTicket(Ticket ticket) {
        if (ticket == null) {
            throw new NullPointerException("ticket is marked @NonNull but is null");
        } else {
            this.addTicket(ticket);
        }
        return ticket;
    }

    /**
     * TGT到期时才调用该方法
     * @param ticketId
     * @return
     */
    @Override
    public boolean deleteSingleTicket(String ticketId) {
        riskCacheService.delMapkey(TICKET_KEY,ticketId);
        if(riskCacheService.hasMapKey(TICKET_KEY,ticketId)){
            return false;
        }
        //TGT到期主动销毁各个系统session
        centralAuthenticationService.destroyTicketGrantingTicket(ticketId);
        return true;
    }

    /**
     * 星星显示
     * @param val
     * @return
     */
    private String getVal(String val){
        if(StringUtils.isBlank(val)){
            return val;
        }else{
            return val.substring(0, 5)+  val.substring(5, val.length()-3).replaceAll("[0-9a-zA-Z]", "*")  + val.substring(val.length()-10, val.length());
        }
    }
}

代码configuration ,用于覆盖原有CAS配置

action 配置也一并配在其中,action代码在下面

import com.hpay.sso.support.auth.RedisTicketRegistry;
import com.hpay.sso.support.auth.action.CreateTicketGrantingTicketAction;
import com.hpay.sso.support.auth.action.TicketGrantingTicketCheckAction;
import org.apereo.cas.CentralAuthenticationService;
import org.apereo.cas.CipherExecutor;
import org.apereo.cas.DefaultCentralAuthenticationService;
import org.apereo.cas.audit.AuditableExecution;
import org.apereo.cas.authentication.AuthenticationServiceSelectionPlan;
import org.apereo.cas.authentication.ContextualAuthenticationPolicyFactory;
import org.apereo.cas.authentication.principal.DefaultPrincipalFactory;
import org.apereo.cas.authentication.principal.PrincipalFactory;
import org.apereo.cas.configuration.CasConfigurationProperties;
import org.apereo.cas.logout.LogoutManager;
import org.apereo.cas.services.ServiceContext;
import org.apereo.cas.services.ServicesManager;
import org.apereo.cas.ticket.factory.DefaultTicketFactory;
import org.apereo.cas.ticket.registry.DefaultTicketRegistrySupport;
import org.springframework.beans.factory.ObjectProvider;
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.cloud.context.config.annotation.RefreshScope;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.transaction.annotation.EnableTransactionManagement;
import org.springframework.webflow.execution.Action;

import javax.annotation.Resource;


/**
 * Created by Garc on 2018/7/24.
 * redis集群配置
 * ticket 集群配置,将CasCoreConfiguration.class源码颠覆使用自定义配置
 * 该配置通过剖析源码得出,官方未给出文档,请不要随意更改。
 *
 */

@Configuration("redisTicketRegistryConfiguration")
@EnableConfigurationProperties(CasConfigurationProperties.class)
@EnableTransactionManagement(
  proxyTargetClass = true
)
public class RedisTicketRegistryConfiguration {

    @Resource
    private ServicesManager servicesManager;

    @Resource
    private LogoutManager logoutManager;

    @Resource
    private DefaultTicketFactory defaultTicketFactory;

    @Resource
    private ContextualAuthenticationPolicyFactory<ServiceContext> authenticationPolicyFactory;

    @Resource
    private ApplicationEventPublisher applicationEventPublisher;

    @Resource
    private AuditableExecution registeredServiceAccessStrategyEnforcer;

    @Autowired
    @Qualifier("protocolTicketCipherExecutor")
    private ObjectProvider<CipherExecutor> cipherExecutor;

    @Bean
    public RedisTicketRegistry redisTicketRegistry(){
        return new RedisTicketRegistry();
    }

    @Bean
    public DefaultTicketRegistrySupport defaultTicketRegistrySupport(){
        return new DefaultTicketRegistrySupport(redisTicketRegistry());
    }

    @Bean
    @Autowired
    @ConditionalOnMissingBean(
      name = {"centralAuthenticationService"}
    )
    public CentralAuthenticationService centralAuthenticationService(@Qualifier("authenticationServiceSelectionPlan") AuthenticationServiceSelectionPlan authenticationServiceSelectionPlan) {

        PrincipalFactory principalFactory = new DefaultPrincipalFactory();

        return new DefaultCentralAuthenticationService(applicationEventPublisher, redisTicketRegistry(), servicesManager, logoutManager, defaultTicketFactory, authenticationServiceSelectionPlan, authenticationPolicyFactory, principalFactory, this.cipherExecutor.getIfAvailable(), this.registeredServiceAccessStrategyEnforcer);
    }

}

以上是ticket redis 共享教程,下面是session共享

二、session 共享

maven 依赖

                <dependency>
                    <groupId>org.springframework.session</groupId>
                    <artifactId>spring-session-data-redis</artifactId>
                    <version>1.3.2.RELEASE</version>
                </dependency>

perproties 配置

spring.session.store-type=redis

代码 configuration

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.session.data.redis.config.annotation.web.http.EnableRedisHttpSession;
import org.springframework.session.data.redis.config.annotation.web.http
  .RedisHttpSessionConfiguration;
import org.springframework.session.web.http.DefaultCookieSerializer;

/**
 * Created by Garc on 2018/8/22.
 * 配置spring-session redis共享
 */

@Configuration
@EnableRedisHttpSession
public class SpringSessionRedisConfig {

    @Bean
    public DefaultCookieSerializer defaultCookieSerializer(){
        DefaultCookieSerializer defaultCookieSerializer = new DefaultCookieSerializer();
        defaultCookieSerializer.setCookiePath("/Maat");
        defaultCookieSerializer.setCookieName("JSESSIONID");
        return defaultCookieSerializer;
    }

    @Bean
    public RedisHttpSessionConfiguration redisHttpSessionConfiguration(){
        RedisHttpSessionConfiguration configuration = new RedisHttpSessionConfiguration();
        configuration.setCookieSerializer(defaultCookieSerializer());
        configuration.setMaxInactiveIntervalInSeconds(60*15);
        return configuration;
    }
}

三、CAS action 源码覆盖

说明:该 action源码是从CAS上直接修改的,请不要随意乱修改源码。

由于已经在刚才上面的redis ticket 配置中配置了Bean,接下来就直接上代码即可

创建 CreateTicket 动作  CreateTicketGrantingTicketAction.class

import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.builder.EqualsBuilder;
import org.apereo.cas.CentralAuthenticationService;
import org.apereo.cas.authentication.Authentication;
import org.apereo.cas.authentication.AuthenticationHandlerExecutionResult;
import org.apereo.cas.authentication.AuthenticationResult;
import org.apereo.cas.authentication.AuthenticationResultBuilder;
import org.apereo.cas.authentication.AuthenticationSystemSupport;
import org.apereo.cas.authentication.MessageDescriptor;
import org.apereo.cas.authentication.principal.Service;
import org.apereo.cas.ticket.TicketGrantingTicket;
import org.apereo.cas.ticket.registry.TicketRegistrySupport;
import org.apereo.cas.web.support.WebUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.binding.message.MessageBuilder;
import org.springframework.binding.message.MessageContext;
import org.springframework.webflow.action.AbstractAction;
import org.springframework.webflow.action.EventFactorySupport;
import org.springframework.webflow.core.collection.LocalAttributeMap;
import org.springframework.webflow.execution.Event;
import org.springframework.webflow.execution.RequestContext;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import java.util.Collection;
import java.util.Map.Entry;
import java.util.Set;
import java.util.stream.Collectors;

public class CreateTicketGrantingTicketAction extends AbstractAction {

    private static final Logger LOGGER = LoggerFactory.getLogger(CreateTicketGrantingTicketAction.class);
    @Resource
    private CentralAuthenticationService centralAuthenticationService;

    @Resource
    private AuthenticationSystemSupport authenticationSystemSupport;

    @Resource
    private TicketRegistrySupport ticketRegistrySupport;

    @Resource
    private HttpServletRequest request;

    public Event doExecute(RequestContext context) {
        Service service = WebUtils.getService(context);
        AuthenticationResultBuilder authenticationResultBuilder = WebUtils.getAuthenticationResultBuilder(context);
        LOGGER.debug("Finalizing authentication transactions and issuing ticket-granting ticket");
        AuthenticationResult authenticationResult = this.authenticationSystemSupport.finalizeAllAuthenticationTransactions(authenticationResultBuilder, service);
        Authentication authentication = this.buildFinalAuthentication(authenticationResult);
        String ticketGrantingTicket = WebUtils.getTicketGrantingTicketId(context);
        TicketGrantingTicket tgt = this.createOrUpdateTicketGrantingTicket(authenticationResult, authentication, ticketGrantingTicket);
        WebUtils.putTicketGrantingTicketInScopes(context, tgt);
        //因为session已经共享,再将TGT存储进session中共享至其他节点,ticketCheck从session中获取
        String ticketValue = tgt != null ? tgt.getId() : null;
        request.getSession().setAttribute("ticketGrantingTicketId",ticketValue);
        WebUtils.putAuthenticationResult(authenticationResult, context);
        WebUtils.putAuthentication(tgt.getAuthentication(), context);
        Collection<MessageDescriptor> warnings = calculateAuthenticationWarningMessages(tgt, context.getMessageContext());
        if (!warnings.isEmpty()) {
            LocalAttributeMap attributes = new LocalAttributeMap("authenticationWarnings", warnings);
            return (new EventFactorySupport()).event(this, "successWithWarnings", attributes);
        } else {
            return this.success();
        }
    }

    protected Authentication buildFinalAuthentication(AuthenticationResult authenticationResult) {
        return authenticationResult.getAuthentication();
    }

    protected TicketGrantingTicket createOrUpdateTicketGrantingTicket(AuthenticationResult authenticationResult, Authentication authentication, String ticketGrantingTicket) {
        TicketGrantingTicket tgt;
        if (this.shouldIssueTicketGrantingTicket(authentication, ticketGrantingTicket)) {
            tgt = this.centralAuthenticationService.createTicketGrantingTicket(authenticationResult);
        } else {
            tgt = (TicketGrantingTicket)this.centralAuthenticationService.getTicket(ticketGrantingTicket, TicketGrantingTicket.class);
            tgt.getAuthentication().update(authentication);
            this.centralAuthenticationService.updateTicket(tgt);
        }

        return tgt;
    }

    private boolean shouldIssueTicketGrantingTicket(Authentication authentication, String ticketGrantingTicket) {
        boolean issueTicketGrantingTicket = true;
        if (StringUtils.isNotBlank(ticketGrantingTicket)) {
            LOGGER.debug("Located ticket-granting ticket in the context. Retrieving associated authentication");
            Authentication authenticationFromTgt = this.ticketRegistrySupport.getAuthenticationFrom(ticketGrantingTicket);
            if (authenticationFromTgt == null) {
                LOGGER.debug("Authentication session associated with [{}] is no longer valid", ticketGrantingTicket);
                this.centralAuthenticationService.destroyTicketGrantingTicket(ticketGrantingTicket);
            } else if (this.areAuthenticationsEssentiallyEqual(authentication, authenticationFromTgt)) {
                LOGGER.debug("Resulting authentication matches the authentication from context");
                issueTicketGrantingTicket = false;
            } else {
                LOGGER.debug("Resulting authentication is different from the context");
            }
        }

        return issueTicketGrantingTicket;
    }

    private boolean areAuthenticationsEssentiallyEqual(Authentication auth1, Authentication auth2) {
        if ((auth1 != null || auth2 == null) && (auth1 == null || auth2 != null)) {
            EqualsBuilder builder = new EqualsBuilder();
            builder.append(auth1.getPrincipal(), auth2.getPrincipal());
            builder.append(auth1.getCredentials(), auth2.getCredentials());
            builder.append(auth1.getSuccesses(), auth2.getSuccesses());
            builder.append(auth1.getAttributes(), auth2.getAttributes());
            return builder.isEquals();
        } else {
            return false;
        }
    }

    private static Collection<MessageDescriptor> calculateAuthenticationWarningMessages(TicketGrantingTicket tgtId, MessageContext messageContext) {
        Set<Entry<String, AuthenticationHandlerExecutionResult>> entries = tgtId.getAuthentication().getSuccesses().entrySet();
        return (Collection)entries.stream().map((entry) -> {
            return ((AuthenticationHandlerExecutionResult)entry.getValue()).getWarnings();
        }).flatMap(Collection::stream).map((message) -> {
            addMessageDescriptorToMessageContext(messageContext, message);
            return message;
        }).collect(Collectors.toSet());
    }

    protected static void addMessageDescriptorToMessageContext(MessageContext context, MessageDescriptor warning) {
        MessageBuilder builder = (new MessageBuilder()).warning().code(warning.getCode()).defaultText(warning.getDefaultMessage()).args((Object[])warning.getParams());
        context.addMessage(builder.build());
    }
}

创建 Ticket check action :TicketGrantingTicketCheckAction.class

5.3版本不知道是BUG还是必须要使用https才能将TGT存储在cookie。

该类自定义处理后,通过session共享已经处理好了该过程。

不适用https的话,TGT不会存储在cookie

import org.apache.commons.lang.StringUtils;
import org.apereo.cas.CentralAuthenticationService;
import org.apereo.cas.ticket.AbstractTicketException;
import org.apereo.cas.ticket.Ticket;
import org.apereo.cas.web.support.WebUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.webflow.action.AbstractAction;
import org.springframework.webflow.execution.Event;
import org.springframework.webflow.execution.RequestContext;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;

/**
 * Created by Garc on 2018/7/24.
 * 因为配置了session集群和ticket集群
 * 所以更改CAS源码并判断共享session信息
 *
 */
public class TicketGrantingTicketCheckAction extends AbstractAction {

    private static final Logger LOGGER = LoggerFactory.getLogger(TicketGrantingTicketCheckAction.class);

    @Resource
    private CentralAuthenticationService centralAuthenticationService;

    @Resource
    private HttpServletRequest request;

    public Event doExecute(RequestContext requestContext) {

        //请求判断requestScope和session是否过存在TGT
        String scopeTgtId = WebUtils.getTicketGrantingTicketId(requestContext);
        String sessionTgtId = (String) request.getSession().getAttribute("ticketGrantingTicketId");
        if (StringUtils.isBlank(scopeTgtId)&&StringUtils.isBlank(sessionTgtId)) {
            LOGGER.info("TGT not Exists");
            return new Event(this, "notExists");
        }

        if(StringUtils.isBlank(sessionTgtId)){
            LOGGER.info("session TGT not Exists");
            request.getSession().setAttribute("ticketGrantingTicketId",scopeTgtId);
        }

        if (StringUtils.isBlank(scopeTgtId)){
            LOGGER.info("scope TGT not Exists");
            WebUtils.putTicketGrantingTicketInScopes(requestContext,sessionTgtId);
        }


        String tgtId = StringUtils.isNotBlank(scopeTgtId)?scopeTgtId:StringUtils.isNotBlank(sessionTgtId)?sessionTgtId:"";

        try {
            Ticket ticket = this.centralAuthenticationService.getTicket(tgtId, Ticket.class);
            if (ticket != null && !ticket.isExpired()) {
                return new Event(this, "valid");
            }
        } catch (AbstractTicketException var4) {
            LOGGER.trace("Could not retrieve ticket id [{}] from registry.", var4.getMessage());
        }

        return new Event(this, "invalid");
    }
}

action的Bean配置在第二篇

请继续看下篇!

第一篇教程内容到此已经结束了,只要根据此教程一步一步操作,就可以实现集群登录操作。

如果想了解更多,可以加QQ群 119170668

猜你喜欢

转载自blog.csdn.net/Qensq/article/details/81981877