【CAS6.6源码解析】深度解析默认票据存储策略及其拓展支持-探究存储策略的设计

CAS作为一款企业级中央认证服务系统,其票据的生成是非常重要的一环,在票据的生成中,还有一个比较重要的点是票据的存储,本文将默认票据存储策略及其拓展支持,并延伸到探究存储策略的设计。

文章重点分析源码的过程,不想看分析过程可以直接跳到总结处看结论!!!



A.相关阅读

B.涉及源码作用及位置介绍

CAS中,默认支持的是内存的存储策略,涉及存储策略的核心代模块默认会被依赖,但是一些拓展支持的模块如redis存储等属于support模块,需要添加依赖后才会生效。

1.票据存储策略核心源码

1.票据存储策略相关顶级接口在cas-servver-core-api-ticket中的registry包下:

  • TicketRegistry是票据存储的顶级接口,里面规范了一种存储策略需要实现的方法。
  • TicketRegistryCleaner是票据清理的顶级接口,里面规范了一种票据清理器需要实现的方法。
  • TicketRegistrySupport是一个帮助者模式的顶级接口,里面定义了一些需要相互共享和使用的互相不相关的方法。
    在这里插入图片描述

2.上述接口的默认实现类,在cas-server-core-tickets-api模块下的registry包下。

在这里插入图片描述

2.拓展存储策略支持

所有支持的存储策略模块均在support模块下,模块名以-ticket-registry结尾。例如redis支持:
在这里插入图片描述

CAS6.6支持的存储策略有:(13种存储方式)

  • redis
  • couchbase(Couchbase是一个开源的分布式NoSQL文档数据库)
  • couchdb(CouchDB 是一个开源的面向文档的数据库管理系统)
  • dynamodb(AmazonDynamoDB被设计成用来托管的NoSQL数据库服务、可预期的性能、可实现无缝扩展性和可靠性等核心问题)
  • ehcache3(Ehcache 3 是一个强大的缓存技术,它提供了分布式缓存和本地缓存两种模式,并且支持缓存的大小控制、缓存的预热、缓存存储选项和缓存的管理等功能)
  • ehcache(EhCache 是一个纯Java的进程内缓存框架,具有快速、精干等特点,是Hibernate中默认的CacheProvider)
  • hazelcast(Hazelcast是一个高度可扩展的数据分发和集群平台,可用于实现分布式数据存储、数据缓存)
  • ignite(追求高性能/高吞吐量和线性扩展能力 关系型数据库缓存)
  • infinispan(一个分布式集群缓存系统)
  • JMS(Java Message Service)
  • JPA
  • memcached(memcached是一套分布式的高速缓存系统)
  • mongodb(基于分布式文件存储的数据库)

C.默认存储策略深入解析

这里以TGT票据的存储为例展开解析,ST与TGT保持一致。

1.入口-存储票据

1.1 默认票据存储实例

DefaultCentralAuthenticationServicecreateTicketGrantingTicket中,创建了TGT后,会通过configurationContext拿到TicketRegistry的一个实例,并且将票据进行存储,如下图:
在这里插入图片描述

1.2 默认票据存储策略配置

查看configurationContext的配置可以发现,默认注入的TicketRegistry的实现类是DefaultTicketRegistry

这里有个很关键的点:TicketRegistry没有实现类时,才会去注入DefaultTicketRegistry,这是完成票据存储拓展支持的核心点。
在这里插入图片描述
并且可以看到传入的参数是一个ConcurrentHashMap,其中初始容量、并发数和加密器是在配置文件中进行配置的。查看默认配置:

在这里插入图片描述
在这里插入图片描述
默认配置里,初始容量是1000,并发数是20,并且默认是不开启加密的。

2.DefaultTicketRegistry分析

从上述入口可以看出默认使用的是DefaultTicketRegistry实现类,并且知道了默认配置参数。接下来就是仔细分析DefaultTicketRegistry这个类的实现了。

2.1 类关系图

类关系图如下:
在这里插入图片描述
可以发现DefaultTicketRegistry继承了AbstractMapBasedTicketRegistry,是一个Map型的存储模式。

2.2 DefaultTicketRegistry

查看其源码:

@Getter
public class DefaultTicketRegistry extends AbstractMapBasedTicketRegistry {

    /**
     * A map to contain the tickets.
     */
    private final Map<String, Ticket> mapInstance;

    public DefaultTicketRegistry() {
        this(CipherExecutor.noOp());
    }
    public DefaultTicketRegistry(final CipherExecutor cipherExecutor) {
        super(cipherExecutor);
        this.mapInstance = new ConcurrentHashMap<>();
    }
    public DefaultTicketRegistry(final Map<String, Ticket> storageMap, final CipherExecutor cipherExecutor) {
        super(cipherExecutor);
        this.mapInstance = storageMap;
    }
}

主要就是将配置好的ConcurrentHashMap传给父类AbstractMapBasedTicketRegistry

2.3 AbstractMapBasedTicketRegistry

基于Map进行存储的逻辑在AbstractMapBasedTicketRegistry中。

@Slf4j
@NoArgsConstructor(access = AccessLevel.PROTECTED)
public abstract class AbstractMapBasedTicketRegistry extends AbstractTicketRegistry {

    protected AbstractMapBasedTicketRegistry(final CipherExecutor cipherExecutor) {
        setCipherExecutor(cipherExecutor);
    }

    @Override
    public void addTicketInternal(final Ticket ticket) throws Exception {
        val encTicket = encodeTicket(ticket);
        LOGGER.debug("Putting ticket [{}] in registry.", ticket.getId());
        getMapInstance().put(encTicket.getId(), encTicket);
    }

    @Override
    public Ticket getTicket(final String ticketId, final Predicate<Ticket> predicate) {
        val encTicketId = encodeTicketId(ticketId);
        if (StringUtils.isBlank(ticketId)) {
            return null;
        }
        val found = getMapInstance().get(encTicketId);
        if (found == null) {
            LOGGER.debug("Ticket [{}] could not be found", encTicketId);
            return null;
        }

        val result = decodeTicket(found);
        if (!predicate.test(result)) {
            LOGGER.debug("Cannot successfully fetch ticket [{}]", ticketId);
            return null;
        }
        return result;
    }

    @Override
    public long deleteSingleTicket(final String ticketId) {
        val encTicketId = encodeTicketId(ticketId);
        return !StringUtils.isBlank(encTicketId) && getMapInstance().remove(encTicketId) != null ? 1 : 0;
    }

    @Override
    public long deleteAll() {
        val size = getMapInstance().size();
        getMapInstance().clear();
        return size;
    }

    @Override
    public Collection<? extends Ticket> getTickets() {
        return decodeTickets(getMapInstance().values());
    }

    @Override
    public Ticket updateTicket(final Ticket ticket) throws Exception {
        LOGGER.trace("Updating ticket [{}] in registry...", ticket.getId());
        addTicket(ticket);
        return ticket;
    }

    /**
     * Create map instance, which must ben created during initialization phases
     * and always be the same instance.
     *
     * @return the map
     */
    public abstract Map<String, Ticket> getMapInstance();
}

梳理一下核心逻辑,可以发现,在此类中实现的方法,仅仅是和票据存储相关的(存储,编码解码),其余票据存储的前后逻辑,仍在其父类AbstractTicketRegistry中。

2.4 AbstractTicketRegistry

分析其最常用的增删改查方法:(代码过长,只贴部分)

1.存储票据时只校验一下是否过期(过期策略不是本章的重点),具体存储操作交由其子类来处理。
在这里插入图片描述
2.获取票据时会依据提供的类进行强转,每个票据获取时还会进行过期校验,如果过期会直接删除。

在这里插入图片描述
在这里插入图片描述

3.删除票据时,如果是TGT,还会将其授予的ST全部删除。

在这里插入图片描述

2.5 涉及设计模式

AbstractTicketRegistry类采用模版方法模式将具体的存储操作交由子类完成,拓展了存储的多样性。

AbstractMapBasedTicketRegistry类采用模版方法模式将Map的实例化交由其子类来完成,拓展了Map的多样性。

2.6 小结

DefaultTicketRegistry本质是将票据存储在ConcurrentHashMap中,将其初始容量,并发数,加密器拓展成了配置,并有默认配置。

DefaultTicketRegistry进行了分层设计,从顶级抽象类到该类,每个中间类都只是完成它管辖范围内的操作,其余操作交由其子类来具体实现。

3.ST存储策略以及和TGT的关系

上述是TGT的默认存储策略,我们来看一下ST是如何存储的。

3.1 ST创建链路

授予ST的入口在DefaultCentralAuthenticationServicegrantServiceTicket方法中,核心代码如下:

在这里插入图片描述
首先拿到ST的factory,通过factory创建ST,然后更新TGT,最后将ST进行存储。

其最终授予ST的核心代码如下:是通过ticketGrantingTicketgrantServiceTicket进行授予ST。
在这里插入图片描述
在这里插入图片描述

其中,在新建ST对象的时候,会关联TGT:
在这里插入图片描述

同时,在trackingPolicy.track(this, serviceTicket);中,会将ST关联的TGT的services MAP中。

在这里插入图片描述

其余票据生成过程不是本章关心的内容,这里主要分析其存储的关联关系。

总结:ST是由TGT实现类的某个方法授予的,ST在初始化的时候,指定了TGT属性进行关联,ST创建完成后,TGT会将ST加入到一个services的map中进行关联。在删除TGT的时候,也会将其关联的ST全部删除。

3.2 ST存储

存储ST的存储策略仍然是通过configurationContext.getTicketRegistry()获取的,与TGT完全一致。

4.如何做到票据存储策略的模块化拓展

以redis为例,分析如何拓展支持一种存储策略。

cas-server-support-redis-ticket-registry模块的config包下,注入了RedisTicketRegistry

在这里插入图片描述
注意此处是直接用的@Bean申明为一个实体,而在CasCoreTicketsConfiguration中,如果已经有Ticketregistry的实体,将不会再注入默认的票据存储策略。

在这里插入图片描述
此时Spring容器里面,Ticketregistry的实现实体就只有RedisTicketRegistry,那么在通过configurationContext.getTicketRegistry()获取票据存储策略的时候,得到的就是RedisTicketRegistry

注意,拓展支持的存储策略模块中的配置,都是使用@Bean进行注入的,并未申明对象名字,那么如果同时开启多个存储策略模块,SpringBoot将无法成功启动!


C.总结

  • CAS6.6中通过默认存储策略的@ConditionalOnMissingBean(name = TicketRegistry.BEAN_NAME)注解和拓展支持类中的@Bean实现了默认票据存储策略及其它拓展票据存储策略的支持。
  • DefaultTicketRegistry本质是将票据存储在ConcurrentHashMap中,将其初始容量,并发数,加密器拓展成了配置,并有默认配置。
  • DefaultTicketRegistry进行了分层设计,从顶级抽象类到该类,每个中间类都只是完成它管辖范围内的操作,其余操作交由其子类来具体实现。是一种经典的模版方法模式。
  • ST和TGT进行了关联,在删除TGT的时候,同时会删除ST。
  • 若需要新增一种存储策略,只需要依赖新模块后,用@Bean注解将Ticketregistry的新实现类注入到容器中,即可完成拓展。

D.展望

本文只着重分析了TicketRegistry下默认的票据存储策略和拓展支持的分析,对票据过期策略和票据清理策略等的设计还未分析,预计会在未来详细分析这些模块。

E.探究存储策略的设计

参考CAS的思路,为某种数据设计存储策略时,若要保障足够的拓展性,可以从以下几个方面进行考虑:

  • 将存储的过程进行详细的拆分,设计多级接口多级抽象类,每个类完成指定范围内的工作,剩下的操作使用模版方法模式拓展给子类进行实现。
  • 将凡是可能变的参数配置在配置文件中,并提供默认配置,这样能通过配置文件完成高度的拓展。
  • 通过使用@ConditionalOnMissingBean注解的方式为顶级接口注入默认的实现类,若要拓展出一种其他的存储策略,只需要实现顶级接口,并使用@Bean注入容器中,即可实现。

参考

截止2023-07-31为止,还没有专门分析CAS6源码的文章可检索,本文只参考了CAS6.6的源代码,所有分析过程均经过动态调试验证。


ATFWUS 2023-07-31

  • 26
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
cas client core(简称cas-client-core)是一个Java应用程序库,用于实现客户端与CAS(Central Authentication Service,中央认证服务)服务器之间的单点登录和身份验证。以下是对cas-client-core源码进行解析的说明。 cas-client-core的源码包含了许多类和接口,其主要目的是与CAS服务器进行通信并处理认证和授权的相关操作。其中一些重要的类和接口包括: 1. CasAuthenticationFilter:这是一个Servlet过滤器,拦截请求并将用户重定向到CAS服务器进行认证。一旦认证完成,它将负责创建和设置用户的认证信息。 2. CasAuthenticationProvider:作为Spring Security中的认证提供者,它实现了认证过程的逻辑。在经过CasAuthenticationFilter的认证之后,CasAuthenticationProvider将负责检查票据(ticket)是否有效,并构建和返回用户的认证对象。 3. CasTicketValidator:这个接口定义了用于验证CAS服务器返回的票据的方法。cas-client-core中提供了几个默认的实现,例如Cas10TicketValidator和Cas20ProxyingTicketValidator,它们适用于不同版本的CAS服务器。 4. CasAuthenticationToken:这是Spring Security中用于表示CAS认证信息的认证令牌。它继承了UsernamePasswordAuthenticationToken类,具有类似身份验证凭据和权限的功能。 5. CasAuthenticationEntryPoint:这个类实现了AuthenticationEntryPoint接口,用于在用户未经过身份验证时,将用户重定向到CAS服务器认证页面。 总体来说,cas-client-core的源码解析主要围绕着如何与CAS服务器进行通信、验证票据和构建认证信息等两个核心功能展开。通过理解这些关键类和接口的作用,我们可以更好地理解和使用cas-client-core库,并实现与CAS服务器的单点登录和身份验证。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

ATFWUS

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值