Skip to content

CAS 7.3 + Spring Boot 3.x + Java 21 新特性实战:拥抱现代 Java 生态的全面升级

作者: 必码 | bima.cc


前言:为什么需要这次升级

在企业级单点登录(SSO)领域,Apereo CAS 一直是最成熟、功能最丰富的开源解决方案之一。然而,技术生态的快速演进使得旧版本 CAS 面临着诸多挑战:

  • 安全合规压力:CAS 5.3 基于 Spring Boot 1.x 和 Java 8,已无法获得安全补丁支持
  • 生态兼容性:Spring Boot 2.x 已于 2023 年底结束商业支持,大量第三方库停止维护旧版 API
  • 性能瓶颈:传统线程模型在高并发场景下的资源开销问题日益突出
  • 云原生需求:容器化、Kubernetes 编排、Service Mesh 等现代运维体系要求应用具备更好的适配能力

CAS 7.3.4 的发布,标志着 Apereo CAS 正式全面拥抱现代 Java 生态。它基于 Java 21(LTS)、Spring Boot 3.5.6、Spring Framework 6.x 和 Jakarta EE 10 构建,是一次从底层运行时到上层应用架构的全面升级。

本文将基于我们团队实际完成的 CAS Overlay 项目迁移(从 CAS 5.3/6.6 升级至 7.3.4),逐一解析每个技术维度的变化,并提供可直接参考的配置示例和代码片段。


一、CAS 7.3 技术栈全景

1.1 核心版本矩阵

CAS 7.3 不是一个简单的功能迭代,而是一次底层技术栈的全面换代。以下是核心依赖版本对照:

技术组件CAS 5.3CAS 6.6CAS 7.3变化说明
Java8 / 1111 / 1721 (LTS)最低要求 Java 21
Spring Boot1.5.x2.7.x3.5.6大版本跨越
Spring Framework4.3.x5.3.x6.x核心框架重构
Gradle4.10.37.6.49.1.0构建工具全面升级
Jakarta EEJava EE 7Java EE 8Jakarta EE 10命名空间全面迁移
Servlet3.14.06.0API 规范升级
MyBatis-Spring1.3.11.3.13.0.3跨越式版本升级
连接池commons-dbcp 1.4commons-dbcp 1.4commons-dbcp2 2.10.0全新连接池实现
Bootstrap3.x4.x5.3.3前端框架大版本升级
jQuery2.x3.x3.7.1稳定版本
Docker 基础镜像openjdk:8-jreadoptopenjdk:11-jreazul/zulu-openjdk:21更现代的 JDK 发行版

1.2 Gradle 构建配置概览

CAS 7.3 的 gradle.properties 文件清晰地定义了整个技术栈的版本基线:

properties
# CAS 7.3.4 核心版本定义
cas.version=7.3.4
springBootVersion=3.5.6

# Java 版本要求(源码和目标字节码均为 21)
sourceCompatibility=21
targetCompatibility=21

# JVM 供应商选择(支持多种发行版)
jvmVendor=AMAZON

# 构建工具版本
lombokVersion=1.18.42
jibVersion=3.5.3
gradleCyclonePluginVersion=3.2.0
gradleDockerPluginVersion=9.4.0
gradleFreeFairPluginVersion=9.2.0

# 容器镜像配置
baseDockerImage=azul/zulu-openjdk:21
containerImageOrg=apereo
containerImageName=cas

1.3 依赖管理机制变化

CAS 7.3 采用了更加严格的依赖管理策略。在 build.gradle 中,依赖管理通过两个核心 BOM(Bill of Materials)实现:

groovy
dependencies {
    // CAS BOM:统一管理所有 CAS 模块版本
    implementation enforcedPlatform("org.apereo.cas:cas-server-support-bom:${project.'cas.version'}")

    // Spring Boot BOM:统一管理 Spring 生态版本
    implementation platform(org.springframework.boot.gradle.plugin.SpringBootPlugin.BOM_COORDINATES)
}

关键变化点:

  1. enforcedPlatform 替代了旧版的 platform,强制使用 BOM 中指定的版本,不允许任何版本覆盖
  2. 所有 CAS 模块依赖无需显式指定版本号,由 BOM 统一管控
  3. 移除了 io.spring.gradle:dependency-management-plugin,改用 Gradle 原生的 platform 机制

1.4 Java Toolchain 机制

CAS 7.3 引入了 Gradle Java Toolchain,实现构建 JDK 与运行 JDK 的分离:

groovy
java {
    toolchain {
        languageVersion = JavaLanguageVersion.of(project.targetCompatibility)

        // 支持多种 JVM 供应商
        def chosenJvmVendor = null
        if (project.jvmVendor != null) {
            try {
                // Gradle 8.x 枚举写法
                chosenJvmVendor = JvmVendorSpec."${project.jvmVendor?.toUpperCase()}"
            } catch (MissingPropertyException e) {
                // Gradle 7.x 兼容回退
                chosenJvmVendor = JvmVendorSpec.of(project.jvmVendor?.toUpperCase())
            }
        }
        if (chosenJvmVendor != null) {
            vendor = chosenJvmVendor
        }
    }
}

这意味着你可以在 JDK 17 上运行 Gradle,但编译目标仍然是 JDK 21。Gradle 会自动检测并下载所需的 JDK 版本(通过 Foojay 插件)。


二、Java 21 新特性在 CAS 中的应用

Java 21 于 2023 年 9 月正式发布,是继 Java 17 之后的最新 LTS 版本。它带来了多项革命性特性,这些特性在 CAS 7.3 的内部实现和我们的 Overlay 项目中都有广泛应用。

2.1 Record 类:不可变数据载体

JEP 395/JSR 390 - 正式特性

Record 类是 Java 引入的用于定义不可变数据载体的声明式语法。在 CAS 7.3 中,Record 被大量用于定义配置模型、认证凭证和服务注册信息等数据传输对象。

传统方式 vs Record 方式

java
// ===== 传统方式(CAS 5.x/6.x 时代)=====
public class CasServiceDefinition {
    private final String serviceId;
    private final String name;
    private final long id;
    private final int evaluationOrder;
    private final boolean enabled;

    public CasServiceDefinition(String serviceId, String name,
                                long id, int evaluationOrder, boolean enabled) {
        this.serviceId = serviceId;
        this.name = name;
        this.id = id;
        this.evaluationOrder = evaluationOrder;
        this.enabled = enabled;
    }

    // 需要手动编写 getter、equals、hashCode、toString...
    public String getServiceId() { return serviceId; }
    public String getName() { return name; }
    public long getId() { return id; }
    public int getEvaluationOrder() { return evaluationOrder; }
    public boolean isEnabled() { return enabled; }

    @Override
    public boolean equals(Object o) { /* ... */ }

    @Override
    public int hashCode() { /* ... */ }

    @Override
    public String toString() { /* ... */ }
}

// ===== Record 方式(CAS 7.3 时代)=====
public record CasServiceDefinition(
    String serviceId,
    String name,
    long id,
    int evaluationOrder,
    boolean enabled
) {}

Record 的核心优势:

  1. 零样板代码:编译器自动生成构造器、访问器、equals()hashCode()toString()
  2. 不可变性保证:所有字段隐式为 final,线程安全
  3. 语义清晰:类声明即文档,一眼就能看出这是一个纯数据载体
  4. 与 Pattern Matching 深度集成:可以在 switchinstanceof 中直接解构

在 CAS Overlay 中的实际应用

在我们的自定义认证模块中,使用 Record 定义认证请求和响应:

java
/**
 * 认证请求 - 使用 Record 定义不可变数据载体
 */
public record AuthenticationRequest(
    String username,
    String credential,
    String clientIp,
    String serviceUrl,
    Instant timestamp
) {
    // 紧凑构造器用于参数校验
    public AuthenticationRequest {
        Objects.requireNonNull(username, "用户名不能为空");
        Objects.requireNonNull(credential, "凭证不能为空");
        if (username.isBlank()) {
            throw new IllegalArgumentException("用户名不能为空白");
        }
    }

    // 可以添加自定义方法
    public boolean isFromInternalNetwork() {
        return clientIp != null && clientIp.startsWith("10.");
    }
}

/**
 * 认证结果 - 密封接口配合 Record 使用
 */
public sealed interface AuthResult
    permits AuthResult.Success, AuthResult.Failure, AuthResult.RequiresMfa {

    record Success(String principal, Map<String, Object> attributes,
                   Instant authenticatedAt) implements AuthResult {}

    record Failure(String errorCode, String errorMessage,
                   int remainingAttempts) implements AuthResult {}

    record RequiresMfa(String principal, List<String> availableFactors)
        implements AuthResult {}
}

2.2 Pattern Matching for switch

JEP 441 - 正式特性

Java 21 将 Pattern Matching for switch 提升为正式特性,允许在 switch 中直接进行类型模式匹配,极大地简化了条件分支代码。

传统 switch vs 模式匹配 switch

java
// ===== 传统方式 =====
public String processAuthenticationResult(Object result) {
    if (result instanceof AuthResult.Success) {
        AuthResult.Success success = (AuthResult.Success) result;
        return "认证成功: " + success.principal();
    } else if (result instanceof AuthResult.Failure) {
        AuthResult.Failure failure = (AuthResult.Failure) result;
        return "认证失败: " + failure.errorMessage();
    } else if (result instanceof AuthResult.RequiresMfa) {
        AuthResult.RequiresMfa mfa = (AuthResult.RequiresMfa) result;
        return "需要多因素认证: " + mfa.availableFactors();
    }
    return "未知结果";
}

// ===== Pattern Matching for switch(Java 21)=====
public String processAuthenticationResult(Object result) {
    return switch (result) {
        case AuthResult.Success(var principal, var attrs, var time)
            -> "认证成功: " + principal + " 于 " + time;

        case AuthResult.Failure(var code, var msg, var remaining)
            -> "认证失败[%s]: %s (剩余尝试次数: %d)".formatted(code, msg, remaining);

        case AuthResult.RequiresMfa(var principal, var factors)
            -> "需要多因素认证: " + String.join(", ", factors);

        case null -> "结果为空";
        default -> "未知结果类型: " + result.getClass().getSimpleName();
    };
}

Record 解构模式

当 Record 与 Pattern Matching 结合时,可以直接在 case 标签中解构 Record 的组件:

java
public void handleCasEvent(Object event) {
    switch (event) {
        // 直接解构 Record 组件
        case TicketGrantingTicketCreated(
            var ticketId, var principal, var service
        ) -> {
            log.info("TGT 创建: ticket={}, principal={}, service={}",
                ticketId, principal, service);
        }

        case ServiceTicketValidated(
            var ticketId, var principal, var attributes
        ) when attributes.containsKey("memberOf") -> {
            // 带守卫条件的模式匹配
            var groups = (List<String>) attributes.get("memberOf");
            log.info("管理员验证通过: {}, 组: {}", principal, groups);
        }

        case AuthenticationSuccess(
            var principal, _, var timestamp  // 使用 _ 忽略不需要的组件
        ) -> {
            log.info("认证成功: {} at {}", principal, timestamp);
        }

        default -> log.debug("未处理的事件: {}", event);
    }
}

守卫条件(Guard) 是 Java 21 中新增的语法,允许在模式匹配后追加 when 子句进行额外条件过滤。这在 CAS 的事件处理和认证策略路由中极为有用。

2.3 Sealed Classes(密封类)

JEP 409/JSR 417 - 正式特性

密封类允许开发者精确控制哪些类可以继承或实现一个给定的类型。在 CAS 7.3 中,密封类被用于构建类型安全的认证处理器层次结构。

密封类在认证体系中的应用

java
/**
 * 密封接口:认证处理器基类
 * 仅允许指定的三个子类实现
 */
public sealed interface AuthenticationHandler
    permits UsernamePasswordHandler,
            X509CertificateHandler,
            OAuth2TokenHandler {

    AuthenticationResult authenticate(AuthenticationRequest request)
        throws AuthenticationException;

    boolean supports(CredentialType type);
}

// ===== 允许的子类实现 =====

/**
 * 用户名密码认证处理器 - final 不可再被继承
 */
public final class UsernamePasswordHandler implements AuthenticationHandler {
    @Override
    public AuthenticationResult authenticate(AuthenticationRequest request) {
        // 实现用户名密码认证逻辑
        return new AuthenticationResult.Success(request.username());
    }

    @Override
    public boolean supports(CredentialType type) {
        return type == CredentialType.USERNAME_PASSWORD;
    }
}

/**
 * X.509 证书认证处理器
 */
public final class X509CertificateHandler implements AuthenticationHandler {
    @Override
    public AuthenticationResult authenticate(AuthenticationRequest request) {
        // 实现 X.509 证书认证逻辑
        return new AuthenticationResult.Success(request.username());
    }

    @Override
    public boolean supports(CredentialType type) {
        return type == CredentialType.X509_CERTIFICATE;
    }
}

/**
 * OAuth2 Token 认证处理器
 */
public final class OAuth2TokenHandler implements AuthenticationHandler {
    @Override
    public AuthenticationResult authenticate(AuthenticationRequest request) {
        // 实现 OAuth2 Token 认证逻辑
        return new AuthenticationResult.Success(request.username());
    }

    @Override
    public boolean supports(CredentialType type) {
        return type == CredentialType.OAUTH2_TOKEN;
    }
}

密封类的三层权限控制:

修饰符含义适用场景
sealed仅允许列出的类继承基类/接口
non-sealed重新开放继承中间层
final完全封闭,不可继承叶子节点

密封类与 Pattern Matching 的协同

密封类最大的价值在于与 Pattern Matching 的协同——编译器能够检查 switch 表达式是否穷尽了所有可能的子类型:

java
public String getHandlerDescription(AuthenticationHandler handler) {
    // 编译器会检查是否覆盖了所有 AuthenticationHandler 的子类型
    // 如果遗漏某个子类,会发出警告
    return switch (handler) {
        case UsernamePasswordHandler h -> "用户名密码认证";
        case X509CertificateHandler h  -> "X.509 证书认证";
        case OAuth2TokenHandler h       -> "OAuth2 Token 认证";
        // 不需要 default 分支,编译器知道所有可能的子类型
    };
}

2.4 Virtual Threads(虚拟线程)

JEP 444 - 正式特性

虚拟线程是 Java 21 中最具革命性的特性。它引入了一种轻量级线程实现,由 JVM 而非操作系统管理,可以轻松创建数百万个线程而不会耗尽系统资源。

传统线程 vs 虚拟线程

java
// ===== 传统平台线程(每个线程占用约 1MB 栈空间)=====
ExecutorService executor = Executors.newFixedThreadPool(200);
// 200 个并发已经是传统线程池的上限

// ===== 虚拟线程(每个线程仅占用约 1KB 内存)=====
ExecutorService executor = Executors.newVirtualThreadPerTaskExecutor();
// 可以轻松处理百万级并发任务

在 CAS 中的潜在应用场景

CAS 作为 SSO 服务器,在高并发登录场景下(如开学季集中认证),虚拟线程可以显著提升吞吐量:

java
/**
 * 使用虚拟线程处理并发认证请求
 */
@Configuration
public class VirtualThreadConfig {

    @Bean
    public AsyncTaskExecutor asyncTaskExecutor() {
        // Spring Boot 3.2+ 原生支持虚拟线程
        var executor = new SimpleAsyncTaskExecutor("cas-virtual-");
        executor.setVirtualThreads(true);
        return executor;
    }
}

application.yml 中启用虚拟线程:

yaml
spring:
  threads:
    virtual:
      enabled: true

虚拟线程的性能优势:

指标平台线程虚拟线程
线程创建成本~1ms~1μs(快 1000 倍)
内存占用~1MB/线程~1KB/线程
最大线程数~数千~数百万
上下文切换操作系统内核态JVM 用户态
适用场景CPU 密集型I/O 密集型

注意: 虚拟线程最适合 I/O 密集型操作(如数据库查询、HTTP 调用、LDAP 查询等),这正是 CAS 认证流程的典型特征。对于 CPU 密集型操作(如加密解密),仍应使用平台线程。

2.5 String Templates(字符串模板)

JEP 430 - 预览特性

字符串模板是 Java 21 中的预览特性,提供了一种更安全、更灵活的字符串插值机制。虽然目前仍是预览状态,但 CAS 7.3 的代码库中已经可以看到相关的设计思路。

java
// ===== 传统字符串拼接 =====
String message = "用户 " + username + " 于 " + Instant.now() + " 从 " + clientIp + " 登录成功";

// ===== String.format(类型不安全)=====
String message = String.format("用户 %s 于 %s 从 %s 登录成功", username, Instant.now(), clientIp);

// ===== String Templates(Java 21 预览特性,需启用 --enable-preview)=====
String message = STR."用户 \{username} 于 \{Instant.now()} 从 \{clientIp} 登录成功";

// ===== 模板处理器可以自定义验证逻辑 =====
// 例如:SQL 参数化查询模板
String query = SQL."SELECT * FROM users WHERE username = \{username} AND status = \{status}";
// SQL 模板处理器会自动进行参数绑定,防止 SQL 注入

String Templates 的四种内置模板处理器:

处理器功能示例
STR基本字符串插值STR."Hello \{name}"
FMT格式化插值FMT."Value: %.2f\{price}"
RAW原始片段访问RAW."...\{expr}..."
JSONJSON 字符串构建JSON."{\"name\": \"\{name}\"}"

2.6 Generational ZGC(分代 ZGC)

JEP 439 - 正式特性

Generational ZGC 是 ZGC 垃圾收集器的分代增强版本。它将堆内存分为年轻代和老年代,针对 CAS 这类"大部分对象朝生夕灭"的应用场景进行了深度优化。

启用 Generational ZGC

bash
# 启用分代 ZGC(Java 21 默认启用)
java -XX:+UseZGC -XX:+ZGenerational -jar cas.jar

# 推荐的 JVM 参数配置
java \
  -XX:+UseZGC \
  -XX:+ZGenerational \
  -Xms2g \
  -Xmx4g \
  -XX:+AlwaysPreTouch \
  -XX:MaxGCPauseMillis=10 \
  -jar cas.jar

ZGC 分代 vs 非分代的性能对比

指标ZGC(非分代)Generational ZGCG1GC
最大暂停时间< 1ms< 1ms~200ms
吞吐量~95%~98%~92%
内存开销较高中等较低
适用堆大小8MB-16TB8MB-16TB< 32GB
分代支持

在 CAS 场景中的实际效果:

CAS 的典型工作负载特征是大量短生命周期的 Ticket 对象(TGT、ST、PGT 等)。Generational ZGC 的年轻代回收可以高效清理这些临时对象,将 GC 停顿时间降低 90% 以上,同时提升整体吞吐量。

2.7 Foreign Function & Memory API(FFM API)

JEP 442 - 预览特性

FFM API 提供了一种安全、高效的方式与原生代码(C/C++ 库)和原生内存交互,替代了传统的 JNI 机制。

java
// ===== 传统 JNI 方式(需要编写 C 代码、生成头文件、编译动态链接库)=====
// 步骤:1. 编写 Java native 方法声明
//       2. javah 生成 C 头文件
//       3. 编写 C 实现代码
//       4. 编译为动态链接库
//       5. System.loadLibrary() 加载

// ===== FFM API 方式(纯 Java 实现)=====
import java.lang.foreign.*;
import java.lang.invoke.*;

// 链接 C 标准库的 strlen 函数
Linker linker = Linker.nativeLinker();
SymbolLookup stdlib = linker.defaultLookup();
MethodHandle strlen = linker.downcallHandle(
    stdlib.lookup("strlen").orElseThrow(),
    FunctionDescriptor.of(ValueLayout.JAVA_LONG, ValueLayout.ADDRESS)
);

// 调用原生函数
try (Arena arena = Arena.ofConfined()) {
    MemorySegment str = arena.allocateFrom("Hello CAS 7.3!");
    long len = (long) strlen.invokeExact(str);
    System.out.println("字符串长度: " + len);
}

在 CAS 中的潜在应用: FFM API 可以用于与 LDAP 的原生 C 库、Kerberos 库以及硬件安全模块(HSM)进行高性能交互,无需编写 JNI 桥接代码。


三、Spring Boot 3.x 迁移要点

从 Spring Boot 2.x 到 3.x 是一次跨越式升级,涉及命名空间变更、API 重构和自动配置机制的全面调整。

3.1 javax 到 jakarta 命名空间迁移

这是 Spring Boot 3.x 迁移中最基础、影响面最广的变化。Jakarta EE 将包名从 javax.* 迁移到 jakarta.*,这是一个不可逆的破坏性变更。

java
// ===== Spring Boot 2.x(javax 命名空间)=====
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.Filter;
import javax.validation.Valid;
import javax.validation.constraints.NotBlank;
import javax.persistence.Entity;
import javax.persistence.Id;

// ===== Spring Boot 3.x(jakarta 命名空间)=====
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import jakarta.servlet.Filter;
import jakarta.validation.Valid;
import jakarta.validation.constraints.NotBlank;
import jakarta.persistence.Entity;
import jakarta.persistence.Id;

批量迁移策略:

对于大型项目,建议使用 OpenRewrite 进行自动化迁移:

xml
<!-- pom.xml 中添加 OpenRewrite 插件 -->
<plugin>
    <groupId>org.openrewrite.maven</groupId>
    <artifactId>rewrite-maven-plugin</artifactId>
    <version>5.20.0</version>
    <configuration>
        <activeRecipes>
            <recipe>org.openrewrite.java.migrate.jakarta.JavaxMigrationToJakarta</recipe>
        </activeRecipes>
    </configuration>
</plugin>

对于 Gradle 项目:

groovy
// build.gradle 中添加 OpenRewrite 插件
plugins {
    id 'org.openrewrite.rewrite' version '6.8.0'
}

rewrite {
    activeRecipe('org.openrewrite.java.migrate.jakarta.JavaxMigrationToJakarta')
}

3.2 Spring Security 6.x 变化

Spring Security 6.x 带来了声明式安全配置的重大变化,从传统的继承 WebSecurityConfigurerAdapter 转向组件化的配置方式。

java
// ===== Spring Boot 2.x 方式(已废弃)=====
@Configuration
@EnableWebSecurity
public class CasSecurityConfig extends WebSecurityConfigurerAdapter {

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests()
            .antMatchers("/cas/status/**").permitAll()
            .antMatchers("/cas/login").permitAll()
            .anyRequest().authenticated()
            .and()
            .csrf().disable();
    }
}

// ===== Spring Boot 3.x 方式(组件化配置)=====
@Configuration
@EnableWebSecurity
public class CasSecurityConfig {

    @Bean
    public SecurityFilterChain casSecurityFilterChain(HttpSecurity http) throws Exception {
        http
            .authorizeHttpRequests(auth -> auth
                .requestMatchers("/cas/status/**").permitAll()
                .requestMatchers("/cas/login").permitAll()
                .anyRequest().authenticated()
            )
            .csrf(csrf -> csrf.disable())
            .headers(headers -> headers
                .contentSecurityPolicy(csp -> csp
                    .policyDirectives("default-src 'self'")
                )
            );
        return http.build();
    }
}

关键 API 变更对照:

Spring Security 5.xSpring Security 6.x说明
antMatchers()requestMatchers()匹配器方法重命名
authorizeRequests()authorizeHttpRequests()授权配置方法重命名
WebSecurityConfigurerAdapterSecurityFilterChain Bean配置方式重构
access("hasRole('ADMIN')")hasRole("ADMIN")简化表达式
and()链式调用不再需要 and() 连接

3.3 Spring MVC vs WebFlux 选择

CAS 7.3 默认使用 Spring MVC(基于 Servlet),但也支持 WebFlux(基于 Reactive)。在 CAS 的典型使用场景中,我们推荐继续使用 Spring MVC,原因如下:

维度Spring MVCWebFlux
编程模型同步/阻塞异步/非阻塞
学习曲线
生态兼容性成熟发展中
CAS 支持度完整支持部分支持
适用场景传统 Web 应用高并发流式处理
yaml
# application.yml - 确保 CAS 使用 Servlet 栈
spring:
  main:
    web-application-type: servlet

3.4 自动配置变更

Spring Boot 3.x 对自动配置机制进行了多项调整:

yaml
# ===== 旧版配置(Spring Boot 2.x)=====
spring:
  http:
    encoding:
      charset: UTF-8
      enabled: true
      force: true

# ===== 新版配置(Spring Boot 3.x)=====
server:
  servlet:
    encoding:
      charset: UTF-8
      enabled: true
      force: true

主要配置属性迁移对照:

旧属性路径新属性路径说明
spring.http.encoding.charsetserver.servlet.encoding.charset字符编码
spring.http.encoding.enabledserver.servlet.encoding.enabled编码启用
spring.http.encoding.forceserver.servlet.encoding.force强制编码
spring.mvc.localespring.web.locale区域设置
spring.mvc.locale-resolverspring.web.locale-resolver区域解析器
management.endpoints.web.base-pathmanagement.endpoints.web.base-path端点路径(不变)
management.endpoints.web.exposure.includemanagement.endpoints.web.exposure.include端点暴露(不变)

3.5 management.endpoints 新配置方式

CAS 7.3 对 Actuator 端点配置进行了优化:

yaml
# CAS 7.3 推荐的 Actuator 配置
management:
  endpoints:
    web:
      exposure:
        include: health,info
        exclude: refresh,shutdown
      base-path: /status
  endpoint:
    health:
      enabled: true
      show-details: when_authorized
    info:
      enabled: true
  security:
    enabled: true
    roles: ACTUATOR,ADMIN
  health:
    status:
      order: WARN, DOWN, OUT_OF_SERVICE, UNKNOWN, UP

Spring Boot 3.5.x 新增的 Actuator 特性:

  1. 严格的 .enabled 属性.enabled 配置项现在只接受 truefalse,不再接受 on/off/yes/no 等变体
  2. 健康状态分组:支持自定义健康状态分组,便于不同角色查看不同粒度的健康信息
  3. 端点缓存:新增端点响应缓存配置,减少频繁查询的开销

四、Jakarta EE 10 迁移

Jakarta EE 10 是 Jakarta EE 迁移到 Eclipse 基金会后的第三个主要版本,带来了全面的 API 更新。

4.1 命名空间迁移全览

Jakarta EE 10 的核心变化是所有 Java EE/Jakarta EE 规范的包名从 javax.* 迁移到 jakarta.*。以下是 CAS 项目中涉及的主要迁移:

java
// ===== Servlet API =====
// 旧: import javax.servlet.http.HttpServletRequest;
// 新:
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import jakarta.servlet.http.HttpSession;
import jakarta.servlet.Filter;
import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;

// ===== Validation API =====
// 旧: import javax.validation.Valid;
// 新:
import jakarta.validation.Valid;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Size;
import jakarta.validation.ConstraintViolation;

// ===== Persistence API (JPA) =====
// 旧: import javax.persistence.Entity;
// 新:
import jakarta.persistence.Entity;
import jakarta.persistence.Id;
import jakarta.persistence.Table;
import jakarta.persistence.Column;
import jakarta.persistence.GeneratedValue;

// ===== Mail API =====
// 旧: import javax.mail.internet.MimeMessage;
// 新:
import jakarta.mail.internet.MimeMessage;
import jakarta.mail.internet.InternetAddress;
import jakarta.mail.Session;

// ===== XML Binding (JAXB) =====
// 旧: import javax.xml.bind.JAXBContext;
// 新:
import jakarta.xml.bind.JAXBContext;
import jakarta.xml.bind.Marshaller;
import jakarta.xml.bind.Unmarshaller;

4.2 影响范围分析

在我们的 CAS Overlay 项目中,Jakarta EE 10 迁移的影响范围如下:

影响领域影响程度迁移工作量说明
CAS 核心代码CAS 7.3 已完成内部迁移
自定义认证处理器替换 import 语句即可
自定义 Web Filter替换 import 语句即可
MyBatis 集成极低MyBatis 不直接依赖 Jakarta
数据库配置极低commons-dbcp2 已使用 jakarta
前端模板Thymeleaf 模板不受影响
第三方依赖需检查所有依赖的兼容性

4.3 第三方依赖兼容性处理

在迁移过程中,我们发现部分第三方依赖尚未完成 Jakarta 迁移。CAS 7.3 的 build.gradle 中通过排除机制处理了这些冲突:

groovy
configurations {
    all {
        exclude(group: "cglib", module: "cglib")
        exclude(group: "cglib", module: "cglib-full")
        exclude(group: "org.slf4j", module: "slf4j-log4j12")
        exclude(group: "org.slf4j", module: "slf4j-simple")
        exclude(group: "ch.qos.logback", module: "logback-core")
        exclude(group: "ch.qos.logback", module: "logback-classic")
    }
}

特别注意: javax.mail:mail:1.4.7 是一个遗留依赖。在 CAS 7.3 中,应使用 org.eclipse.angus:angus-mailcom.sun.mail:jakarta.mail 替代:

groovy
// 推荐的 Jakarta Mail 依赖
implementation 'org.eclipse.angus:angus-mail:2.0.2'
// 或
implementation 'com.sun.mail:jakarta.mail:2.0.1'

五、CAS 7.3 架构变化

CAS 7.3 在架构层面引入了多项重要变化,这些变化直接影响 Overlay 项目的配置和自定义方式。

5.1 CasInitializerConfig:全新初始化架构

CAS 7.3 引入了全新的 CasInitializerConfig 机制,替代了旧版的 CasWebApplicationInitializer。这是 CAS 初始化流程的根本性重构。

java
// ===== CAS 6.x 初始化方式 =====
public class CasWebApplicationInitializer
        extends SpringBootServletInitializer {

    @Override
    protected SpringApplicationBuilder configure(
            SpringApplicationBuilder builder) {
        return builder
            .sources(CasWebApplication.class)
            .bannerMode(Banner.Mode.OFF);
    }
}

// ===== CAS 7.3 初始化方式 =====
@Configuration
public class CasInitializerConfig {

    @Bean
    @ConditionalOnMissingBean
    public CasWebApplicationInitializer casWebApplicationInitializer() {
        return new CasWebApplicationInitializer();
    }
}

关键变化:

  1. 模块化初始化:初始化逻辑从单一的 ServletInitializer 拆分为多个可配置的 @Bean
  2. 条件装配:通过 @ConditionalOnMissingBean 允许 Overlay 项目灵活覆盖默认行为
  3. 嵌入式容器支持:Tomcat、Jetty、Undertow 的初始化配置独立管理

5.2 @RefreshScope 配置热刷新

CAS 7.3 增强了对 @RefreshScope 的支持,允许在不重启应用的情况下动态更新配置:

java
@Configuration
public class DynamicCasConfiguration {

    /**
     * 可热刷新的服务注册配置
     * 当配置中心推送更新时,该 Bean 会被重新创建
     */
    @Bean
    @RefreshScope
    public ServiceRegistryConfig serviceRegistryConfig(
            @Value("${cas.service-registry.json.location}") String location) {
        return new ServiceRegistryConfig(location);
    }

    /**
     * 可热刷新的认证策略配置
     */
    @Bean
    @RefreshScope
    public AuthenticationPolicyConfig authenticationPolicyConfig() {
        return new AuthenticationPolicyConfig(
            maxAttempts = 5,
            lockDuration = Duration.ofMinutes(30)
        );
    }
}

5.3 @ConditionalOnMissingBean 条件装配

CAS 7.3 大量使用 Spring Boot 的条件装配注解,使得 Overlay 项目的自定义配置更加优雅:

java
@Configuration
public class CasOverlayOverrideConfiguration {

    /**
     * 自定义数据源配置
     * 仅当容器中不存在 DataSource Bean 时生效
     */
    @Bean
    @ConditionalOnMissingBean(DataSource.class)
    public DataSource customDataSource() {
        var ds = new BasicDataSource();
        ds.setDriverClassName("com.mysql.cj.jdbc.Driver");
        ds.setUrl("jdbc:mysql://localhost:3306/cas_db");
        ds.setUsername("cas_user");
        ds.setPassword("secure_password");
        ds.setInitialSize(5);
        ds.setMaxTotal(100);
        ds.setMaxIdle(30);
        ds.setMinIdle(5);
        return ds;
    }

    /**
     * 自定义认证事件发布器
     */
    @Bean
    @ConditionalOnMissingBean(AuthenticationEventPublisher.class)
    public AuthenticationEventPublisher customEventPublisher(
            ApplicationEventPublisher publisher) {
        return new DefaultAuthenticationEventPublisher(publisher);
    }
}

5.4 显式清除默认认证处理器

CAS 7.3 改变了认证处理器的注册机制。在旧版本中,CAS 会自动注册一组默认的认证处理器(如 AcceptUsersAuthenticationHandler)。在 7.3 中,需要显式控制这些默认行为:

yaml
# application.yml - 显式禁用默认的静态用户认证
cas:
  authn:
    accept:
      enabled: false
      users: ""
java
/**
 * 显式清除不需要的默认认证处理器
 * 并注册自定义处理器
 */
@Configuration
public class CustomAuthenticationConfiguration {

    @Bean
    public AuthenticationHandler customDatabaseAuthenticationHandler(
            DataSource dataSource) {

        // 创建自定义的数据库认证处理器
        var handler = new QueryDatabaseAuthenticationHandler();
        handler.setDataSource(dataSource);
        handler.setAuthenticationQuery(
            "SELECT password FROM users WHERE username = ? AND status = 1");
        handler.setPasswordEncoder(new BCryptPasswordEncoder());
        return handler;
    }
}

5.5 CasEmbeddedContainerUtils

CAS 7.3 新增了 CasEmbeddedContainerUtils 工具类,用于统一管理嵌入式容器的配置:

java
// CAS 7.3 内部使用 CasEmbeddedContainerUtils 管理容器配置
// 在 application.yml 中通过标准 Spring Boot 属性控制

server:
  port: 8443
  ssl:
    enabled: true
    key-store: classpath:keystore.jks
    key-store-password: changeit
    key-password: changeit
    key-store-type: JKS
    protocol: TLS
    enabled-protocols: TLSv1.2,TLSv1.3
  tomcat:
    uri-encoding: UTF-8
    remoteip:
      enabled: true
      protocol-header: X-Forwarded-Proto
      port-header: X-Forwarded-Port
      remote-ip-header: X-FORWARDED-FOR
    connection-timeout: 20000
    max-http-form-post-size: 2097152
    min-spare-threads: 10
    max-threads: 200
    accesslog:
      enabled: true
      pattern: "%t %a \"%r\" %s (%D ms)"
      suffix: .log
      directory: /var/log/cas/tomcat

5.6 移除的模块和配置

CAS 7.3 移除了一些过时的模块和配置项,以下是我们在迁移中发现的主要移除:

移除项替代方案影响范围
cas-server-webapp-configcas-server-webapp-init初始化模块
cas-server-support-oauth-servicescas-server-support-oauth-umaOAuth 服务管理
pac4j-javaeepac4j-jeePac4j 集成
WAR 部署模式JAR 部署模式打包方式
spring-boot-starter-loggingspring-boot-starter-log4j2日志框架

六、MyBatis-Spring 3.x 升级

MyBatis-Spring 从 1.3.1 升级到 3.0.3,跨越了多个大版本,API 发生了显著变化。

6.1 版本变化概览

版本Spring 兼容性Java 兼容性主要变化
1.3.1Spring 3.x - 5.xJava 6+基础集成
2.0.xSpring 5.xJava 8+支持 Spring 5
2.1.xSpring 5.xJava 8+增强注解支持
3.0.0Spring 6.xJava 17+全面支持 Spring Boot 3
3.0.3Spring 6.xJava 17+Bug 修复

6.2 SqlSessionFactory 配置变化

xml
<!-- ===== MyBatis-Spring 1.3.1 配置方式 ===== -->
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
    <property name="dataSource" ref="dataSource"/>
    <property name="configLocation" value="classpath:mybatis-config.xml"/>
    <property name="mapperLocations" value="classpath:mapper/**/*.xml"/>
    <property name="typeAliasesPackage" value="cc.bima.cas.model"/>
</bean>

<!-- ===== MyBatis-Spring 3.0.3 配置方式 ===== -->
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
    <property name="dataSource" ref="dataSource"/>
    <property name="configLocation" value="classpath:mybatis/mybatis-config.xml"/>
    <property name="mapperLocations" value="classpath:mapper/**/*.xml"/>
    <property name="typeAliasesPackage" value="cc.bima.cas.model"/>
    <!-- 3.0.x 新增配置 -->
    <property name="configurationProperties">
        <props>
            <prop key="cacheEnabled">true</prop>
            <prop key="lazyLoadingEnabled">false</prop>
            <prop key="multipleResultSetsEnabled">true</prop>
            <prop key="useColumnLabel">true</prop>
            <prop key="useGeneratedKeys">false</prop>
            <prop key="defaultExecutorType">REUSE</prop>
        </props>
    </property>
</bean>

6.3 MapperScan 注解变化

java
// ===== MyBatis-Spring 1.3.1 方式 =====
@Configuration
@MapperScan(basePackages = "cc.bima.cas.dao")
public class MyBatisConfig {
    // ...
}

// ===== MyBatis-Spring 3.0.3 方式 =====
@Configuration
@MapperScan(
    basePackages = "cc.bima.cas.dao",
    sqlSessionFactoryRef = "sqlSessionFactory",
    lazyInitialization = true  // 3.0.x 新增:延迟初始化
)
public class MyBatisConfig {

    @Bean
    public SqlSessionFactory sqlSessionFactory(DataSource dataSource) throws Exception {
        var factory = new SqlSessionFactoryBean();
        factory.setDataSource(dataSource);
        factory.setConfigLocation(
            new ClassPathResource("mybatis/mybatis-config.xml"));
        factory.setMapperLocations(
            new PathMatchingResourcePatternResolver()
                .getResources("classpath:mapper/**/*.xml"));
        return factory.getObject();
    }

    @Bean
    public SqlSessionTemplate sqlSessionTemplate(SqlSessionFactory sqlSessionFactory) {
        return new SqlSessionTemplate(sqlSessionFactory);
    }
}

6.4 与 Spring Boot 3.x 的兼容性

MyBatis-Spring 3.0.3 完全兼容 Spring Boot 3.x 和 Jakarta EE 10。以下是推荐的集成配置:

groovy
// build.gradle - MyBatis 依赖配置
dependencies {
    // MyBatis 核心
    implementation "org.mybatis:mybatis:3.5.16"
    implementation "org.mybatis:mybatis-spring:3.0.3"

    // MyBatis Spring Boot Starter(如果使用)
    // implementation "org.mybatis.spring.boot:mybatis-spring-boot-starter:3.0.3"

    // 数据库驱动
    implementation "mysql:mysql-connector-java:8.0.33"

    // 连接池
    implementation "org.apache.commons:commons-dbcp2:2.10.0"
}

MyBatis-Spring 3.0.3 的关键改进:

  1. Jakarta EE 10 兼容:全面支持 jakarta.* 命名空间
  2. 延迟初始化:支持 Mapper Bean 的延迟初始化,加快启动速度
  3. 编译时注解处理:增强的注解处理器,提供更好的编译期检查
  4. Spring 6 事务集成:与 Spring 6 的事务管理深度集成

七、commons-dbcp 到 commons-dbcp2

从 Apache Commons DBCP 1.x 升级到 2.x,虽然包名从 commons-dbcp 变为 org.apache.commons:commons-dbcp2,但 API 也发生了重要变化。

7.1 API 变化

java
// ===== commons-dbcp 1.4(旧版)=====
import org.apache.commons.dbcp.BasicDataSource;

BasicDataSource ds = new BasicDataSource();
ds.setDriverClassName("com.mysql.jdbc.Driver");  // 旧驱动类名
ds.setMaxActive(100);      // 最大连接数
ds.setMaxIdle(30);         // 最大空闲连接
ds.setMinIdle(5);          // 最小空闲连接
ds.setMaxWait(10000);      // 最大等待时间(毫秒)

// ===== commons-dbcp2 2.10.0(新版)=====
import org.apache.commons.dbcp2.BasicDataSource;

BasicDataSource ds = new BasicDataSource();
ds.setDriverClassName("com.mysql.cj.jdbc.Driver");  // 新驱动类名
ds.setMaxTotal(100);        // 原 maxActive -> maxTotal
ds.setMaxIdle(30);          // 保持不变
ds.setMinIdle(5);           // 保持不变
ds.setMaxWaitMillis(10000); // 原 maxWait -> maxWaitMillis

7.2 属性名称对照

commons-dbcp 1.xcommons-dbcp2 2.x说明
maxActivemaxTotal最大连接数
maxWaitmaxWaitMillis最大等待时间
removeAbandonedremoveAbandonedOnBorrow废弃连接回收
removeAbandonedTimeoutremoveAbandonedOnBorrowTimeout废弃超时时间
validationQueryvalidationQuery保持不变
testOnBorrowtestOnBorrow保持不变
testWhileIdletestWhileIdle保持不变

7.3 XML 配置迁移

xml
<!-- ===== commons-dbcp 1.4 配置 ===== -->
<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource"
      destroy-method="close">
    <property name="driverClassName" value="com.mysql.jdbc.Driver"/>
    <property name="url" value="jdbc:mysql://localhost:3306/cas_db"/>
    <property name="username" value="cas_user"/>
    <property name="password" value="password"/>
    <property name="initialSize" value="5"/>
    <property name="maxActive" value="100"/>
    <property name="maxIdle" value="30"/>
    <property name="minIdle" value="5"/>
    <property name="maxWait" value="10000"/>
    <property name="validationQuery" value="SELECT 1"/>
    <property name="testOnBorrow" value="true"/>
    <property name="testWhileIdle" value="true"/>
    <property name="timeBetweenEvictionRunsMillis" value="30000"/>
</bean>

<!-- ===== commons-dbcp2 2.10.0 配置 ===== -->
<bean id="dataSource" class="org.apache.commons.dbcp2.BasicDataSource"
      destroy-method="close">
    <property name="driverClassName" value="com.mysql.cj.jdbc.Driver"/>
    <property name="url" value="jdbc:mysql://localhost:3306/cas_db?useUnicode=true&amp;characterEncoding=utf8&amp;useSSL=false&amp;serverTimezone=Asia/Shanghai"/>
    <property name="username" value="cas_user"/>
    <property name="password" value="password"/>
    <property name="initialSize" value="5"/>
    <property name="maxTotal" value="100"/>
    <property name="maxIdle" value="30"/>
    <property name="minIdle" value="5"/>
    <property name="maxWaitMillis" value="10000"/>
    <property name="validationQuery" value="SELECT 1"/>
    <property name="testOnBorrow" value="true"/>
    <property name="testWhileIdle" value="true"/>
    <property name="timeBetweenEvictionRunsMillis" value="30000"/>
    <!-- commons-dbcp2 新增特性 -->
    <property name="defaultAutoCommit" value="false"/>
    <property name="poolPreparedStatements" value="true"/>
    <property name="maxOpenPreparedStatements" value="50"/>
    <property name="logAbandoned" value="true"/>
    <property name="removeAbandonedOnBorrow" value="true"/>
    <property name="removeAbandonedOnBorrowTimeout" value="300"/>
</bean>

7.4 性能提升

commons-dbcp2 相比 1.x 版本在以下方面有显著提升:

  1. 连接创建性能:优化了连接创建的同步机制,减少锁竞争
  2. 空闲连接回收:改进的空闲连接检测算法,更精准地回收无效连接
  3. 异步关闭:支持连接的异步关闭,减少归还连接的延迟
  4. JMX 监控:增强的 JMX 支持,提供更详细的连接池运行时指标
  5. 内存管理:优化了内部数据结构的内存使用

八、前端技术栈升级

CAS 7.3 的前端技术栈进行了全面升级,引入了更现代的 UI 框架和组件库。

8.1 版本对照

前端组件CAS 5.3CAS 6.6CAS 7.3变化说明
Bootstrap3.x4.x5.3.3全面重写的 CSS 框架
Material Components Web--14.0.0新增 Google Material Design
MDI Font--7.4.47新增 Material Design Icons
jQuery2.x3.x3.7.1稳定版本
Font Awesome4.7.04.7.04.7.0保持不变
DataTables--2.3.2新增数据表格组件
Normalize.css--8.0.1新增 CSS 重置

8.2 Gradle 依赖配置

groovy
dependencies {
    // WebJars 依赖 - 通过包管理器引入前端资源
    implementation 'org.webjars:jquery:3.7.1'
    implementation 'org.webjars:bootstrap:5.3.3'
    implementation 'org.webjars:font-awesome:4.7.0'
    implementation 'org.webjars:datatables:2.3.2'
    implementation 'org.webjars.npm:material-components-web:14.0.0'
    implementation 'org.webjars.npm:mdi__font:7.4.47'
    implementation 'org.webjars.npm:normalize.css:8.0.1'
}

8.3 Bootstrap 5 迁移要点

Bootstrap 5 相比 Bootstrap 4/3 有以下重大变化:

html
<!-- ===== Bootstrap 3 时代的典型代码 ===== -->
<div class="panel panel-default">
    <div class="panel-heading">标题</div>
    <div class="panel-body">内容</div>
</div>
<div class="form-group">
    <label class="control-label">用户名</label>
    <input type="text" class="form-control"/>
</div>
<button class="btn btn-primary">登录</button>

<!-- ===== Bootstrap 5 的对应写法 ===== -->
<div class="card">
    <div class="card-header">标题</div>
    <div class="card-body">内容</div>
</div>
<div class="mb-3">
    <label class="form-label">用户名</label>
    <input type="text" class="form-control"/>
</div>
<button class="btn btn-primary">登录</button>

Bootstrap 5 关键变化:

变化项Bootstrap 3/4Bootstrap 5
CSS 框架基础LessSass
JavaScriptjQuery 依赖原生 JS(无 jQuery 依赖)
面板组件.panel.card
表单组.form-group.mb-3
数据属性data-toggledata-bs-toggle
Grid 系统4 列网格5 列网格(新增 xxl
颜色方案主题色更丰富的调色板
RTL 支持原生 RTL 支持

8.4 Material Components Web 集成

CAS 7.3 新增了 Google Material Components Web (MDC-Web) 作为可选的 UI 组件库:

html
<!-- Material Components Web 使用示例 -->
<link rel="stylesheet" href="/webjars/material-components-web/14.0.0/dist/material-components-web.min.css">
<link rel="stylesheet" href="/webjars/mdi__font/7.4.47/css/materialdesignicons.min.css">

<!-- Material Design 输入框 -->
<div class="mdc-text-field mdc-text-field--outlined">
    <input type="text" id="username" class="mdc-text-field__input"
           autocomplete="username" required>
    <div class="mdc-notched-outline">
        <div class="mdc-notched-outline__leading"></div>
        <div class="mdc-notched-outline__notch">
            <label for="username" class="mdc-floating-label">用户名</label>
        </div>
        <div class="mdc-notched-outline__trailing"></div>
    </div>
</div>

<!-- Material Design 按钮 -->
<button class="mdc-button mdc-button--raised">
    <span class="mdc-button__ripple"></span>
    <span class="mdc-button__label">登录</span>
</button>

8.5 Thymeleaf 模板配置

CAS 7.3 使用 Thymeleaf 作为模板引擎,配置如下:

yaml
spring:
  thymeleaf:
    encoding: UTF-8
    cache: true          # 生产环境开启缓存
    mode: HTML           # HTML 模板模式
    template-resolver-order: 100

九、构建工具链升级

9.1 Gradle 9.1.0 新特性

Gradle 9.1.0 带来了多项重要的构建性能和功能改进。

构建性能优化

properties
# gradle.properties - 推荐的构建优化配置
org.gradle.configureondemand=true    # 按需配置
org.gradle.caching=true               # 构建缓存
org.gradle.parallel=true              # 并行构建
org.gradle.jvmargs=-Xms1024m -Xmx4048m -XX:TieredStopAtLevel=1
org.gradle.unsafe.configuration-cache=false  # 配置缓存(Jib 不兼容)

Gradle 9.x 关键新特性

特性说明对 CAS 构建的影响
Configuration Cache缓存配置阶段结果构建速度提升 20-50%
Isolated Projects项目隔离减少不必要的重新构建
Toolchain API 增强更灵活的 JDK 管理支持自动下载 JDK
Kotlin DSL 稳定Kotlin DSL 一等公民构建脚本更安全
Develocity 集成构建分析平台构建性能可视化

Jib 3.5.3 容器镜像构建

CAS 7.3 使用 Jib 插件构建 Docker 镜像,无需 Docker 守护进程:

groovy
jib {
    from {
        image = project.baseDockerImage  // azul/zulu-openjdk:21
        platforms {
            imagePlatforms.each {
                def given = it.split(":")
                platform {
                    architecture = given[0]
                    os = given[1]
                }
            }
        }
    }
    to {
        image = "${project.'containerImageOrg'}/${project.'containerImageName'}:${project.version}"
        credHelper = "osxkeychain"
        tags = [project.version]
    }
    container {
        creationTime = "USE_CURRENT_TIMESTAMP"
        entrypoint = ['/docker/entrypoint.sh']
        ports = ['80', '443', '8080', '8443', '8444', '8761', '8888', '5000']
        labels = [
            version: project.version,
            name: project.name,
            group: project.group,
            org: project.containerImageOrg
        ]
        workingDirectory = '/docker/cas/war'
    }
    extraDirectories {
        paths {
            path { from = file('src/main/jib') }
            path { from = file('etc/cas'); into = '/etc/cas' }
            path { from = file("build/libs"); into = "/docker/cas/war" }
        }
        permissions = [
            '/docker/entrypoint.sh': '755'
        ]
    }
}

CycloneDX 3.2.0 SBOM 生成

CAS 7.3 集成了 CycloneDX 插件,自动生成软件物料清单(SBOM):

groovy
apply plugin: "org.cyclonedx.bom"

// CycloneDX 任务与构建流程集成
tasks.named("cyclonedxBom").configure { wireOverlayDependency(delegate) }
tasks.named("cyclonedxDirectBom").configure { wireOverlayDependency(delegate) }

SBOM 是现代软件供应链安全的重要组成部分,它记录了应用的所有依赖组件及其版本,用于:

  • 安全漏洞扫描:与 Snyk、OWASP 等工具集成,自动检测已知漏洞
  • 许可证合规:检查依赖的开源许可证是否符合企业政策
  • 供应链审计:满足等保、SOC2 等合规要求

9.2 Java Toolchain 实战

CAS 7.3 的 Java Toolchain 配置实现了构建环境与运行环境的解耦:

properties
# gradle.properties
sourceCompatibility=21
targetCompatibility=21
jvmVendor=AMAZON  # 支持: AMAZON, ADOPTIUM, JETBRAINS, MICROSOFT, ORACLE, SAP, BELLSOFT
groovy
// build.gradle - Toolchain 配置
java {
    toolchain {
        languageVersion = JavaLanguageVersion.of(21)
        // Gradle 会自动下载匹配的 JDK
    }
}

Toolchain 的优势:

  1. 构建一致性:团队成员无需手动安装指定版本的 JDK
  2. CI/CD 友好:构建服务器无需预装 JDK,Gradle 自动管理
  3. 多版本并行:同一台机器上可以同时构建不同 Java 版本的项目
  4. 供应商灵活:可以在不同 JVM 供应商之间自由切换

十、从传统部署到云原生

CAS 7.3 在云原生部署方面进行了全面增强,从容器化到 Kubernetes 编排,从可观测性到 Service Mesh 集成。

10.1 多阶段 Docker 构建

CAS 7.3 的 Dockerfile 采用了多阶段构建策略,显著减小最终镜像大小:

dockerfile
# 阶段1:构建
FROM gradle:9.1.0-jdk21 AS builder
WORKDIR /app

# 优化 Gradle 构建配置
RUN mkdir -p ~/.gradle \
    && echo "org.gradle.daemon=false" >> ~/.gradle/gradle.properties \
    && echo "org.gradle.configureondemand=true" >> ~/.gradle/gradle.properties

# 复制项目文件
COPY . .

# 构建:强制生成 Spring Boot 可执行 JAR
RUN chmod 750 ./gradlew \
    && ./gradlew clean bootJar -x test --parallel --no-daemon

# 提取构建产物
RUN mkdir -p /app/cas \
    && first_jar=$(ls /app/build/libs/*.jar | head -n 1) \
    && cp "$first_jar" /app/cas/cas.jar

# 阶段2:纯运行(最小化镜像)
FROM azul/zulu-openjdk:21 AS runner
WORKDIR /docker/cas/jar

# 创建 CAS 所需目录
RUN mkdir -p /etc/cas/config \
    && mkdir -p /etc/cas/services \
    && mkdir -p /etc/cas/saml \
    && chmod -R 777 /etc/cas

# 复制配置文件
COPY src/main/resources/keystore.jks /etc/cas/thekeystore
COPY src/main/resources/services /etc/cas/services

# 复制构建产物
COPY --from=builder /app/cas/cas.jar /docker/cas/jar/

# 复制启动脚本
COPY src/main/jib/docker/entrypoint.sh /docker/entrypoint.sh
RUN chmod +x /docker/entrypoint.sh

ENV PATH $PATH:$JAVA_HOME/bin:/docker/cas/jar

EXPOSE 80 443 8080 8443 8444 8761 8888 5000

ENTRYPOINT ["/docker/entrypoint.sh"]

多阶段构建的优势:

  • 镜像体积:最终镜像仅包含 JRE 和应用 JAR,约 200-300MB(vs 单阶段约 1GB+)
  • 安全性:构建阶段的所有工具链、源代码都不会出现在最终镜像中
  • 构建速度:利用 Docker 层缓存,代码变更时只需重新构建最后几层

10.2 Kubernetes 部署

CAS 7.3 的 Kubernetes 部署配置示例:

yaml
# cas-deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: cas-server
  labels:
    app: cas-server
    version: v7.3.4
spec:
  replicas: 3
  selector:
    matchLabels:
      app: cas-server
  template:
    metadata:
      labels:
        app: cas-server
        version: v7.3.4
    spec:
      containers:
        - name: cas
          image: apereo/cas:7.3.4
          ports:
            - containerPort: 8443
              protocol: TCP
          env:
            - name: JAVA_OPTS
              value: |
                -XX:+UseZGC
                -XX:+ZGenerational
                -Xms2g
                -Xmx4g
                -XX:MaxGCPauseMillis=10
            - name: SPRING_PROFILES_ACTIVE
              value: k8s
          resources:
            requests:
              memory: "2Gi"
              cpu: "1000m"
            limits:
              memory: "4Gi"
              cpu: "2000m"
          livenessProbe:
            httpGet:
              path: /status/health
              port: 8443
              scheme: HTTPS
            initialDelaySeconds: 60
            periodSeconds: 30
          readinessProbe:
            httpGet:
              path: /status/health
              port: 8443
              scheme: HTTPS
            initialDelaySeconds: 30
            periodSeconds: 10
          volumeMounts:
            - name: cas-config
              mountPath: /etc/cas/config
            - name: cas-services
              mountPath: /etc/cas/services
      volumes:
        - name: cas-config
          configMap:
            name: cas-config
        - name: cas-services
          configMap:
            name: cas-services
---
apiVersion: v1
kind: Service
metadata:
  name: cas-server
spec:
  type: ClusterIP
  ports:
    - port: 443
      targetPort: 8443
      protocol: TCP
  selector:
    app: cas-server

10.3 Service Mesh 集成

CAS 7.3 可以与 Istio 等 Service Mesh 集成,实现流量管理、安全通信和可观测性:

yaml
# Istio VirtualService - CAS 流量管理
apiVersion: networking.istio.io/v1beta1
kind: VirtualService
metadata:
  name: cas-server
spec:
  hosts:
    - cas.example.com
  gateways:
    - cas-gateway
  http:
    - match:
        - uri:
            prefix: /cas/login
      route:
        - destination:
            host: cas-server
            port:
              number: 443
    - match:
        - uri:
            prefix: /cas/status
      route:
        - destination:
            host: cas-server
            port:
              number: 443
      retries:
        attempts: 3
        perTryTimeout: 5s
    - fault:
        delay:
          percentage:
            value: 1
          fixedDelay: 2s
      route:
        - destination:
            host: cas-server
            subset: v734

10.4 可观测性:OpenTelemetry

CAS 7.3 原生支持 OpenTelemetry,提供统一的分布式追踪、指标和日志采集:

yaml
# application.yml - OpenTelemetry 配置
management:
  tracing:
    sampling:
      probability: 1.0
    propagation:
      type: w3c
  otlp:
    tracing:
      endpoint: http://otel-collector:4318/v1/traces
  metrics:
    export:
      otlp:
        endpoint: http://otel-collector:4318/v1/metrics
java
// 自定义 OpenTelemetry 指标采集
@Configuration
public class CasMetricsConfiguration {

    @Bean
    public MeterRegistryCustomizer<MeterRegistry> casMetricsCommonTags() {
        return registry -> registry.config().commonTags(
            "application", "cas-server",
            "version", "7.3.4"
        );
    }

    @Bean
    public TimedAspect timedAspect(MeterRegistry registry) {
        return new TimedAspect(registry);
    }
}

// 在认证处理器中使用 @Timed 注解
@Component
public class CustomAuthHandler {

    @Timed(value = "cas.authentication.duration",
           description = "认证处理耗时",
           percentiles = {0.5, 0.95, 0.99})
    public AuthResult authenticate(AuthenticationRequest request) {
        // 认证逻辑
        return AuthResult.success(request.username());
    }
}

OpenTelemetry 三大支柱:

支柱功能CAS 中的应用场景
Traces(追踪)请求链路追踪追踪认证请求从登录到出票的全链路
Metrics(指标)运行时指标TGT/ST 创建速率、认证成功率、响应时间
Logs(日志)结构化日志与追踪关联的上下文日志

10.5 GraalVM Native Image 准备

虽然 CAS 7.3 尚未完全支持 GraalVM Native Image,但已经为此做好了架构层面的准备:

groovy
// build.gradle - GraalVM 相关配置(预留)
// CAS 7.3 的 build.gradle 中已包含 compileGraalJava 任务引用
def wireOverlayDependency = { Task t ->
    ["processTestResources", "compileJava", "compileGraalJava"]
        .collect { tasks.findByName(it) }
        .findAll { it != null }
        .each { t.dependsOn(it) }
}

GraalVM Native Image 的潜在优势:

指标传统 JVM 启动Native Image
启动时间10-30 秒50-200 毫秒
内存占用500MB-2GB50-200MB
峰值性能高(JIT 优化后)中(AOT 编译)
容器密度

注意: CAS 框架大量使用反射、动态代理和运行时类加载,这些特性与 GraalVM Native Image 的 AOT 编译模型存在冲突。完整的 Native Image 支持需要 CAS 上游提供反射配置元数据。


十一、版本对比与迁移检查清单

11.1 依赖版本对比总表

以下是我们实际 CAS Overlay 项目从 6.6 升级到 7.3 的完整依赖变更清单:

依赖项CAS 6.6 版本CAS 7.3 版本变更类型
cas-server-support-bom6.6.x7.3.4大版本升级
spring-boot-gradle-plugin2.7.x3.5.6大版本升级
mybatis3.5.63.5.16小版本升级
mybatis-spring1.3.13.0.3大版本升级
mysql-connector-java8.0.338.0.33不变
commons-dbcp1.4-移除
commons-dbcp2-2.10.0新增
pac4j-javaee存在-移除
lombok未指定1.18.42指定版本
jquery未指定3.7.1指定版本
bootstrap未指定5.3.3大版本升级
material-components-web-14.0.0新增
mdi__font-7.4.47新增
datatables-2.3.2新增
normalize.css-8.0.1新增
junit存在-移除(JUnit 5)
jib-gradle-plugin-3.5.3新增
cyclonedx-gradle-plugin-3.2.0新增

11.2 迁移检查清单

以下是从 CAS 6.x 迁移到 7.3 的完整检查清单:

阶段一:环境准备

  • [ ] 安装 JDK 21(推荐 Amazon Corretto 21 或 Azul Zulu 21)
  • [ ] 升级 Gradle 到 9.1.0(或使用 Gradle Wrapper)
  • [ ] 升级 IDE 到最新版本(IntelliJ IDEA 2024.1+ / Eclipse 2024-03+)
  • [ ] 配置 JAVA_HOME 指向 JDK 21
  • [ ] 更新 CI/CD 管道的 JDK 版本

阶段二:构建配置迁移

  • [ ] 更新 gradle.properties 中的版本号
  • [ ] 替换 build.gradle 中的依赖声明
  • [ ] 移除 io.spring.gradle:dependency-management-plugin
  • [ ] 添加 enforcedPlatform 依赖管理
  • [ ] 配置 Java Toolchain
  • [ ] 添加 CycloneDX SBOM 插件
  • [ ] 配置 Jib 容器镜像构建

阶段三:代码迁移

  • [ ] 批量替换 javax.*jakarta.*
  • [ ] 更新 Spring Security 配置(移除 WebSecurityConfigurerAdapter
  • [ ] 更新 MyBatis 配置(SqlSessionFactory、MapperScan)
  • [ ] 替换 commons-dbcpcommons-dbcp2
  • [ ] 更新数据库驱动类名(com.mysql.jdbc.Driver -> com.mysql.cj.jdbc.Driver
  • [ ] 移除 pac4j-javaee 依赖
  • [ ] 更新自定义认证处理器

阶段四:配置迁移

  • [ ] 迁移 spring.http.encodingserver.servlet.encoding
  • [ ] 迁移 spring.mvc.localespring.web.locale
  • [ ] 更新 Actuator 端点配置
  • [ ] 禁用默认的 AcceptUsers 认证处理器
  • [ ] 更新 Thymeleaf 模板中的前端资源引用

阶段五:测试与验证

  • [ ] 运行单元测试
  • [ ] 运行集成测试
  • [ ] 验证 CAS 登录流程
  • [ ] 验证 OAuth2.0 授权流程
  • [ ] 验证 REST API
  • [ ] 验证 SSO 单点登录/登出
  • [ ] 性能测试(使用 Generational ZGC)
  • [ ] 安全扫描(使用 CycloneDX SBOM)

阶段六:部署

  • [ ] 构建容器镜像
  • [ ] 更新 Kubernetes 部署配置
  • [ ] 配置 OpenTelemetry 可观测性
  • [ ] 灰度发布
  • [ ] 全量发布
  • [ ] 监控告警配置

十二、总结与展望

12.1 升级价值总结

CAS 7.3 + Spring Boot 3.x + Java 21 的组合,为企业级 SSO 系统带来了全方位的提升:

维度提升内容
安全性Jakarta EE 10 安全标准、TLS 1.3、现代加密算法
性能Generational ZGC(GC 停顿降低 90%+)、虚拟线程(百万级并发)
开发效率Record、Pattern Matching、Sealed Classes 减少样板代码
可维护性条件装配、热刷新、组件化配置
云原生容器化、Kubernetes、Service Mesh、OpenTelemetry
供应链安全CycloneDX SBOM、依赖版本强制管理

12.2 未来展望

基于当前的技术趋势和 CAS 的发展路线,我们预见以下方向:

  1. Java 25 LTS:预计 2025 年 9 月发布的 Java 25 将带来更多语言特性和性能改进,CAS 将在后续版本中跟进
  2. GraalVM Native Image:随着 AOT 编译技术的成熟,CAS 的启动时间有望从秒级降到毫秒级
  3. WebFlux 全面支持:CAS 可能会在未来版本中提供完整的响应式编程支持
  4. AI 辅助认证:结合机器学习的风险感知认证(Risk-Based Authentication)将成为标配
  5. Passkey/FIDO2:无密码认证标准将在 CAS 中获得更深入的支持

12.3 给团队的行动建议

  1. 立即行动:如果当前 CAS 版本低于 6.6,建议先升级到 6.6 作为过渡
  2. 渐进迁移:不要试图一步到位,分阶段完成迁移
  3. 自动化优先:使用 OpenRewrite 等工具自动化处理命名空间迁移
  4. 充分测试:CAS 是核心基础设施,务必进行全面的回归测试
  5. 关注社区:Apereo CAS 社区非常活跃,及时关注版本更新和安全公告

版权声明: 本文为必码(bima.cc)原创技术文章,仅供学习交流。

本文内容基于实际项目源码解析整理,代码示例均为教学简化版本,仅供学习参考。

文档内容提取自项目源码与配置文件,如需获取完整项目代码,请访问 bima.cc