【CAS6.6源码解析】深入解析TGT和ST的唯一ID是怎样生成的-探究ID生成器的设计

news2024/11/15 17:37:11

CAS作为一款企业级中央认证服务系统,其票据的生成是非常重要的一环,在票据的生成中,有一个比较重要的点就是为票据生成唯一ID,本文将深入解析CAS系统中的TGT和ST的唯一ID是怎样生成的。

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


文章目录

  • A.涉及源码位置介绍
  • B.源码深入解析
    • 1.调用入口
    • 2.TGT默认唯一ID生成器分析
    • 3.默认NumericGenerator分析
    • 4.默认RandomStringGenerator分析
    • 5.总结:ID组装逻辑
  • C.总结


A.涉及源码位置介绍

1.票据生成相关factory位于cas-server-core-tickets-api模块下的factory包下(该处的factory是调用ID生成器的入口):
在这里插入图片描述

2.ID生成器位于cas-server-core-tickets-api模块下的util包下(这里是支持的各种ID生成器):
在这里插入图片描述

3.ID生成器涉及的一些工具类位于cas-server-core-util-api模块下的gen包下(包括数字生成、字符串生成等等):
在这里插入图片描述

B.源码深入解析

ID生成器会被所有票据所用到,这里先分析TGT使用的默认ID生成器,ST类似。本节将分别从调用入口,几个部分详细的介绍CAS中ID生成器的设计思路。

1.调用入口

ID生成器会在创建票据前调用,唯一ID是创建票据的必须参数。

1.在创建票据时,会进入到org.apereo.cas.ticket.factory.DefaultTicketGrantingTicketFactorycreate方法。
在这里插入图片描述

2.进入produceTicketIdentifier方法即可看到对ID生成器的调用:
在这里插入图片描述

3.去DefaultTicketGrantingTicketFactory中寻找默认注入的ticketGrantingTicketUniqueTicketIdGenerator是哪个实现类。源码位置:org.apereo.cas.config.CasCoreTicketsConfiguration
在这里插入图片描述
可以发现默认使用的是TicketGrantingTicketIdGenerator的实体,并且长度和后缀都是从配置文件中取的。查看配置类,可以看到默认的长度和后缀分别是(50和空):
在这里插入图片描述
在这里插入图片描述

总结上述四个过程,可以确定,从创建票据的入口处,最终会调到TicketGrantingTicketIdGeneratorgetNewTicketId方法。

2.TGT默认唯一ID生成器分析

1.先来看一下TicketGrantingTicketIdGenerator的类关系图:
在这里插入图片描述从上图可以发现,TGT的ID生成器实现了UniqueTicketIdGenerator接口,继承自HostNameBasedUniqueTicketIdGenerator

3.接下来看一下TicketGrantingTicketIdGenerator的源码:
在这里插入图片描述
很简单,只是表示用的是HostNameBasedUniqueTicketIdGenerator的类。

4.接下来看一下HostNameBasedUniqueTicketIdGenerator的源码:(省去注释)

public class HostNameBasedUniqueTicketIdGenerator extends DefaultUniqueTicketIdGenerator {
    public HostNameBasedUniqueTicketIdGenerator(final long maxLength, final String suffix) {
        super(maxLength, determineTicketSuffixByHostName(suffix));
    }
    private static String determineTicketSuffixByHostName(final String suffix) {
        if (StringUtils.isNotBlank(suffix)) {
            return suffix;
        }
        return InetAddressUtils.getCasServerHostName();
    }

}

可以发现,也很简单,就做了一件事情:是否配置了ID的后缀,没有则使用主机名作为ID的后缀。

5.上面两个类都很简单,所以重点在DefaultUniqueTicketIdGenerator中。直接上源码:

@Setter
public class DefaultUniqueTicketIdGenerator implements UniqueTicketIdGenerator {

    /**
     * The numeric generator to generate the static part of the id.
     */
    private NumericGenerator numericGenerator;

    /**
     * The RandomStringGenerator to generate the secure random part of the id.
     */
    private RandomStringGenerator randomStringGenerator;

    /**
     * Optional suffix to ensure uniqueness across JVMs by specifying unique
     * values.
     */
    private String suffix;

    /**
     * Creates an instance of DefaultUniqueTicketIdGenerator with default values
     * including a {@link DefaultLongNumericGenerator} with a starting value of
     * 1.
     */
    public DefaultUniqueTicketIdGenerator() {
        this(TICKET_SIZE);
    }

    /**
     * Creates an instance of DefaultUniqueTicketIdGenerator with a specified
     * maximum length for the random portion.
     *
     * @param maxLength the maximum length of the random string used to generate
     *                  the id.
     */
    public DefaultUniqueTicketIdGenerator(final long maxLength) {
        this(maxLength, null);
    }

    /**
     * Creates an instance of DefaultUniqueTicketIdGenerator with a specified
     * maximum length for the random portion.
     *
     * @param maxLength the maximum length of the random string used to generate
     *                  the id.
     * @param suffix    the value to append at the end of the unique id to ensure
     *                  uniqueness across JVMs.
     */
    public DefaultUniqueTicketIdGenerator(final long maxLength, final String suffix) {
        setMaxLength(maxLength);
        setSuffix(suffix);
    }

    /**
     * Creates an instance of DefaultUniqueTicketIdGenerator with a specified
     * maximum length for the random portion.
     *
     * @param numericGenerator      the numeric generator
     * @param randomStringGenerator the random string generator
     * @param suffix                the value to append at the end of the unique id to ensure
     *                              uniqueness across JVMs.
     * @since 4.1.0
     */
    public DefaultUniqueTicketIdGenerator(final NumericGenerator numericGenerator,
                                          final RandomStringGenerator randomStringGenerator,
                                          final String suffix) {
        this.randomStringGenerator = randomStringGenerator;
        this.numericGenerator = numericGenerator;
        setSuffix(suffix);
    }

    /**
     * Due to a bug in mod-auth-cas and possibly other clients in the way tickets are parsed,
     * the ticket id body is sanitized to remove the character "_", replacing it with "-" instead.
     * This might be revisited in the future and removed, once at least mod-auth-cas fixes
     * the issue.
     *
     * @param prefix The prefix we want attached to the ticket.
     * @return the ticket id
     */
    @Override
    public String getNewTicketId(final String prefix) {
        val number = this.numericGenerator.getNextNumberAsString();
        val ticketBody = this.randomStringGenerator.getNewString().replace('_', SEPARATOR);
        val origSuffix = StringUtils.defaultString(this.suffix);
        val finalizedSuffix = StringUtils.isEmpty(origSuffix) ? origSuffix : SEPARATOR + origSuffix;
        return prefix + SEPARATOR + number + SEPARATOR + ticketBody + finalizedSuffix;
    }

    /**
     * Sets max length of id generation.
     *
     * @param maxLength the max length
     */
    public void setMaxLength(final long maxLength) {
        this.randomStringGenerator = new Base64RandomStringGenerator(maxLength);
        this.numericGenerator = new DefaultLongNumericGenerator(1);
    }
}

其中有两个重要属性numericGeneratorrandomStringGenerator。可以看到这两个对象的都是在setMaxLength方法中进行的初始化,getNewTicketId方法其实只是对这两个生成器的内容做一次简单的组装。
这里对两个对象初始化的时候,给randomStringGenerator传入的参数是票据最大长度,给numericGenerator传入的是1。

3.默认NumericGenerator分析

首先来看第一个对象,DefaultLongNumericGenerator是怎样生成数字的。

1.类关系图如下:
在这里插入图片描述
主要实现了两个接口。源码如下:

public class DefaultLongNumericGenerator implements LongNumericGenerator {

    /**
     * The maximum length the string can be.
     */
    private static final int MAX_STRING_LENGTH = Long.toString(Long.MAX_VALUE).length();

    /**
     * The minimum length the String can be.
     */
    private static final int MIN_STRING_LENGTH = 1;

    private final AtomicLong count;

    /**
     * Instantiates a new default long numeric generator.
     */
    public DefaultLongNumericGenerator() {
        this(0);
    }

    /**
     * Instantiates a new default long numeric generator.
     *
     * @param initialValue the initial value
     */
    public DefaultLongNumericGenerator(final long initialValue) {
        this.count = new AtomicLong(initialValue);
    }

    @Override
    public long getNextLong() {
        return this.getNextValue();
    }

    @Override
    public String getNextNumberAsString() {
        return Long.toString(this.getNextValue());
    }

    @Override
    public int maxLength() {
        return DefaultLongNumericGenerator.MAX_STRING_LENGTH;
    }

    @Override
    public int minLength() {
        return DefaultLongNumericGenerator.MIN_STRING_LENGTH;
    }


    /**
     * Gets the next value.
     *
     * @return the next value. If the count has reached {@link Long#MAX_VALUE},
     * then {@link Long#MAX_VALUE} is returned. Otherwise, the next increment.
     */
    protected long getNextValue() {
        if (this.count.compareAndSet(Long.MAX_VALUE, 0)) {
            return Long.MAX_VALUE;
        }
        return this.count.getAndIncrement();
    }
}

2.初始化时,首先初始化了一个原子变量,值为0。并且定义了最小字符串长度为1,最大字符串长度为Long最大值转为字符串的长度。在使用的时候,传入的初始值位1。

3.核心方法getNextNumberAsString实际上就是产生一个自增后的数字,其中,这个数字的自增操作采用原子变量及CAS的方式保证了线程安全。并且当达到Long的最大值后,又会从0开始循环使用。

总结:DefaultLongNumericGenerator实际上就是在保证线程安全的前提下生成一个自增的数字。

4.默认RandomStringGenerator分析

再来看是如何生成随机字符串的。

1.Base64RandomStringGenerator的类关系如下:
在这里插入图片描述
源码如下:

@NoArgsConstructor
public class Base64RandomStringGenerator extends AbstractRandomStringGenerator {

    public Base64RandomStringGenerator(final long defaultLength) {
        super(defaultLength);
    }

    /**
     * Converts byte[] to String by Base64 encoding.
     *
     * @param random raw bytes
     * @return a converted String
     */
    @Override
    protected String convertBytesToString(final byte[] random) {
        return EncodingUtils.encodeUrlSafeBase64(random);
    }

}

可以发现主要是将父类生成的随机字节转换成了Base64.

2.查看父类AbstractRandomStringGenerator

@Getter
@RequiredArgsConstructor(access = AccessLevel.PROTECTED)
public abstract class AbstractRandomStringGenerator implements RandomStringGenerator {
    /**
     * An instance of secure random to ensure randomness is secure.
     */
    protected final SecureRandom randomizer = RandomUtils.getNativeInstance();

    /**
     * Default string length before encoding.
     */
    protected final long defaultLength;

    /**
     * Instantiates a new default random string generator
     * with length set to {@link RandomStringGenerator#DEFAULT_LENGTH}.
     */
    protected AbstractRandomStringGenerator() {
        this(DEFAULT_LENGTH);
    }

    @Override
    public String getAlgorithm() {
        return randomizer.getAlgorithm();
    }

    /**
     * Converts byte[] to String by simple cast. Subclasses should override.
     *
     * @param random raw bytes
     * @return a converted String
     */
    protected String convertBytesToString(final byte[] random) {
        return new String(random, StandardCharsets.UTF_8);
    }

    @Override
    public String getNewString(final int size) {
        val random = getNewStringAsBytes(size);
        return convertBytesToString(random);
    }

    @Override
    public String getNewString() {
        return getNewString(Long.valueOf(getDefaultLength()).intValue());
    }

    @Override
    public byte[] getNewStringAsBytes(final int size) {
        val random = new byte[size];
        this.randomizer.nextBytes(random);
        return random;
    }
}

核心是通过this.randomizer.nextBytes(random);生成随机字节。其中randomizer来自java.security包下的SecureRandom类。

可以在顶级接口RandomStringGenerator中看到,默认的随机字节大小为36,但是默认ID生成器在调用的时候,传入的最大长度是50。所以最终产生了长度为50的字节数组。
在这里插入图片描述
在这里插入图片描述

5.总结:ID组装逻辑

核心组装逻辑如下:
在这里插入图片描述
其中:

  • 前缀是在调用的时候传入的,是票据的类型:TGT或者ST。
  • 数字是全局自增的数字。
  • 字符串是随机50字节按BASE64进行编码,其中所有下划线会被替换成横杠。
  • 后缀是配置定义,若配置未定义则是主机名。
  • 每一个部分中间用横杠连接。

所以,默认的TGT ID是采用如下的规则生成的:

前缀(TGT或ST)-全局自增数字-随机50字节转成的BASE64-主机名或自定义后缀

一个TGT的样例如下:
TGT-1-OTGAU1o-LI-R-F-B1S3g8svY5kBDsQSeZ3sahJaZyP0k-GzFiywCjGRfYNc-FIdt84w-myMacBook-Pro

ST票据的默认ID生成除了前缀不同外,其余与TGT一致。

C.总结

从保障唯一性上来看:

  • 从TGT唯一ID的结构可以看出唯一性,是由一个全局自增的数字和随机的50字节保证的,后缀可以由用户自定义,不是用来保证唯一性的。
  • 注意,这个全局自增的数字是保存在内存中的,一旦CAS重启,这个数字将又会从0开始。随机的50个字节400位,产生冲突的概率极小。

从结构设计上来看:

  • 可拓展性极强,基本上ID生成的所有过程都可以拓展,比如数字生成过程、随机字符串生成过程,ID组装过程,、后缀等等。
  • 整个的ID生成器设计,将业务流程划分很明确,接口设计非常精细,可以多个类分离拓展完成的,绝不写在一个类里面。写在一个类里面意味着部分逻辑丧失拓展性。
  • 上述所有分析都只是针对CAS的默认配置而言,对于不同的业务,可以通过新增UniqueTicketIdGenerator接口的实现类,并注入容器中,实现自定义。或者采用overlay的方式重写某个实现类。

中肯评价:代码优美、拓展性极强


ATFWUS 2023-07-31

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/815252.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

W2NER详解

论文:https://arxiv.org/pdf/2112.10070.pdf 代码:https://github.com/ljynlp/W2NER 文章目录 W2NER介绍模型架构解码 源码介绍数据输入格式模型代码 参考资料 W2NER 介绍 W2NER模型,将NER任务转化预测word-word(备注&#xff…

基于flask旅游大数据可视化分析推荐系统-计算机毕设 附源码10903

flask旅游大数据可视化分析推荐系统 摘 要 信息化社会内需要与之针对性的信息获取途径,但是途径的扩展基本上为人们所努力的方向,由于站在的角度存在偏差,人们经常能够获得不同类型信息,这也是技术最为难以攻克的课题。针对旅游大…

味知香VS千味央厨,谁是预制菜新王?

夏日炎炎,预制菜赛道的下半场也硝烟弥漫,可谓“冰火两重天”。 预制菜赛道两大“玩家”:“预制菜第一股”味知香(605089.SH)、“餐饮供应链第一股”千味央厨(001215.SZ)均于近日公布了2023年一季报,其业绩有所分化。 …

3D Web轻量化渲染开发工具HOOPS Communicator是什么?

HOOPS Communicator是Tech Soft 3D旗下的主流产品之一,具有强大的、专用的高性能图形内核,是一款专注于基于Web端的高级3D工程应用程序。由HOOPS Server和HOOPS Web Viewer两大部分组成,提供了HOOPS Convertrer、Data Authoring的模型转换和编…

考试系统对教育评估的作用和意义

随着现代教育的发展,考试系统已经成为评估学生学业水平的重要工具。考试系统通过量化学生的知识掌握程度、学术能力和解决问题的能力,为教育评估提供了客观的数据基础。 考试系统能够帮助学校和教育部门全面了解学生的学习状况。通过考试结果&#xff0…

2023东三省“深圳杯”A题全保姆论文讲解

A题 影响城市居民身体健康的因素分析 以心脑血管疾病、糖尿病、恶性肿瘤以及慢性阻塞性肺病为代表的慢性非传染性疾病(以下简称慢性病)已经成为影响我国居民身体健康的重要问题。随着人们生活方式的改变,慢性病的患病率持续攀升。众所周知&am…

C#winform顺序打包成安装项目(VS2022)

一、在打包之前 (VS中需要包括Microsoft visual studio installer projects扩展项目) 1、在vs中找到扩展>管理扩展>搜索 installer projects 进行扩展的下载 2、右键Application Folder >点击 Add>点击项目输出>点击确认后>旁边则会生成一个主输出的文 3、…

132个心理性格趣味测试ACCESS\EXCEL数据库

今天又遇到了一个心理测试的数据库,这个数据库在表结构的设置上很直观,属于那种好的数据库结构,共分三个表,一个是测试项目描述表、一个是测试题选项得分表、一个是根据得分区间解析表,表与表之间通过“question_id”字…

WIFI模块常见的三种接口类型

什么是WIFI接口: WIFI接口是用于让设备无线连接到网络的功能,使你的电脑、手机、平板等设备可以通过无线信号连接到互联网或局域网。 1.USB接口 USB接口是平时见得最多的一种接口了,用在wifi模块上,它是一种通用串行总线&#…

docker如何运行容器?

文章目录 1 容器操作1.1容器相关命令1.2 创建并运行一个容器docker命令解析nacos启动成功 访问进入容器,修改配置文件 总结 接上集 CentOS 7安装Docker https://blog.csdn.net/qq_39017153/article/details/131955100 1 容器操作 1.1容器相关命令 容器操作的命令如…

【客户案例】云联壹云助力某保险公司搭建公有云费用管理平台

客户介绍 客户成立于 1996 年 11 月,现已拥有逾 2000 名员工和 12000 名营销员,为 280 万客户提供专业的金融保险服务。在上海、北京、广东、浙江、江苏、四川、山东、福建、重庆、辽宁、天津、湖北、河北、湖南和陕西等地的 50 多个城市稳步发展&#…

激光雷达在辅助驾驶领域正在沦为“花拳绣腿”?

摘要: 激光雷达的优点显而易见,但如何结合算法发挥出激光雷达的优势,我想除了主机厂以外,激光雷达厂商也可以主动参与,主动探索先进的融合感知算法,向行业不断证明:我不是个“花瓶”。 激光雷达…

CMake简介

文章目录 为什么需要头文件为什么 C 需要声明头文件 - 批量插入几行代码的硬核方式头文件进阶 - 递归地使用头文件 CMake什么是编译器多文件编译与链接CMake 的命令行调用为什么需要库(library)CMake 中的静态库与动态库CMake 中的子模块子模块的头文件如…

进入NetApp FAS存储系统loader的三种方法

有时候需要在loader模式下对系统硬件做一些offline的诊断,但offline 对系统物理部件做诊断需要进入到loader模式,如何从一个正常运行的系统进入到loader模式呢? 第一种就是启动的时候看到CtrlC的提示,就可以顺利进入loader。 如…

网络工程毕设-----基于华为ensp搭建校园网

本实验用华为模拟器ensp搭建简单的校园网络,其中用到的技术有动态路由协议OSPF,静态路由配置,HTTP、DNS以及FTP服务器的配置,PNAT端口地址转换协议,MSTP多生成树协议,VLAN划分及配置IP地址划分及配置等! 选…

机器学习实战:Python基于EM期望最大化进行参数估计(十五)

文章目录 1. 前言1.1 EM的介绍1.2 EM的应用场景 2. 高斯混合模型估计2.1 导入函数2.2 创建数据2.3 初始化2.4 Expectation Step2.5 Maximization step2.6 循环迭代可视化 3. 多维情况4. 讨论 1. 前言 1.1 EM的介绍 (Expectation-Maximization,EM&#…

实战案例:使用 Python 机器学习预测外卖送餐时间

现在的天气是一天比一天热,好多人周末休息在家的时候,就会选择点外卖,毕竟出去一趟又晒又热。 如果你太饿了,点餐太晚了,就可能去关注外卖员送餐到哪了,还有多少时间能送达。 这些信息在美团、饿了吗的Ap…

MapReduce原理剖析

一、基本介绍 MapReduce是Hadoop的核心,是Google提出的一个软件架构,用于大规模数据集(大于1TB)的并行运算。概念“Map(映射)”和“Reduce(化简)”,及他们的主要思想&am…

AWS 推出开源 AutoML 工具包“AutoGluon”

亚马逊网络服务最近推出了一个开源库,使开发人员只需几行代码即可在图像、文本或表格数据上实现深度学习模型。 AutoGluon 旨在成为一个易于使用且易于扩展的 AutoML 工具包,适合机器学习初学者和专家。它只需几行即可对深度学习模型进行原型设计;自动超…

stm8_独立看门狗配置顺序错误导致不断复位

1、问题 在配置stm8独立看门狗的时候,先设置分频、重载寄存器,然后启动看门狗,发现不断复位。 按照手册中的表格,看门狗的超时时间应该是1s,但是在这1s中多次喂狗也不断复位,然后排查到是配置顺序的问题&…