Appearance
CAS Gradle 构建体系深度解析:从 Overlay 项目初始化到自定义任务扩展
作者: 必码 | bima.cc
前言
Apereo CAS(Central Authentication Service)作为企业级单点登录(SSO)解决方案的事实标准,在全球范围内被广泛应用于高等教育、政府机构和大型企业。CAS 的 Overlay 构建模式是其最核心的架构设计理念之一——它允许开发者在不修改 CAS 源码的前提下,通过"覆盖层"的方式定制化 CAS 的行为、外观和功能集成。而 Gradle 作为 CAS Overlay 项目的官方推荐构建工具,承担着依赖管理、编译打包、容器化构建、自定义任务编排等关键职责。
本文将基于 CAS Overlay 5.3.x、6.6.x、7.3.x 三个里程碑版本的 Gradle 构建文件,进行一次全方位的深度技术解析。我们将从项目结构、Gradle 版本演进、BOM 依赖管理策略、Spring Boot 集成、自定义任务体系、容器化插件配置,一直到构建优化技巧,逐一拆解每个构建环节的设计意图与最佳实践。
无论你是正在评估 CAS 技术选型的架构师,还是已经深度使用 CAS Overlay 的开发工程师,抑或是对 Gradle 高级用法感兴趣的技术爱好者,本文都将为你提供有价值的参考。
第一章 CAS Overlay Gradle 项目结构
1.1 Overlay 模式的设计哲学
在深入目录结构之前,我们需要先理解 CAS Overlay 模式的核心设计哲学。传统的 Java Web 项目通常采用"修改源码 + 重新编译"的方式进行定制,这种方式在 CAS 这样的大型框架中存在明显的弊端:升级困难、合并冲突频繁、定制代码与框架代码高度耦合。
CAS Overlay 模式则采用了完全不同的思路:
- 分层覆盖(Layered Overlay): CAS 的核心功能被打包为可执行 JAR/WAR,Overlay 项目仅包含需要定制的部分(配置文件、模板、自定义 Java 类)。构建时,Overlay 层的内容会覆盖 CAS 核心层的同名资源。
- 约定优于配置(Convention over Configuration): Overlay 项目遵循标准化的目录布局,Gradle 构建脚本通过约定来发现和处理这些资源。
- 版本驱动升级(Version-Driven Upgrade): 升级 CAS 版本通常只需修改一个版本号属性,无需改动构建逻辑。
这种设计使得 CAS Overlay 项目本质上是一个"薄层"——它只包含差异化的内容,而将通用功能完全委托给 CAS 核心。
1.2 标准目录布局
CAS Overlay Gradle 项目遵循标准的 Gradle 项目布局约定,同时针对 CAS 的特殊需求进行了扩展。以下是一个典型的 CAS 6.6.x / 7.3.x Overlay 项目的完整目录结构:
cas-overlay/
├── build.gradle # 主构建脚本
├── settings.gradle # 项目设置
├── gradle.properties # 版本与属性管理
├── gradlew # Gradle Wrapper 启动脚本(Unix)
├── gradlew.bat # Gradle Wrapper 启动脚本(Windows)
├── Dockerfile # Docker 构建文件
├── LICENSE.txt # 许可证文件
├── README.md # 项目说明
├── src/
│ ├── main/
│ │ ├── java/ # Java 源码目录
│ │ │ └── cc/bima/cas/
│ │ │ └── CasWebApplication.java
│ │ └── resources/ # 资源文件目录
│ │ ├── application.yml # CAS 主配置文件
│ │ ├── bootstrap.properties # 引导配置
│ │ ├── log4j2.xml # 日志配置
│ │ ├── messages.properties # 国际化消息
│ │ ├── messages_zh_CN.properties
│ │ ├── messages_zh_TW.properties
│ │ └── spring-common.xml # Spring 扩展配置
│ └── test/
│ ├── java/ # 测试源码目录
│ └── resources/ # 测试资源目录
├── gradle/
│ ├── wrapper/
│ │ ├── gradle-wrapper.jar # Wrapper JAR 文件
│ │ └── gradle-wrapper.properties # Wrapper 配置
│ ├── springboot.gradle # Spring Boot 构建配置
│ └── tasks.gradle # 自定义任务集
├── etc/
│ └── cas/
│ └── config/ # 外部配置目录
├── bin/
│ └── main/ # 构建输出目录(开发用)
└── build/ # Gradle 构建输出目录1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
1.2.1 src/main/java 源码目录
这是 Overlay 项目的 Java 源码根目录。在 CAS Overlay 中,此目录通常只包含少量自定义类,例如:
- 自定义认证处理器(Authentication Handler): 实现特定的认证逻辑(如数据库认证、LDAP 认证、自定义协议认证等)。
- 自定义动作(Action): Webflow 中的自定义动作类。
- 自定义策略(Strategy): 如密码策略、服务注册策略等。
- 自定义序列化器/反序列化器: 用于 Ticket 序列化等场景。
在 5.3.x 版本中,源码目录通过 sourceSets 显式声明:
groovy
// CAS 5.3.x - build.gradle
sourceSets {
main {
java {
srcDirs = ['src/main/java']
}
resources {
srcDirs = ['src/main/resources']
}
}
}1
2
3
4
5
6
7
8
9
10
11
2
3
4
5
6
7
8
9
10
11
在 6.6.x 和 7.3.x 中,虽然也保留了显式声明(主要用于 Eclipse 兼容性),但 Gradle 默认的约定已经足够处理标准布局:
groovy
// CAS 6.6.x / 7.3.x - build.gradle
sourceSets {
main {
java {
srcDirs = ['src/main/java'] // 主源码目录
}
resources {
srcDirs = ['src/main/resources'] // 资源文件目录
}
}
test {
java {
srcDirs = ['src/test/java'] // 测试源码目录
}
resources {
srcDirs = ['src/test/resources'] // 测试资源目录
}
}
}1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
工程实践提示: 显式声明 sourceSets 虽然看起来冗余,但在以下场景中非常有价值:
- 多模块项目中的非标准目录布局
- IDE(如 Eclipse)的自动导入和代码索引
- 遗留项目从 Maven 迁移时的兼容性处理
1.2.2 src/main/resources 资源目录
资源目录是 CAS Overlay 定制化的核心阵地。CAS 的 Spring Boot 自动配置机制会从以下位置加载配置(按优先级从高到低):
- 命令行参数
- SPRING_APPLICATION_JSON 环境变量中的 JSON 属性
- ServletConfig 初始化参数
- ServletContext 初始化参数
- JNDI 属性
- Java 系统属性(System.getProperties())
- 操作系统环境变量
src/main/resources/application.yml(或 .properties)src/main/resources/application-{profile}.yml(特定 Profile)- CAS JAR 包内的默认配置
在 Overlay 项目中,src/main/resources 目录下常见的文件包括:
| 文件/目录 | 用途 | 版本差异 |
|---|---|---|
application.yml | CAS 主配置文件 | 5.3/6.6/7.3 通用 |
bootstrap.properties | 引导配置(CAS 初始化前加载) | 5.3/6.6/7.3 通用 |
log4j2.xml | Log4j2 日志配置 | 5.3/6.6/7.3 通用 |
messages.properties | 默认国际化消息 | 5.3/6.6/7.3 通用 |
messages_zh_CN.properties | 简体中文消息 | 5.3/6.6/7.3 通用 |
spring-common.xml | Spring XML 扩展配置 | 5.3/6.6/7.3 通用 |
static/themes/ | 自定义主题资源 | 6.6/7.3 推荐 |
templates/ | Thymeleaf 模板覆盖 | 6.6/7.3 推荐 |
重要提示: CAS 官方强烈建议不要在 Overlay 项目中创建 application.properties 文件。6.6.x 和 7.3.x 的 tasks.gradle 中包含 validateConfiguration 任务,如果检测到该文件存在会直接抛出异常:
groovy
// tasks.gradle - validateConfiguration 任务
task validateConfiguration(type: Copy, group: "CAS",
description: "Validate CAS configuration") {
def file = new File("${projectDir}/src/main/resources/application.properties")
if (file.exists()) {
throw new GradleException("This overlay project is overriding a CAS-supplied "
+ "configuration file at ${file.path}. Overriding this file will disable "
+ "all default CAS settings... It's best to move your configuration inside "
+ "an application.yml file...")
}
}
processResources.dependsOn(validateConfiguration)1
2
3
4
5
6
7
8
9
10
11
12
2
3
4
5
6
7
8
9
10
11
12
这是因为 application.properties 会完全覆盖 CAS 内置的默认配置,导致大量 CAS 功能失效。正确的做法是使用 application.yml,YAML 格式支持合并语义,可以只覆盖需要修改的属性。
1.3 build.gradle 核心结构
build.gradle 是 CAS Overlay 项目的核心构建脚本。随着 CAS 版本的演进,其复杂度和组织方式发生了显著变化。让我们从宏观角度理解三个版本 build.gradle 的结构差异。
1.3.1 CAS 5.3.x 的 build.gradle 结构
CAS 5.3.x 的构建脚本是最为"传统"的 Groovy DSL 风格,其结构可以概括为以下几个核心部分:
groovy
// ===== 第一部分:buildscript 依赖声明 =====
buildscript {
repositories {
mavenCentral()
maven { url 'https://build.shibboleth.net/nexus/content/repositories/releases' }
}
dependencies {
classpath "org.springframework.boot:spring-boot-gradle-plugin:${springBootVersion}"
}
}
// ===== 第二部分:插件应用 =====
apply plugin: 'eclipse'
apply plugin: 'java'
apply plugin: 'org.springframework.boot'
apply plugin: 'io.spring.dependency-management'
// ===== 第三部分:项目元信息 =====
group = 'cc.bima.cas'
version = '1.0-SNAPSHOT'
archivesBaseName = 'cas-overlay'
// ===== 第四部分:Java 编译配置 =====
java {
sourceCompatibility = JavaVersion.VERSION_1_8
targetCompatibility = JavaVersion.VERSION_1_8
}
// ===== 第五部分:依赖管理 =====
dependencyManagement {
imports {
mavenBom "org.apereo.cas:cas-server-support-bom:${project['cas.version']}"
mavenBom 'org.apache.logging.log4j:log4j-bom:2.12.4'
}
dependencies {
dependency 'org.apache.logging.log4j:log4j-core:2.12.4'
dependency 'commons-beanutils:commons-beanutils:1.9.4'
// ... 更多版本覆盖
}
}
// ===== 第六部分:项目依赖 =====
dependencies {
implementation('org.apereo.cas:cas-server-core') {
exclude group: 'org.springframework.boot', module: 'spring-boot-devtools'
// ... 更多排除项
}
// ... 大量 CAS 模块依赖
}
// ===== 第七部分:任务配置 =====
task cleanAllCache(type: Delete) { ... }
tasks.named('bootJar') { ... }
tasks.named('bootRun') { ... }1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
5.3.x 版本的特征是:依赖声明中包含大量的 exclude 块,每个 CAS 模块都需要重复声明相同的排除规则。这是因为 5.3.x 时代还没有成熟的 BOM 平台化导入机制,版本冲突需要手动处理。
1.3.2 CAS 6.6.x 的 build.gradle 结构
6.6.x 版本在结构上进行了显著优化,引入了外部脚本分离和平台化 BOM 导入:
groovy
// ===== 第一部分:buildscript(扩展) =====
buildscript {
repositories {
if (project.privateRepoUrl) { /* 私有仓库 */ }
mavenLocal()
mavenCentral()
gradlePluginPortal()
// ... 更多仓库
}
dependencies {
classpath "org.springframework.boot:spring-boot-gradle-plugin:${project.springBootVersion}"
classpath "io.freefair.gradle:maven-plugin:${project.gradleFreeFairPluginVersion}"
classpath "io.freefair.gradle:lombok-plugin:${project.gradleFreeFairPluginVersion}"
classpath "io.spring.gradle:dependency-management-plugin:${project.gradleDependencyManagementPluginVersion}"
classpath "de.undercouch:gradle-download-task:${project.gradleDownloadTaskVersion}"
classpath "org.apereo.cas:cas-server-core-api-configuration-model:${project.'cas.version'}"
classpath "org.apereo.cas:cas-server-core-configuration-metadata-repository:${project.'cas.version'}"
}
}
// ===== 第二部分:插件应用 =====
apply plugin: 'eclipse'
apply plugin: "java"
apply plugin: "org.springframework.boot"
apply plugin: "io.freefair.lombok"
// ===== 第三部分:外部脚本引入 =====
apply from: rootProject.file("gradle/springboot.gradle")
apply from: rootProject.file("gradle/tasks.gradle")
// ===== 第四部分:全局排除配置 =====
configurations {
all {
resolutionStrategy { ... }
exclude(group: "cglib", module: "cglib")
exclude(group: "org.slf4j", module: "slf4j-log4j12")
// ... 统一排除
}
}
// ===== 第五部分:Java Toolchain =====
java {
toolchain {
languageVersion = JavaLanguageVersion.of(11)
}
}
// ===== 第六部分:平台化 BOM 导入 =====
dependencies {
implementation platform(org.springframework.boot.gradle.plugin.SpringBootPlugin.BOM_COORDINATES)
implementation platform("org.apereo.cas:cas-server-support-bom:${project.'cas.version'}")
// ... 无需版本的 CAS 依赖
}1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
6.6.x 的关键改进包括:
- 外部脚本分离:
springboot.gradle和tasks.gradle独立管理,降低build.gradle复杂度 - 平台化 BOM 导入: 使用
platform()替代mavenBom,依赖无需显式声明版本 - 全局排除配置: 通过
configurations.all统一排除冲突依赖 - Java Toolchain: 声明式指定 JDK 版本,无需依赖本地环境
1.3.3 CAS 7.3.x 的 build.gradle 结构
7.3.x 版本在 6.6.x 的基础上进一步演进,引入了容器化构建、SBOM 生成等现代 DevSecOps 实践:
groovy
// ===== 第一部分:扩展导入 =====
import org.apache.tools.ant.taskdefs.condition.*
import org.gradle.internal.logging.text.*
import org.apereo.cas.metadata.*
import java.nio.file.*
import java.lang.reflect.*
import static org.gradle.internal.logging.text.StyledTextOutput.Style
// ===== 第二部分:buildscript(进一步扩展) =====
buildscript {
dependencies {
classpath "com.google.cloud.tools:jib-gradle-plugin:${project.jibVersion}"
classpath "com.bmuschko:gradle-docker-plugin:${project.gradleDockerPluginVersion}"
classpath "org.cyclonedx:cyclonedx-gradle-plugin:${project.gradleCyclonePluginVersion}"
// ... 保留 6.6.x 的 classpath
}
}
// ===== 第三部分:插件应用(新增容器化和 SBOM) =====
apply plugin: "org.cyclonedx.bom"
apply plugin: "com.google.cloud.tools.jib"
apply plugin: "com.bmuschko.docker-remote-api"
// ===== 第四部分:JVM Vendor 选择 =====
java {
toolchain {
languageVersion = JavaLanguageVersion.of(project.targetCompatibility)
// 动态选择 JVM Vendor
if (project.jvmVendor != null) {
vendor = JvmVendorSpec."${project.jvmVendor?.toUpperCase()}"
}
}
}
// ===== 第五部分:enforcedPlatform BOM =====
dependencies {
implementation enforcedPlatform("org.apereo.cas:cas-server-support-bom:${project.'cas.version'}")
implementation platform(org.springframework.boot.gradle.plugin.SpringBootPlugin.BOM_COORDINATES)
// ...
}
// ===== 第六部分:Jib 容器化配置 =====
jib {
from { image = project.baseDockerImage; platforms { ... } }
to { image = "..."; auth { ... } }
container { entrypoint = [...]; ports = [...] }
extraDirectories { paths { ... } }
}
// ===== 第七部分:Docker 插件配置 =====
tasks.register("casBuildDockerImage", DockerBuildImage) { ... }
tasks.register("casPushDockerImage", DockerPushImage) { ... }1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
1.4 gradle.properties 版本管理
gradle.properties 是 CAS Overlay 项目中最重要的配置文件之一,它集中管理了所有版本号和构建参数。CAS 官方的设计理念是:升级 CAS 版本时,通常只需要修改 cas.version 这一个属性。
1.4.1 CAS 5.3.x 的 gradle.properties
properties
# 项目版本
version=5.3.16
# CAS 服务器版本(核心属性,升级时修改此项)
cas.version=5.3.16
# Spring Boot 版本
springBootVersion=2.7.18
# 项目坐标
group=org.apereo.cas
artifactId=cas-overlay
# Java 兼容性配置
sourceCompatibility=1.8
targetCompatibility=1.8
# 插件版本
gradleFreeFairPluginVersion=8.6
gradleDependencyManagementPluginVersion=1.1.6
# 容器化配置
jibVersion=3.4.0
gradleDockerPluginVersion=9.3.1
containerImageOrg=apereo
containerImageName=cas
baseDockerImage=eclipse-temurin:8-jdk
dockerImagePlatform=amd64:linux
# 可执行 JAR 配置
executable=true
# 嵌入式服务器选择
appServer=-tomcat
# SSL 证书配置
certDir=/etc/cas
serverKeystore=thekeystore
exportedServerCert=cas.crt
storeType=PKCS12
# Gradle 构建优化
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=false1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
1.4.2 CAS 6.6.x 的 gradle.properties
6.6.x 在 5.3.x 基础上的主要变化:
properties
# 版本升级
version=6.6.15.2
cas.version=6.6.15.2
# Spring Boot 版本保持不变
springBootVersion=2.7.18
# Java 版本升级到 11
sourceCompatibility=11
targetCompatibility=11
# 基础镜像升级
baseDockerImage=eclipse-temurin:11-jdk1
2
3
4
5
6
7
8
9
10
11
12
13
2
3
4
5
6
7
8
9
10
11
12
13
1.4.3 CAS 7.3.x 的 gradle.properties
7.3.x 引入了大量新属性:
properties
# 版本大幅升级
version=7.3.4
cas.version=7.3.4
# Spring Boot 升级到 3.x
springBootVersion=3.5.6
# Java 升级到 21
sourceCompatibility=21
targetCompatibility=21
# 新增:JVM Vendor 选择
jvmVendor=AMAZON
# 新增:Foojay Gradle Plugin(JDK 自动下载)
gradleFoojayPluginVersion=1.0.0
# 插件版本升级
gradleFreeFairPluginVersion=9.2.0
lombokVersion=1.18.42
jibVersion=3.5.3
gradleDockerPluginVersion=9.4.0
# 新增:CycloneDX SBOM 插件
gradleCyclonePluginVersion=3.2.0
# 基础镜像升级到 JDK 21
baseDockerImage=azul/zulu-openjdk:21
# 新增:HTTP 超时配置
systemProp.org.gradle.internal.http.connectionTimeout=60000
systemProp.org.gradle.internal.http.requestTimeout=1200001
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
版本管理最佳实践:
- 单一真相源(Single Source of Truth): 所有版本号集中在
gradle.properties中管理,build.gradle通过${project.xxx}引用,避免硬编码。 - 版本锁定策略:
cas.version和springBootVersion必须严格匹配,不可随意升级。CAS 官方在每个版本中明确绑定了经过测试的 Spring Boot 版本。 - 渐进式升级: 建议按照 5.3 -> 6.6 -> 7.3 的路径逐步升级,每次升级一个主版本。
- 属性覆盖机制:
gradle.properties支持通过命令行参数覆盖,例如./gradlew build -Dcas.version=6.6.16。
1.5 settings.gradle 项目配置
settings.gradle 文件定义了项目的名称和构建范围。三个版本的差异反映了 Gradle 生态的演进:
CAS 5.3.x / 6.6.x:
groovy
rootProject.name = 'cas-overlay-5.3.x'1
CAS 7.3.x:
groovy
plugins {
id "org.gradle.toolchains.foojay-resolver-convention" version "${gradleFoojayPluginVersion}"
}
rootProject.name = 'cas-overlay-7.3.x'1
2
3
4
2
3
4
7.3.x 的 settings.gradle 新增了 Foojay Resolver Convention 插件声明。这是一个关键变化——该插件使得 Gradle 能够自动从 Foojay API 下载匹配的 JDK 发行版,无需手动安装。我们将在第二章详细解析这一机制。
1.6 Gradle Wrapper
Gradle Wrapper 是 Gradle 项目的标准组成部分,它确保所有开发者和 CI/CD 环境使用完全相同的 Gradle 版本。Wrapper 由三个文件组成:
| 文件 | 用途 |
|---|---|
gradlew | Unix/Linux/macOS 启动脚本 |
gradlew.bat | Windows 启动脚本 |
gradle-wrapper.jar | Wrapper 引导 JAR |
gradle/wrapper/gradle-wrapper.properties | Wrapper 配置文件 |
1.6.1 gradle-wrapper.properties 配置解析
CAS 5.3.x / 6.6.x(Gradle 7.5):
properties
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://mirrors.huaweicloud.com/gradle/gradle-7.5-bin.zip
networkTimeout=10000
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists1
2
3
4
5
6
2
3
4
5
6
CAS 7.3.x(Gradle 9.1.0):
properties
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://mirrors.cloud.tencent.com/gradle/gradle-9.1.0-bin.zip
networkTimeout=60000
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists1
2
3
4
5
6
2
3
4
5
6
配置要点解析:
- distributionUrl: 指定 Gradle 发行版的下载地址。实际项目中使用了华为云和腾讯云的镜像地址,这是国内网络环境下的最佳实践。注释中保留了官方地址作为备选。
- networkTimeout: 网络超时时间。7.3.x 从 10 秒增加到 60 秒,适应大文件下载场景。
- distributionBase / distributionPath: 控制 Gradle 发行版的存储位置。默认存储在
~/.gradle/wrapper/dists/目录下。 - zipStoreBase / zipStorePath: 控制解压后文件的存储位置。
Wrapper 使用最佳实践:
bash
# 使用 Wrapper 执行构建(推荐)
./gradlew build
# 使用 Wrapper 运行 CAS
./gradlew bootRun
# 使用 Wrapper 创建密钥库
./gradlew createKeystore
# 升级 Wrapper 版本
./gradlew wrapper --gradle-version=9.1.01
2
3
4
5
6
7
8
9
10
11
2
3
4
5
6
7
8
9
10
11
安全提示: 永远不要直接使用系统安装的 Gradle 版本构建 CAS Overlay 项目。Wrapper 确保了构建的可重复性(Reproducibility),这是 CI/CD 流水线稳定运行的基础。
第二章 Gradle 版本演进
2.1 版本对应关系总览
CAS Overlay 的三个主要版本分别绑定了不同的 Gradle 版本,这反映了 Gradle 生态和 Java 平台的演进轨迹:
| CAS 版本 | Gradle 版本 | Java 版本 | Spring Boot 版本 | 发布周期 |
|---|---|---|---|---|
| 5.3.x | 7.5 | 8 | 2.7.x | 2019-2022 |
| 6.6.x | 7.5 | 11 | 2.7.x | 2022-2024 |
| 7.3.x | 9.1.0 | 21 | 3.5.x | 2024-至今 |
2.2 Gradle 7.5:CAS 5.3.x / 6.6.x 的构建基石
Gradle 7.5 是一个 LTS(长期支持)级别的版本,它为 CAS 5.3.x 和 6.6.x 提供了稳定的构建基础。其核心特性包括:
2.2.1 配置缓存(Configuration Cache)预览
Gradle 7.5 引入了配置缓存机制,这是一项革命性的性能优化。传统的 Gradle 构建中,每次执行任务都需要重新解析整个构建脚本(配置阶段),即使代码没有变化。配置缓存通过缓存配置阶段的结果来消除这一开销。
在 CAS Overlay 项目中,配置缓存默认是关闭的:
properties
# gradle.properties
org.gradle.unsafe.configuration-cache=false
org.gradle.unsafe.configuration-cache-problems=warn1
2
3
2
3
之所以默认关闭,是因为 CAS 的构建脚本中大量使用了动态特性(如 project.hasProperty()、System.properties 等),这些特性与配置缓存不完全兼容。开启配置缓存需要仔细审查所有构建脚本,确保它们是幂等的。
2.2.2 Java Toolchain 支持
Gradle 7.3+ 引入了 Java Toolchain API,允许构建脚本声明项目所需的 JDK 版本,而不是依赖本地安装的 JDK。CAS 6.6.x 开始使用这一特性:
groovy
// CAS 6.6.x - build.gradle
java {
toolchain {
languageVersion = JavaLanguageVersion.of(11)
}
}1
2
3
4
5
6
2
3
4
5
6
这段配置的含义是:项目需要 Java 11 来编译和运行。Gradle 会自动在以下位置查找匹配的 JDK:
~/.gradle/jdks/(由 Toolchain 自动下载的 JDK)- 本地系统的 JDK 安装目录
JAVA_HOME指向的 JDK
如果找不到匹配的 JDK,Gradle 7.x 会报错,但不会自动下载。这正是 7.3.x 引入 Foojay Plugin 的原因。
2.2.3 依赖验证(Dependency Verification)
Gradle 7.5 增强了依赖验证机制,可以通过校验和来验证依赖的完整性。虽然 CAS Overlay 项目没有显式启用这一特性,但在安全要求较高的环境中,这是一个值得考虑的加固措施。
2.3 Gradle 9.1.0:CAS 7.3.x 的现代构建引擎
Gradle 9.1.0 是一个重大版本升级,带来了许多面向现代 Java 开发的特性。CAS 7.3.x 选择 Gradle 9.1.0 的核心原因包括:
2.3.1 Java 21 完整支持
Gradle 9.x 对 Java 21 提供了一流的支持,包括:
- 正确处理 Java 21 的模块系统(JPMS)
- 支持 Record 模式、Switch 表达式等新语法
- 兼容 Java 21 的 JVM 参数和行为变更
2.3.2 配置缓存稳定化
虽然 CAS 7.3.x 仍然默认关闭配置缓存,但 Gradle 9.x 的配置缓存实现更加成熟。在 build.gradle 中可以看到对配置缓存兼容性的显式处理:
groovy
// CAS 7.3.x - build.gradle
def configurationCacheRequested = services.get(BuildFeatures).configurationCache.requested.getOrElse(true)
// Jib 插件与配置缓存的兼容性处理
['jibDockerBuild', 'jibBuildTar', 'jib'].each { taskName ->
getTasksByName(taskName, true).each(it -> {
it.notCompatibleWithConfigurationCache("Jib is not compatible with configuration cache");
it.enabled = !configurationCacheRequested
})
}1
2
3
4
5
6
7
8
9
10
2
3
4
5
6
7
8
9
10
这段代码展示了 CAS 7.3.x 对配置缓存的精细控制:
- 检测配置缓存是否被请求
- 对于不兼容配置缓存的任务(如 Jib),标记为
notCompatibleWithConfigurationCache - 当配置缓存开启时,自动禁用不兼容的任务
2.3.3 Kotlin DSL 增强和 Groovy DSL 持续支持
Gradle 9.x 继续增强 Kotlin DSL 的支持,同时保持对 Groovy DSL 的完全兼容。CAS Overlay 项目选择继续使用 Groovy DSL,这主要是出于以下考虑:
- CAS 社区的历史习惯和文档一致性
- Groovy DSL 的动态特性更适合 CAS 构建脚本中的条件逻辑
tasks.gradle中大量使用 Groovy 的元编程能力(如FileTreeBuilder)
2.4 Java Toolchain 深度解析
Java Toolchain 是 Gradle 提供的一项强大特性,它将"构建所需的 JDK"与"运行 Gradle 的 JDK"解耦。这意味着:
- 你可以用 JDK 17 运行 Gradle,但编译项目时使用 JDK 21
- 团队成员可以使用不同的 JDK 版本运行 Gradle,但编译结果一致
- CI/CD 环境无需预装特定版本的 JDK
2.4.1 CAS 6.6.x 的 Toolchain 配置
groovy
// CAS 6.6.x - build.gradle
java {
toolchain {
languageVersion = JavaLanguageVersion.of(11)
}
}1
2
3
4
5
6
2
3
4
5
6
这是最简单的 Toolchain 声明——只指定了语言版本。Gradle 会自动查找本地安装的 JDK 11。
2.4.2 CAS 7.3.x 的增强 Toolchain 配置
groovy
// CAS 7.3.x - build.gradle
java {
toolchain {
languageVersion = JavaLanguageVersion.of(project.targetCompatibility)
def chosenJvmVendor = null
if (project.jvmVendor != null) {
out.withStyle(Style.Info).println(
"JVM vendor ${project.jvmVendor} is requested for the Java toolchain")
try {
// 优先尝试 Gradle 8.x 枚举写法
chosenJvmVendor = JvmVendorSpec."${project.jvmVendor?.toUpperCase()}"
} catch (MissingPropertyException e) {
// 失败则回退到 Gradle 7.x of() 方法
try {
chosenJvmVendor = JvmVendorSpec.of(project.jvmVendor?.toUpperCase())
} catch (MissingMethodException ex) {
out.withStyle(Style.Warning).println(
"JVM vendor ${project.jvmVendor} is not supported")
}
}
}
if (chosenJvmVendor != null) {
vendor = chosenJvmVendor
}
}
}1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
7.3.x 的 Toolchain 配置增加了 JVM Vendor 选择 功能。这是一个非常实用的特性,因为不同的 JDK 发行版在性能、许可证和生态支持方面存在差异。
2.4.3 支持的 JVM Vendor
CAS 7.3.x 支持以下 JVM Vendor(通过 jvmVendor 属性配置):
| Vendor | 枚举值 | 特点 | 适用场景 |
|---|---|---|---|
| Amazon Corretto | AMAZON | AWS 优化,免费使用 | AWS 部署环境 |
| Eclipse Adoptium | ADOPTIUM | 社区驱动,广泛兼容 | 通用场景 |
| JetBrains | JETBRAINS | 开发工具优化 | 开发环境 |
| Microsoft | MICROSOFT | Azure 优化 | Azure 部署环境 |
| Oracle | ORACLE | 官方发行版 | 商业许可环境 |
| SAP | SAP | 企业级支持 | SAP 生态集成 |
| BellSoft | BELLSOFT | Liberica JDK | 欧洲市场 |
| Azul Zulu | AZUL | 高性能 | 通用/容器化场景 |
配置示例:
properties
# gradle.properties - 使用 Amazon Corretto
jvmVendor=AMAZON1
2
2
2.4.4 Toolchain 版本检测机制
CAS 7.3.x 的 tasks.gradle 中包含了一个 Java 版本验证任务:
groovy
// tasks.gradle - verifyRequiredJavaVersion
task verifyRequiredJavaVersion {
def currentVersion = org.gradle.api.JavaVersion.current()
logger.info "Checking current Java version ${currentVersion} "
logger.info "for required Java version ${project.targetCompatibility}"
def targetVersion = JavaVersion.toVersion(project.targetCompatibility)
if (!currentVersion.isCompatibleWith(targetVersion)) {
logger.warn("Careful: Current Java version ${currentVersion} "
+ "does not match required version ${project.targetCompatibility}")
}
}1
2
3
4
5
6
7
8
9
10
11
2
3
4
5
6
7
8
9
10
11
这个任务会在构建早期发出警告,帮助开发者快速发现 JDK 版本不匹配的问题。
2.5 Foojay Gradle Plugin:JDK 自动下载
2.5.1 背景与动机
在 CAS 7.3.x 之前,如果本地没有安装正确版本的 JDK,Gradle Toolchain 只能报错。开发者需要手动下载和安装 JDK,这在以下场景中非常不便:
- 新成员加入团队时的环境搭建
- CI/CD 流水线中的 JDK 版本管理
- 需要在同一台机器上切换多个 JDK 版本
Foojay Gradle Plugin(org.gradle.toolchains.foojay-resolver-convention)解决了这个问题。它是一个 Toolchain Resolver,能够自动从 Foojay Discovery API 下载匹配的 JDK 发行版。
2.5.2 配置方式
在 CAS 7.3.x 的 settings.gradle 中声明:
groovy
// settings.gradle
plugins {
id "org.gradle.toolchains.foojay-resolver-convention" version "1.0.0"
}1
2
3
4
2
3
4
在 gradle.properties 中配置版本:
properties
gradleFoojayPluginVersion=1.0.01
2.5.3 工作原理
Foojay Plugin 的工作流程如下:
- Gradle 解析
java.toolchain配置,确定需要的 JDK 版本和 Vendor - Gradle 首先在本地查找匹配的 JDK(
~/.gradle/jdks/、系统路径等) - 如果本地没有找到,Foojay Plugin 通过 Foojay API 查询可用的发行版
- 自动下载匹配的 JDK 到
~/.gradle/jdks/目录 - 后续构建直接使用已下载的 JDK,无需重复下载
重要特性:
- 自动检测优先: 只有在本地找不到匹配 JDK 时才会触发下载
- 增量更新: 已下载的 JDK 不会被自动更新,需要手动管理
- Vendor 感知: 如果指定了
jvmVendor=AMAZON,则只下载 Amazon Corretto - 缓存友好: 下载的 JDK 存储在 Gradle 缓存目录中,可被多个项目共享
2.5.4 实际效果
配置 Foojay Plugin 后,一个全新的开发环境只需要安装任意版本的 JDK 来运行 Gradle Wrapper,然后执行构建命令:
bash
# 全新环境,只需安装任意 JDK(用于运行 Gradle)
# Gradle 会自动下载 JDK 21 Amazon Corretto
./gradlew build1
2
3
2
3
构建日志中会显示类似以下内容:
> Task :compileJava
Using Java toolchain: 21 (Amazon Corretto 21.0.x)
Downloading Amazon Corretto 21.0.x from https://api.foojay.io/...
Unpacking JDK to ~/.gradle/jdks/amazon-corretto-21.0.x/1
2
3
4
2
3
4
第三章 BOM 依赖管理
3.1 BOM(Bill of Materials)概述
BOM 是 Maven/Gradle 生态中用于集中管理依赖版本的核心机制。一个 BOM 本质上是一个特殊的 POM 文件,它列出了所有相关依赖的推荐版本号,但不引入任何实际的依赖。项目通过导入 BOM 来获取版本信息,从而避免在每个依赖声明中重复指定版本号。
CAS 项目提供了 cas-server-support-bom,其中包含了所有 CAS 模块及其传递依赖的精确版本号。这是 CAS 能够实现"修改一个版本号即可升级"的关键基础设施。
3.2 CAS 5.3.x:mavenBom + dependencyManagement
CAS 5.3.x 使用的是最传统的 BOM 导入方式,结合 io.spring.dependency-management 插件:
groovy
// CAS 5.3.x - build.gradle
apply plugin: "io.spring.dependency-management"
dependencyManagement {
imports {
mavenBom "org.apereo.cas:cas-server-support-bom:${project['cas.version']}"
mavenBom 'org.apache.logging.log4j:log4j-bom:2.12.4'
}
dependencies {
// 手动覆盖 BOM 中的版本
dependency 'org.apache.logging.log4j:log4j-core:2.12.4'
dependency 'commons-beanutils:commons-beanutils:1.9.4'
dependency 'org.dom4j:dom4j:2.1.3'
dependency 'org.apache.commons:commons-configuration2:2.8.0'
dependency 'org.jsoup:jsoup:1.15.3'
dependency 'ch.qos.logback:logback-core:1.1.11'
dependency 'ch.qos.logback:logback-classic:1.1.11'
dependency 'org.quartz-scheduler:quartz:2.3.2'
dependency 'com.fasterxml.jackson.core:jackson-databind:2.13.4.2'
dependency 'com.fasterxml.jackson.core:jackson-core:2.13.4'
dependency 'com.fasterxml.jackson.core:jackson-annotations:2.13.4'
dependency 'org.apache.tomcat.embed:tomcat-embed-core:8.5.32'
dependency 'org.apache.tomcat.embed:tomcat-embed-websocket:8.5.32'
}
}1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
5.3.x BOM 管理的特点和问题:
- 需要
io.spring.dependency-management插件: 这是 Spring 提供的 Gradle 插件,用于在 Gradle 中模拟 Maven 的dependencyManagement机制。 - 大量手动版本覆盖: 由于 CAS 5.3.x 的 BOM 不够完善,许多第三方依赖的版本需要手动指定。这导致了
dependencyManagement.dependencies块非常冗长。 - 版本冲突风险: 手动指定的版本可能与 BOM 中的版本冲突,需要通过
exclude块来解决。 - 依赖声明冗余: 每个 CAS 模块都需要重复声明相同的
exclude块:
groovy
// 5.3.x 中每个依赖都需要这样的排除声明
implementation('org.apereo.cas:cas-server-core') {
exclude group: 'org.springframework.boot', module: 'spring-boot-devtools'
exclude group: 'org.apache.logging.log4j', module: 'log4j-slf4j-impl'
exclude group: 'dom4j', module: 'dom4j'
}
implementation('org.apereo.cas:cas-server-support-rest') {
exclude group: 'org.springframework.boot', module: 'spring-boot-devtools'
exclude group: 'org.apache.logging.log4j', module: 'log4j-slf4j-impl'
exclude group: 'org.apache.logging.log4j', module: 'log4j-web'
exclude group: 'org.slf4j', module: 'slf4j-api'
exclude group: 'org.slf4j', module: 'jul-to-slf4j'
exclude group: 'dom4j', module: 'dom4j'
}
// ... 重复 30+ 次1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
3.3 CAS 6.6.x:platform() BOM 导入
CAS 6.6.x 引入了 Gradle 原生的 platform() 依赖范围,这是 BOM 管理的一次重大简化:
groovy
// CAS 6.6.x - build.gradle
dependencies {
/**
* Do NOT modify the lines below or else you will risk breaking dependency management.
**/
implementation platform(org.springframework.boot.gradle.plugin.SpringBootPlugin.BOM_COORDINATES)
implementation platform("org.apereo.cas:cas-server-support-bom:${project.'cas.version'}")
/**
* Do NOT modify the lines below or else you will risk breaking the build.
**/
implementation "org.apereo.cas:cas-server-core-api-configuration-model"
implementation "org.apereo.cas:cas-server-webapp-init"
// CAS 依赖无需指定版本号
implementation "org.apereo.cas:cas-server-core"
implementation "org.apereo.cas:cas-server-support-oauth"
implementation "org.apereo.cas:cas-server-support-redis-ticket-registry"
// ...
}1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
platform() 的核心优势:
- Gradle 原生支持: 不需要额外的插件(
io.spring.dependency-management插件在 6.6.x 中被移除)。 - 版本号自动解析: 通过
platform()导入 BOM 后,同一配置中的其他依赖可以省略版本号,Gradle 会自动从 BOM 中查找。 - 依赖声明大幅简化: 不再需要每个依赖都声明
exclude块,全局排除通过configurations.all统一处理。
platform() vs enforcedPlatform() 的区别:
platform():推荐版本,但允许被覆盖。如果项目中显式指定了某个依赖的版本,该版本会优先生效。enforcedPlatform():强制版本,不允许被覆盖。即使项目中显式指定了版本,也会被 BOM 中的版本覆盖。
CAS 6.6.x 使用 platform(),这意味着开发者仍然可以在需要时覆盖个别依赖的版本。但这也带来了版本漂移的风险。
3.4 CAS 7.3.x:enforcedPlatform() 强制版本一致性
CAS 7.3.x 将 BOM 导入升级为 enforcedPlatform(),这是对依赖管理严格性的进一步提升:
groovy
// CAS 7.3.x - build.gradle
dependencies {
/**
* Do NOT modify the lines below or else you will risk breaking dependency management.
**/
implementation enforcedPlatform("org.apereo.cas:cas-server-support-bom:${project.'cas.version'}")
implementation platform(org.springframework.boot.gradle.plugin.SpringBootPlugin.BOM_COORDINATES)
// ...
}1
2
3
4
5
6
7
8
9
2
3
4
5
6
7
8
9
注意顺序: enforcedPlatform 用于 CAS BOM,而 platform 用于 Spring Boot BOM。这意味着:
- CAS 的依赖版本被强制锁定,任何覆盖尝试都会被忽略
- Spring Boot 的依赖版本被推荐,允许在必要时覆盖
为什么 CAS BOM 使用 enforcedPlatform?
- 版本一致性保证: CAS 是一个高度集成的系统,各个模块之间的版本必须严格匹配。使用
enforcedPlatform可以防止开发者意外引入不兼容的版本。 - 减少升级风险: 强制版本一致性意味着升级 CAS 版本时,所有 CAS 模块会自动升级到匹配的版本,不会出现部分模块版本不一致的情况。
- 简化故障排查: 当出现依赖冲突时,
enforcedPlatform会明确地以 BOM 版本为准,消除了版本选择的不确定性。
为什么 Spring Boot BOM 仍然使用 platform?
Spring Boot BOM 使用 platform() 而非 enforcedPlatform(),是因为 CAS 在某些场景下需要覆盖 Spring Boot 管理的依赖版本(例如特定版本的 Tomcat)。通过 platform() 保留了这种灵活性。
3.5 Spring Boot BOM 协调
CAS 项目同时依赖 CAS BOM 和 Spring Boot BOM,两者的协调是一个微妙的问题。CAS BOM 本身已经包含了 Spring Boot 的版本信息,但 CAS 6.6.x 和 7.3.x 仍然显式导入了 Spring Boot BOM:
groovy
// CAS 6.6.x / 7.3.x
implementation platform(org.springframework.boot.gradle.plugin.SpringBootPlugin.BOM_COORDINATES)1
2
2
这里使用 SpringBootPlugin.BOM_COORDINATES 而非硬编码的 BOM 坐标,确保了 Spring Boot BOM 的版本与 spring-boot-gradle-plugin 完全一致。
BOM 优先级规则:
当两个 BOM 对同一个依赖定义了不同的版本时,Gradle 按以下规则处理:
enforcedPlatform优先于platform- 先声明的优先于后声明的
- 显式指定的版本优先于 BOM 中的版本(仅对
platform)
在 CAS 7.3.x 中,CAS BOM(enforcedPlatform)优先于 Spring Boot BOM(platform),这意味着 CAS 团队已经确保了 CAS BOM 中包含的 Spring 依赖版本与 Spring Boot BOM 兼容。
3.6 buildscript 依赖(classpath)
buildscript 块中的 dependencies 声明了构建脚本自身需要的依赖。这些依赖不会成为项目运行时依赖,而是用于驱动构建过程。
CAS 5.3.x 的 buildscript:
groovy
buildscript {
repositories {
mavenCentral()
maven { url 'https://build.shibboleth.net/nexus/content/repositories/releases' }
}
dependencies {
classpath "org.springframework.boot:spring-boot-gradle-plugin:${springBootVersion}"
}
}1
2
3
4
5
6
7
8
9
2
3
4
5
6
7
8
9
5.3.x 的 buildscript 非常简洁,只需要 Spring Boot Gradle Plugin。
CAS 6.6.x 的 buildscript:
groovy
buildscript {
dependencies {
classpath "org.springframework.boot:spring-boot-gradle-plugin:${project.springBootVersion}"
classpath "io.freefair.gradle:maven-plugin:${project.gradleFreeFairPluginVersion}"
classpath "io.freefair.gradle:lombok-plugin:${project.gradleFreeFairPluginVersion}"
classpath "io.spring.gradle:dependency-management-plugin:${project.gradleDependencyManagementPluginVersion}"
classpath "de.undercouch:gradle-download-task:${project.gradleDownloadTaskVersion}"
classpath "org.apereo.cas:cas-server-core-api-configuration-model:${project.'cas.version'}"
classpath "org.apereo.cas:cas-server-core-configuration-metadata-repository:${project.'cas.version'}"
}
}1
2
3
4
5
6
7
8
9
10
11
2
3
4
5
6
7
8
9
10
11
6.6.x 新增了:
- FreeFair Plugins: 提供 Lombok 集成和 Maven 兼容功能
- Dependency Management Plugin: Spring 的依赖管理插件
- Gradle Download Task: 用于下载 CAS Shell 等外部资源
- CAS Configuration Metadata: 用于
exportConfigMetadata任务
CAS 7.3.x 的 buildscript:
groovy
buildscript {
dependencies {
// 保留 6.6.x 的所有 classpath
classpath "com.google.cloud.tools:jib-gradle-plugin:${project.jibVersion}"
classpath "com.bmuschko:gradle-docker-plugin:${project.gradleDockerPluginVersion}"
classpath "org.cyclonedx:cyclonedx-gradle-plugin:${project.gradleCyclonePluginVersion}"
}
}1
2
3
4
5
6
7
8
2
3
4
5
6
7
8
7.3.x 新增了:
- Jib Plugin: Google 的容器化构建工具
- Docker Plugin: bmuschko 的 Docker 远程 API 插件
- CycloneDX Plugin: SBOM 生成工具
3.7 全局依赖排除策略
CAS 6.6.x 和 7.3.x 通过 configurations.all 实现了全局依赖排除,这是相比 5.3.x 的一大改进:
groovy
// CAS 6.6.x / 7.3.x - build.gradle
configurations {
all {
resolutionStrategy {
cacheChangingModulesFor 0, "seconds"
cacheDynamicVersionsFor 0, "seconds"
preferProjectModules()
def failIfConflict = project.hasProperty("failOnVersionConflict")
&& Boolean.valueOf(project.getProperty("failOnVersionConflict"))
if (failIfConflict) {
failOnVersionConflict()
}
if (project.hasProperty("tomcatVersion")) {
eachDependency { DependencyResolveDetails dependency ->
def requested = dependency.requested
if (requested.group.startsWith("org.apache.tomcat")
&& requested.name != "jakartaee-migration") {
dependency.useVersion("${tomcatVersion}")
}
}
}
}
// 全局排除冲突的日志框架
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: "org.slf4j", module: "jcl-over-slf4j")
exclude(group: "org.apache.logging.log4j", module: "log4j-to-slf4j")
exclude(group: "ch.qos.logback", module: "logback-core")
exclude(group: "ch.qos.logback", module: "logback-classic")
}
}1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
排除策略解析:
- 日志框架统一: CAS 使用 Log4j2 作为日志框架,因此排除了 Logback、SLF4J 的其他绑定实现,避免多个日志框架共存导致的冲突。
- CGLib 排除: CGLib 与 Spring 的 CglibAopProxy 存在冲突,统一排除后由 Spring 自行管理。
- 缓存策略:
cacheChangingModulesFor 0, "seconds"和cacheDynamicVersionsFor 0, "seconds"确保每次构建都检查最新的 SNAPSHOT 版本。 - Tomcat 版本覆盖: 通过
tomcatVersion属性可以统一覆盖所有 Tomcat 相关依赖的版本。 - 版本冲突策略: 通过
failOnVersionConflict属性可以控制是否在版本冲突时报错。
第四章 springboot.gradle 深度解析
springboot.gradle 是 CAS Overlay 项目中将 Spring Boot 构建配置独立管理的脚本文件。从 CAS 6.6.x 开始,Spring Boot 相关的配置被抽取到这个独立文件中,通过 apply from: 引入。这种分离使得 build.gradle 更加聚焦于依赖管理和插件配置,而 springboot.gradle 专注于运行时行为。
4.1 bootRun 任务配置
bootRun 是 Spring Boot Gradle Plugin 提供的核心任务之一,用于在开发环境中直接运行 CAS 应用。CAS Overlay 的 springboot.gradle 对 bootRun 进行了大量定制。
4.1.1 自定义 Classpath 配置
CAS 的 bootRun 使用了自定义的 bootRunConfig 配置,而非默认的 runtimeClasspath:
groovy
// springboot.gradle
configurations {
bootRunConfig {
extendsFrom compileClasspath
exclude(group: "org.springframework.boot", module: "spring-boot-starter-logging")
exclude(group: "ch.qos.logback", module: "logback-core")
exclude(group: "ch.qos.logback", module: "logback-classic")
}
}
dependencies {
bootRunConfig "org.apereo.cas:cas-server-core"
bootRunConfig "org.apereo.cas:cas-server-core-logging"
bootRunConfig "org.apereo.cas:cas-server-core-web"
bootRunConfig "org.apereo.cas:cas-server-core-webflow"
bootRunConfig "org.apereo.cas:cas-server-core-cookie"
bootRunConfig "org.apereo.cas:cas-server-core-logout"
bootRunConfig "org.apereo.cas:cas-server-core-authentication"
bootRunConfig "org.apereo.cas:cas-server-core-validation"
bootRunConfig "org.apereo.cas:cas-server-core-audit"
bootRunConfig "org.apereo.cas:cas-server-core-tickets"
bootRunConfig "org.apereo.cas:cas-server-core-services"
bootRunConfig "org.apereo.cas:cas-server-core-util"
// 6.6.x / 7.3.x 的差异
bootRunConfig "org.apereo.cas:cas-server-support-thymeleaf"
bootRunConfig "org.apereo.cas:cas-server-support-validation"
bootRunConfig "org.apereo.cas:cas-server-support-person-directory"
bootRunConfig "org.apereo.cas:cas-server-webapp-config"
bootRunConfig "org.apereo.cas:cas-server-webapp-resources"
bootRunConfig "org.apereo.cas:cas-server-webapp-init"
bootRunConfig "org.apereo.cas:cas-server-webapp-init-tomcat"
bootRunConfig "org.springframework.cloud:spring-cloud-starter-bootstrap"
}1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
为什么要创建自定义的 bootRunConfig?
默认的 runtimeClasspath 包含了所有 implementation 依赖,其中可能包含一些在开发运行时不需要的模块(如特定的数据库驱动、OAuth2 模块等)。通过自定义 bootRunConfig,可以精确控制开发运行时的类路径,加快启动速度。
7.3.x 的差异: 7.3.x 在 bootRunConfig 中新增了 cas-server-support-webconfig 和 spring-boot-devtools:
groovy
// CAS 7.3.x - springboot.gradle
bootRunConfig "org.apereo.cas:cas-server-support-webconfig"
bootRunConfig "org.springframework.boot:spring-boot-devtools"1
2
3
2
3
spring-boot-devtools 提供了热重载(LiveReload)功能,在开发时修改代码后自动重启应用。
4.1.2 bootRun 任务主体配置
groovy
// springboot.gradle
bootRun {
classpath = configurations.bootRunConfig
+ sourceSets.main.compileClasspath
+ sourceSets.main.runtimeClasspath
sourceResources sourceSets.bootRunSources
doFirst {
systemProperties = System.properties
}
// ... JVM 参数和应用参数
}1
2
3
4
5
6
7
8
9
10
11
2
3
4
5
6
7
8
9
10
11
配置解析:
- classpath: 将自定义的
bootRunConfig与主源码的编译和运行时类路径合并。注意顺序——bootRunConfig在前,意味着其中的依赖会优先生效。 - sourceResources: 引入
bootRunSources源码集,该源码集指向/etc/cas/templates/目录,用于加载外部模板。 - systemProperties: 将当前 JVM 的系统属性传递给 CAS 应用进程。这对于传递调试参数、配置覆盖等非常有用。
4.2 JVM 参数配置
CAS 的 bootRun 配置了大量 JVM 参数,这些参数对于 CAS 的正常运行至关重要:
groovy
// springboot.gradle
def list = []
list.add("-XX:TieredStopAtLevel=1") // 限制 JIT 编译层级
list.add("-Xverify:none") // 跳过字节码验证
list.add("--add-modules")
list.add("java.se") // 添加 Java SE 模块
list.add("--add-exports")
list.add("java.base/jdk.internal.ref=ALL-UNNAMED") // 导出内部 API
list.add("--add-opens")
list.add("java.base/java.lang=ALL-UNNAMED") // 开放反射访问
list.add("--add-opens")
list.add("java.base/java.nio=ALL-UNNAMED")
list.add("--add-opens")
list.add("java.base/sun.nio.ch=ALL-UNNAMED")
list.add("--add-opens")
list.add("java.management/sun.management=ALL-UNNAMED")
list.add("--add-opens")
list.add("jdk.management/com.sun.management.internal=ALL-UNNAMED")
list.add("-Xrunjdwp:transport=dt_socket,address=5000,server=y,suspend=n") // 远程调试
jvmArgs = list1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
参数详解:
| 参数 | 作用 | 为什么需要 |
|---|---|---|
-XX:TieredStopAtLevel=1 | 限制 C2 JIT 编译器不启用 | 开发模式下加快启动速度,减少编译开销 |
-Xverify:none | 跳过类加载时的字节码验证 | 进一步加快启动速度(生产环境不建议使用) |
--add-modules java.se | 确保完整的 Java SE 模块可用 | 某些精简的 JDK 运行时可能不包含所有模块 |
--add-exports java.base/jdk.internal.ref=ALL-UNNAMED | 导出 JDK 内部的 Reference 类 | 部分第三方库(如某些缓存实现)依赖这些内部 API |
--add-opens java.base/java.lang=ALL-UNNAMED | 开放 java.lang 包的深层反射 | Spring Framework、Hibernate 等框架需要深层反射来操作 JDK 内部类 |
--add-opens java.base/java.nio=ALL-UNNAMED | 开开 NIO 包的深层反射 | Netty 等 NIO 框架需要 |
--add-opens java.base/sun.nio.ch=ALL-UNNAMED | 开放 NIO 通道实现 | 底层网络通信库需要 |
--add-opens java.management/sun.management=ALL-UNNAMED | 开放 JMX 管理 | 监控和管理功能需要 |
--add-opens jdk.management/com.sun.management.internal=ALL-UNNAMED | 开放 JDK 管理 | HotSpot 诊断功能需要 |
-Xrunjdwp:... | 启用远程调试 | 开发时通过 IDE 连接调试器 |
模块系统参数(--add-opens / --add-exports)的背景:
从 Java 9 开始,Java 引入了模块系统(JPMS),默认情况下 JDK 内部 API 不允许被外部代码访问。但许多成熟的框架(包括 Spring、Hibernate、Netty)依赖这些内部 API。--add-opens 和 --add-exports 参数用于临时开放这些访问权限,直到框架完全适配模块系统。
4.3 系统属性传递
groovy
bootRun {
doFirst {
systemProperties = System.properties
}
}1
2
3
4
5
2
3
4
5
这行代码将运行 Gradle 的 JVM 的所有系统属性传递给 CAS 应用进程。这意味着:
bash
# 在命令行设置的系统属性会自动传递给 CAS
./gradlew bootRun -Dcas.standalone.configuration-directory=/etc/cas/config
./gradlew bootRun -Dcas.service.registry.dir=/etc/cas/services
./gradlew bootRun -Dspring.profiles.active=dev1
2
3
4
2
3
4
注意事项:
systemProperties赋值发生在doFirst阶段,这是任务执行前的最后一个时机- 如果 CAS 应用和 Gradle 运行在同一个 JVM 中(某些 IDE 配置),系统属性会自动共享
- 生产环境部署时不适用此机制,需要通过操作系统的环境变量或启动脚本传递
4.4 调试配置
CAS 7.3.x 的 tasks.gradle 提供了独立的调试任务:
groovy
// CAS 7.3.x - tasks.gradle
tasks.register('debug', JavaExec) {
group = "CAS"
description = "Debug the CAS web application in embedded container mode on port 5005"
jvmArgs = [
"-server",
"-Xmx2048M",
"-XX:+TieredCompilation",
"-XX:TieredStopAtLevel=1"
]
debug = true
systemProperties = System.properties
classpath = files("$buildDir/libs/cas.war")
}1
2
3
4
5
6
7
8
9
10
11
12
13
14
2
3
4
5
6
7
8
9
10
11
12
13
14
调试工作流:
- 在 IDE(如 IntelliJ IDEA)中创建远程调试配置,端口设为 5005
- 执行
./gradlew debug,CAS 会在 5005 端口启动调试监听 - 在 IDE 中连接调试器
- 设置断点,开始调试
注意: springboot.gradle 中的 bootRun 配置已经包含了 -Xrunjdwp:transport=dt_socket,address=5000,server=y,suspend=n,这意味着直接运行 ./gradlew bootRun 也可以通过 5000 端口进行调试。两者使用不同的端口(5000 vs 5005),可以同时存在。
4.5 bootJar 配置
bootJar 任务负责将 CAS 应用打包为可执行的 JAR 文件。CAS Overlay 的 bootJar 配置体现了对生产部署的细致考虑。
4.5.1 CAS 5.3.x 的 bootJar 配置
groovy
// CAS 5.3.x - build.gradle
tasks.named('bootJar') {
archiveFileName = 'cas-overlay.jar'
requiresUnpack '**/*.jar'
}1
2
3
4
5
2
3
4
5
5.3.x 的配置非常简洁:
archiveFileName:指定输出文件名为cas-overlay.jarrequiresUnpack:标记所有嵌套 JAR 需要在运行时解压。这是因为 Spring Boot 的可执行 JAR 使用了嵌套 JAR 加载机制,某些 JAR(特别是使用ClassLoader.getResource()的库)需要被解压后才能正常工作。
4.5.2 CAS 6.6.x / 7.3.x 的 bootJar 配置
groovy
// CAS 6.6.x / 7.3.x - springboot.gradle
bootJar {
def executable = project.hasProperty("executable")
&& Boolean.valueOf(project.getProperty("executable"))
if (executable) {
logger.info "Including launch script for executable JAR artifact"
launchScript()
} else {
logger.info "JAR artifact is not marked as an executable"
}
archiveFileName = "cas.jar"
archiveBaseName = "cas"
entryCompression = ZipEntryCompression.STORED
}1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
配置详解:
- launchScript(): 当
executable=true(在gradle.properties中配置)时,Spring Boot 会在 JAR 文件的前面嵌入一个启动脚本。这使得 JAR 文件可以直接作为 shell 脚本执行:
bash
# 直接执行 JAR 文件(无需 java -jar)
./build/libs/cas.jar1
2
2
这在使用 init.d 或 systemd 管理 CAS 进程时非常方便。
entryCompression = ZipEntryCompression.STORED: 将 JAR 中的条目以"存储"模式(不压缩)写入。这看起来会增大文件体积,但实际上有两个好处:
- 加快启动速度(无需解压)
- 允许在运行时通过内存映射直接访问 JAR 中的资源
7.3.x 的 CycloneDX 集成:
groovy
// CAS 7.3.x - springboot.gradle
bootJar {
dependsOn cyclonedxBom
// ...
}1
2
3
4
5
2
3
4
5
7.3.x 的 bootJar 依赖于 cyclonedxBom 任务,确保在打包前生成 SBOM(软件物料清单)。
4.5.3 CAS 7.3.x 的 CycloneDX 集成
CAS 7.3.x 在 springboot.gradle 中配置了 CycloneDX SBOM 生成的依赖关系:
groovy
// CAS 7.3.x - springboot.gradle
def wireOverlayDependency = { Task t ->
["processTestResources", "compileJava", "compileGraalJava"]
.collect { tasks.findByName(it) }
.findAll { it != null }
.each { t.dependsOn(it) }
}
tasks.named("cyclonedxBom").configure { wireOverlayDependency(delegate) }
tasks.named("cyclonedxDirectBom").configure { wireOverlayDependency(delegate) }1
2
3
4
5
6
7
8
9
2
3
4
5
6
7
8
9
这段代码确保 CycloneDX 的 SBOM 生成任务在以下任务完成后执行:
processTestResources:测试资源处理完成compileJava:Java 编译完成compileGraalJava:GraalVM 原生镜像编译完成(如果存在)
这保证了 SBOM 中包含的是最终编译后的完整依赖信息。
第五章 tasks.gradle 自定义任务集
tasks.gradle 是 CAS Overlay 项目中最具特色的构建脚本之一。它定义了一系列专门为 CAS 开发和管理设计的自定义 Gradle 任务。这些任务覆盖了从开发调试到生产部署的完整生命周期。
5.1 任务体系总览
CAS 6.6.x 和 7.3.x 的 tasks.gradle 定义了以下自定义任务:
| 任务名 | 分组 | 描述 | 依赖 |
|---|---|---|---|
run | CAS | 在嵌入式容器中运行 CAS | build |
debug | CAS | 调试模式运行 CAS(端口 5005) | build |
setExecutable | CAS | 配置项目为可执行模式 | - |
executable | CAS | 以可执行模式运行 CAS | setExecutable, build |
showConfiguration | CAS | 显示依赖配置 | - |
allDependenciesInsight | build | 依赖洞察报告 | - |
allDependencies | build | 依赖树报告 | - |
casVersion | CAS | 显示当前 CAS 版本 | - |
springBootVersion | - | 显示 Spring Boot 版本 | - |
zip | CAS | 打包项目为 ZIP | - |
createKeystore | CAS | 创建 SSL 密钥库 | - |
unzipWAR | CAS | 解压 CAS WAR 文件 | build |
verifyRequiredJavaVersion | - | 验证 Java 版本 | - |
copyCasConfiguration | CAS | 复制配置到 /etc/cas/config | - |
unzip | CAS | 解压 CAS 资源 JAR | unzipWAR |
downloadShell | Shell | 下载 CAS Shell JAR | - |
runShell | Shell | 运行 CAS Shell | downloadShell |
debugShell | Shell | 调试模式运行 CAS Shell | downloadShell |
listTemplateViews | CAS | 列出所有 Thymeleaf 模板 | unzip |
getResource | CAS | 从 CAS JAR 提取资源到 overlay | unzip |
createTheme | CAS | 创建自定义主题目录结构 | - |
validateConfiguration | CAS | 验证配置文件正确性 | - |
exportConfigMetadata | CAS | 导出 CAS 配置元数据 | - |
duct | CAS | 测试 Ticket Registry 功能 | - |
5.2 createKeystore:自动生成 SSL 密钥库
groovy
// tasks.gradle
task createKeystore(group: "CAS", description: "Create CAS keystore") {
def dn = "CN=cas.example.org,OU=Example,OU=Org,C=US"
if (project.hasProperty("certificateDn")) {
dn = project.getProperty("certificateDn")
}
def subjectAltName = "dns:example.org,dns:localhost,ip:127.0.0.1"
if (project.hasProperty("certificateSubAltName")) {
subjectAltName = project.getProperty("certificateSubAltName")
}
doFirst {
def certDir = project.getProperty("certDir")
def serverKeyStore = project.getProperty("serverKeystore")
def exportedServerCert = project.getProperty("exportedServerCert")
def storeType = project.getProperty("storeType")
def keystorePath = "$certDir/$serverKeyStore"
def serverCert = "$certDir/$exportedServerCert"
mkdir certDir
logger.info "Generating keystore for CAS with DN ${dn}"
exec {
workingDir "."
commandLine "keytool", "-genkeypair", "-alias", "cas",
"-keyalg", "RSA",
"-keypass", "changeit", "-storepass", "changeit",
"-keystore", keystorePath,
"-dname", dn, "-ext", "SAN=${subjectAltName}",
"-storetype", storeType
}
logger.info "Exporting cert from keystore..."
exec {
workingDir "."
commandLine "keytool", "-exportcert", "-alias", "cas",
"-storepass", "changeit", "-keystore", keystorePath,
"-file", serverCert
}
logger.info "Import $serverCert into your Java truststore"
}
}1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
任务解析:
- 可配置的证书 DN: 默认使用
CN=cas.example.org,可通过-PcertificateDn覆盖。 - Subject Alternative Name: 默认包含
example.org、localhost和127.0.0.1,可通过-PcertificateSubAltName覆盖。 - 密钥库类型: 使用 PKCS12 格式(由
storeType属性控制),这是 Java 9+ 的推荐格式。 - 两步操作: 先生成密钥对,再导出证书。导出的证书需要导入到 Java 信任库中。
使用示例:
bash
# 使用默认参数创建密钥库
./gradlew createKeystore
# 自定义证书 DN 和 SAN
./gradlew createKeystore \
-PcertificateDn="CN=cas.mycompany.com,OU=IT,O=MyCompany,C=CN" \
-PcertificateSubAltName="dns:cas.mycompany.com,dns:localhost,ip:127.0.0.1"
# 导入证书到 Java 信任库
sudo keytool -importcert -alias cas \
-file /etc/cas/cas.crt \
-keystore $JAVA_HOME/lib/security/cacerts \
-storepass changeit1
2
3
4
5
6
7
8
9
10
11
12
13
2
3
4
5
6
7
8
9
10
11
12
13
5.3 exportConfigMetadata:导出 CAS 配置元数据
groovy
// tasks.gradle
task exportConfigMetadata(group: "CAS", description: "Export collection of CAS properties") {
def file = new File(project.rootDir, 'config-metadata.properties')
def queryType = ConfigurationMetadataCatalogQuery.QueryTypes.CAS
if (project.hasProperty("queryType")) {
queryType = ConfigurationMetadataCatalogQuery.QueryTypes.valueOf(
project.findProperty("queryType"))
}
doLast {
file.withWriter('utf-8') { writer ->
def props = CasConfigurationMetadataCatalog.query(
ConfigurationMetadataCatalogQuery.builder()
.queryType(queryType)
.build())
.properties()
props.each { property ->
writer.writeLine("# Type: ${property.type}")
writer.writeLine("# Module: ${property.module}")
writer.writeLine("# Owner: ${property.owner}")
if (property.deprecationLevel != null) {
writer.writeLine("# This setting is deprecated with "
+ "a severity level of ${property.deprecationLevel}.")
if (property.deprecationReason != null) {
writer.writeLine("# because ${property.deprecationReason}")
}
}
writer.writeLine("#")
def description = property.description.replace("\n", "\n# ")
description = org.apache.commons.text.WordUtils.wrap(
description, 70, "\n# ", true)
writer.writeLine("# ${description}")
writer.writeLine("#")
writer.writeLine("# ${property.name}: ${property.defaultValue}")
writer.writeLine("")
}
}
println "Configuration metadata is available at ${file.absolutePath}"
}
}1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
任务解析:
这个任务利用 CAS 的 CasConfigurationMetadataCatalog API,查询所有可用的配置属性,并将其导出为格式化的 Properties 文件。每个属性包含以下元信息:
- Type: 属性的 Java 类型
- Module: 所属的 CAS 模块
- Owner: 属性的所有者
- Deprecation: 是否已弃用及弃用原因
- Description: 属性的详细描述
- Default Value: 默认值
使用示例:
bash
# 导出所有 CAS 配置属性
./gradlew exportConfigMetadata
# 只导出特定类型的配置
./gradlew exportConfigMetadata -PqueryType=ACCEPTABLE
# 查看生成的文件
cat config-metadata.properties1
2
3
4
5
6
7
8
2
3
4
5
6
7
8
输出示例:
properties
# Type: java.lang.String
# Module: cas-server-core
# Owner: Apereo
#
# Name of the CAS server.
# This setting is used to identify the CAS server.
#
# cas.server.name: https://localhost:8443
# Type: java.lang.Boolean
# Module: cas-server-core-authentication
# Owner: Apereo
#
# This setting is deprecated with a severity level of ERROR.
# because The authentication throttling subsystem has been entirely
# redesigned and replaced.
# Replace with: cas.authn.throttle.core.enabled
#
# cas.authn.failure.throttle.enabled: false1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
5.4 listTemplateViews:列出所有 Thymeleaf 模板
groovy
// tasks.gradle
task listTemplateViews(group: "CAS", description: "List all CAS views") {
dependsOn unzip
def templateViews = fileTree(explodedResourcesDir).matching {
include "**/*.html"
}
.collect {
return it.path.replace(explodedResourcesDir, "")
}
.toSorted()
doFirst {
templateViews.each { println it }
}
}1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
任务解析:
这个任务依赖于 unzip 任务(先解压 CAS WAR 和资源 JAR),然后扫描所有 HTML 文件并按路径排序输出。它帮助开发者了解 CAS 提供了哪些可覆盖的 Thymeleaf 模板。
使用示例:
bash
# 列出所有模板
./gradlew listTemplateViews
# 输出示例:
# /casLoginView.html
# /casLogoutView.html
# /fragments/casHeadFragment.html
# /fragments/casFooterFragment.html
# /fragments/loginForm.html
# /fragments/loginMessages.html
# /error/error.html
# /oauth/confirmAccess.html
# /oauth/authorizeRequest.html
# /pm/changePassword.html
# /pm/unlockAccount.html
# ...1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
与 getResource 任务配合使用:
bash
# 1. 查看有哪些模板
./gradlew listTemplateViews
# 2. 提取特定模板到 overlay 项目
./gradlew getResource -PresourceName=casLoginView1
2
3
4
5
2
3
4
5
5.5 getResource:从 CAS JAR 提取资源到 Overlay
groovy
// tasks.gradle
task getResource(group: "CAS", description: "Fetch a CAS resource and move it into the overlay") {
dependsOn unzip
def resourceName = project.providers.gradleProperty("resourceName").getOrNull()
def resourcesDirectory = fileTree(explodedResourcesDir)
def projectDirectory = projectDir
doFirst {
def results = resourcesDirectory.matching {
include "**/${resourceName}.*"
include "**/${resourceName}"
}
if (results.isEmpty()) {
println "No resources could be found matching ${resourceName}"
return
}
if (results.size() > 1) {
println "Multiple resources found matching ${resourceName}:\n"
results.each {
println "\t-" + it.path.replace(explodedResourcesDir, "")
}
println "\nNarrow down your search criteria and try again."
return
}
def fromFile = explodedResourcesDir
def resourcesDir = "src/main/resources"
new File(resourcesDir).mkdir()
def resourceFile = results[0].canonicalPath
def toResourceFile = new File("${projectDirectory}",
resourceFile.replace(fromFile, resourcesDir))
toResourceFile.getParentFile().mkdirs()
Files.copy(Paths.get(resourceFile),
Paths.get(toResourceFile.absolutePath),
StandardCopyOption.REPLACE_EXISTING)
println "Copied file ${resourceFile} to ${toResourceFile}"
}
}1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
任务解析:
- 模糊匹配: 使用
**/${resourceName}.*和**/${resourceName}两种模式进行匹配,支持带扩展名和不带扩展名的搜索。 - 唯一性检查: 如果找到多个匹配结果,会列出所有匹配项并提示用户缩小搜索范围。
- 目录结构保留: 复制资源时会保留原始的目录结构。
- 幂等操作: 使用
REPLACE_EXISTING选项,可以安全地重复执行。
使用示例:
bash
# 提取登录页面模板
./gradlew getResource -PresourceName=casLoginView
# 提取 CAS 标准消息文件
./gradlew getResource -PresourceName=messages
# 提取特定主题的 CSS
./gradlew getResource -PresourceName=cas.css1
2
3
4
5
6
7
8
2
3
4
5
6
7
8
5.6 createTheme:创建自定义主题目录结构
groovy
// tasks.gradle
task createTheme(group: "CAS", description: "Create theme directory structure in the overlay") {
def theme = project.providers.gradleProperty("theme").getOrNull()
doFirst {
def builder = new FileTreeBuilder()
new File("src/main/resources/${theme}.properties").delete()
builder.src {
main {
resources {
"static" {
themes {
"${theme}" {
css {
'cas.css'('')
}
js {
'cas.js'('')
}
images {
'.ignore'('')
}
}
}
}
templates {
"${theme}" {
fragments {
}
}
}
"${theme}.properties"("""cas.standard.css.file=/themes/${theme}/css/cas.css
cas.standard.js.file=/themes/${theme}/js/cas.js
""")
}
}
}
}
}1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
任务解析:
这个任务使用 Groovy 的 FileTreeBuilder 创建完整的主题目录结构:
src/main/resources/
├── static/themes/{themeName}/
│ ├── css/cas.css # 自定义样式
│ ├── js/cas.js # 自定义脚本
│ └── images/.ignore # 图片目录占位
├── templates/{themeName}/
│ └── fragments/ # 模板片段目录
└── {themeName}.properties # 主题属性配置1
2
3
4
5
6
7
8
2
3
4
5
6
7
8
同时生成主题属性文件,将 CSS 和 JS 文件映射到正确的路径。
使用示例:
bash
# 创建名为 "mytheme" 的主题
./gradlew createTheme -Ptheme=mytheme
# 创建后编辑主题文件
# 1. 编辑 src/main/resources/static/themes/mytheme/css/cas.css
# 2. 编辑 src/main/resources/static/themes/mytheme/js/cas.js
# 3. 在 application.yml 中激活主题:
# cas.theme.default-theme-name: mytheme1
2
3
4
5
6
7
8
2
3
4
5
6
7
8
5.7 downloadShell / runShell:CAS Shell 管理
CAS Shell 是 CAS 提供的交互式管理工具,允许通过命令行与 CAS 服务器交互。
5.7.1 downloadShell 任务
groovy
// CAS 7.3.x - tasks.gradle
task downloadShell(group: "Shell", description: "Download CAS shell jar",
type: Download) {
def shellDir = project.providers.gradleProperty("shellDir").get()
def casVersion = project.providers.gradleProperty("cas.version").get()
def downloadFile
if (casVersion.contains("-SNAPSHOT")) {
// SNAPSHOT 版本:从 Maven 仓库获取最新快照
def snapshotDir = "https://central.sonatype.com/repository/maven-snapshots/"
+ "org/apereo/cas/cas-server-support-shell/${casVersion}"
def metaUrl = "${snapshotDir}/maven-metadata.xml"
def xml = new XmlSlurper().parse(metaUrl.toURL().openStream())
def sv = xml.versioning.snapshotVersions.snapshotVersion.findAll {
it.extension.text() == "jar" && it.classifier.text() == ""
}.sort { a, b -> a.updated.text() <=> b.updated.text() }.last()
def stamped = sv.value.text()
downloadFile = "${snapshotDir}/cas-server-support-shell-${stamped}.jar"
} else {
// 正式版本:从 GitHub Releases 下载
downloadFile = "https://github.com/apereo/cas/releases/download/"
+ "v${casVersion}/cas-server-support-shell-${casVersion}.jar"
}
new File("${shellDir}").mkdir()
logger.info "Downloading file: ${downloadFile} into ${shellDir}"
src downloadFile
dest new File("${shellDir}", "cas-server-support-shell-${casVersion}.jar")
overwrite false
}1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
任务解析:
- 版本感知下载: 区分 SNAPSHOT 版本和正式版本,使用不同的下载源。
- SNAPSHOT 处理: 对于 SNAPSHOT 版本,解析
maven-metadata.xml获取最新的时间戳版本。 - 幂等下载:
overwrite = false确保不会重复下载已有的文件。
5.7.2 runShell / debugShell 任务
groovy
// tasks.gradle
task runShell(group: "Shell", description: "Run the CAS shell") {
dependsOn downloadShell
def shellDir = project.providers.gradleProperty("shellDir").get()
def casVersion = project.providers.gradleProperty("cas.version").get()
doLast {
println "Run the following command to launch the shell:\n\t"
println "java -jar ${shellDir}/cas-server-support-shell-${casVersion}.jar"
}
}
task debugShell(group: "Shell",
description: "Run the CAS shell with debug options, wait for debugger on port 5005") {
dependsOn downloadShell
def casVersion = project.providers.gradleProperty("cas.version").get()
def shellDir = project.providers.gradleProperty("shellDir").get()
doLast {
println """
Run the following command to launch the shell:\n\t
java -Xrunjdwp:transport=dt_socket,address=5000,server=y,suspend=y
-jar ${shellDir}/cas-server-support-shell-${casVersion}.jar
"""
}
}1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
使用示例:
bash
# 下载并运行 CAS Shell
./gradlew runShell
java -jar build/libs/cas-server-support-shell-7.3.4.jar
# 调试模式运行
./gradlew debugShell
# 然后在 IDE 中连接到 5000 端口1
2
3
4
5
6
7
2
3
4
5
6
7
5.8 validateConfiguration:验证配置文件正确性
groovy
// tasks.gradle
def skipValidation = project.hasProperty("validate")
&& project.property("validate").equals("false")
if (!skipValidation) {
task validateConfiguration(type: Copy, group: "CAS",
description: "Validate CAS configuration") {
def file = new File("${projectDir}/src/main/resources/application.properties")
if (file.exists()) {
throw new GradleException(
"This overlay project is overriding a CAS-supplied configuration "
+ "file at ${file.path}. Overriding this file will disable all "
+ "default CAS settings... It's best to move your configuration "
+ "inside an application.yml file... To disable this validation "
+ "step, run the build with -Pvalidate=false.");
}
}
processResources.dependsOn(validateConfiguration)
}1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
设计意图:
这是一个"防御性编程"的典型案例。CAS 内置了大量默认配置(数百个属性),这些配置被打包在 CAS 核心 JAR 中的 application.properties 文件里。如果 Overlay 项目中存在同名的 application.properties,它会完全覆盖 CAS 的默认配置,导致大量功能失效。
为什么使用 type: Copy? 这个任务实际上并不执行复制操作,它只是借用 Copy 任务的类型来获得 Gradle 的增量构建支持。任务的真正逻辑在配置阶段(非 doFirst/doLast)执行,这意味着它会在每次构建配置时都进行检查。
跳过验证:
bash
# 如果确实需要使用 application.properties,可以跳过验证
./gradlew build -Pvalidate=false1
2
2
5.9 任务依赖关系设计
CAS tasks.gradle 中的任务之间形成了精心设计的依赖链:
build
└── unzipWAR (dependsOn build)
└── unzip (dependsOn unzipWAR)
├── listTemplateViews (dependsOn unzip)
└── getResource (dependsOn unzip)
downloadShell
├── runShell (dependsOn downloadShell)
└── debugShell (dependsOn downloadShell)
setExecutable
└── executable (dependsOn setExecutable, build)
validateConfiguration
└── processResources (dependsOn validateConfiguration)
cyclonedxBom (7.3.x)
├── dependsOn compileJava
├── dependsOn processTestResources
└── dependsOn compileGraalJava
└── bootJar (dependsOn cyclonedxBom)1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
设计原则:
- 最小化构建范围: 每个任务只依赖其真正需要的前置任务。例如
listTemplateViews只需要unzip,不需要完整的build。 - 增量构建友好: 任务依赖链中的每个节点都可以独立执行和缓存。
- 按需加载: 使用
project.providers.gradleProperty()延迟解析属性值,避免在配置阶段触发不必要的计算。
第六章 Jib 插件配置(7.3.x)
6.1 Jib 概述
Jib 是 Google 开源的 Java 容器化工具,它能够将 Java 应用直接构建为 OCI 标准的 Docker 镜像,而无需编写 Dockerfile 或安装 Docker 守护进程。CAS 7.3.x 集成了 Jib Gradle Plugin,提供了三种镜像构建方式:
| 任务名 | 用途 | 是否需要 Docker |
|---|---|---|
jibDockerBuild | 构建镜像到本地 Docker Daemon | 是 |
jibBuildTar | 构建镜像为 tar 文件 | 否 |
jib | 直接推送到容器仓库 | 否 |
6.2 OCI 标准镜像构建
Jib 构建的镜像是 OCI(Open Container Initiative)标准的容器镜像,兼容 Docker、Kubernetes、Podman 等所有主流容器运行时。
6.3 多平台镜像支持
groovy
// CAS 7.3.x - build.gradle
def imagePlatforms = project.dockerImagePlatform.split(",")
jib {
from {
image = project.baseDockerImage
platforms {
imagePlatforms.each {
def given = it.split(":")
platform {
architecture = given[0]
os = given[1]
}
}
}
}
}1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
配置解析:
dockerImagePlatform 属性支持逗号分隔的多平台配置:
properties
# gradle.properties - 单平台
dockerImagePlatform=amd64:linux
# 多平台(需要同时构建 x86_64 和 ARM64 镜像)
dockerImagePlatform=amd64:linux,arm64:linux1
2
3
4
5
2
3
4
5
多平台构建的注意事项:
- 构建时间: 多平台构建会为每个平台构建独立的镜像层,构建时间成倍增加。
- 基础镜像: 基础镜像(
baseDockerImage)必须支持所有指定的平台。 - QEMU 依赖: 在 x86_64 机器上构建 ARM64 镜像需要 QEMU 模拟器。
6.4 from / to / container 配置
groovy
// CAS 7.3.x - build.gradle
jib {
from {
image = project.baseDockerImage
// platforms 配置见上文
}
to {
image = "${project.'containerImageOrg'}/${project.'containerImageName'}:${project.version}"
credHelper = "osxkeychain"
if (dockerUsername != null && dockerPassword != null) {
auth {
username = "${dockerUsername}"
password = "${dockerPassword}"
}
}
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'
}
}1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
配置详解:
from 块(基础镜像配置)
properties
# gradle.properties
baseDockerImage=azul/zulu-openjdk:211
2
2
使用 Azul Zulu OpenJDK 21 作为基础镜像。选择这个镜像的原因:
- Azul Zulu 是经过 TCK 认证的 OpenJDK 发行版
- 镜像体积相对较小
- 提供了良好的 Alpine 和标准版本选择
to 块(目标镜像配置)
- image: 最终镜像的完整坐标,格式为
{org}/{name}:{version} - credHelper: 使用 macOS Keychain 存储凭据(仅限 macOS)
- auth: 通过系统属性传递的用户名密码认证
- tags: 镜像标签列表
container 块(容器运行时配置)
- creationTime: 使用当前时间戳而非构建时间,确保镜像可重复构建
- entrypoint: 使用自定义的入口脚本
/docker/entrypoint.sh - ports: 声明容器暴露的端口列表
- labels: OCI 标准标签,用于镜像元数据管理
- workingDirectory: CAS 工作目录
6.5 extraDirectories 资源注入
groovy
// CAS 7.3.x - build.gradle
jib {
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'
]
}
}1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
资源注入策略:
| 源目录 | 目标路径 | 用途 |
|---|---|---|
src/main/jib/ | / | Jib 专用资源(如 entrypoint.sh) |
etc/cas/ | /etc/cas | CAS 外部配置文件 |
build/libs/ | /docker/cas/war | 构建产物(CAS JAR) |
permissions 配置: 确保入口脚本具有可执行权限(755 = rwxr-xr-x)。
6.6 配置缓存兼容性处理
groovy
// CAS 7.3.x - build.gradle
def configurationCacheRequested = services.get(BuildFeatures)
.configurationCache.requested.getOrElse(true)
['jibDockerBuild', 'jibBuildTar', 'jib'].each { taskName ->
getTasksByName(taskName, true).each(it -> {
it.notCompatibleWithConfigurationCache(
"Jib is not compatible with configuration cache");
it.enabled = !configurationCacheRequested
})
}
jib {
if (configurationCacheRequested) {
out.withStyle(Style.Info).println(
"You are seeing this message because the Gradle configuration "
+ "cache is turned on")
out.withStyle(Style.Info).println(
"Running Jib tasks to produce Docker images will require the "
+ "command-line option: --no-configuration-cache")
out.withStyle(Style.Info).println(
"Jib does not support the Gradle configuration cache")
out.withStyle(Style.Info).println("Jib tasks are disabled.")
}
}1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
设计意图:
Jib 插件目前不支持 Gradle 配置缓存(这是一个已知的上游问题,参见 jib#3132)。CAS 7.3.x 的处理方式非常优雅:
- 检测配置缓存是否被请求
- 如果开启配置缓存,标记 Jib 任务为不兼容,并自动禁用
- 输出清晰的提示信息,告诉用户如何解决
使用方式:
bash
# 正常构建(配置缓存关闭时,Jib 可用)
./gradlew jibDockerBuild
# 配置缓存开启时,需要显式禁用
./gradlew jibDockerBuild --no-configuration-cache1
2
3
4
5
2
3
4
5
第七章 CycloneDX SBOM 插件(7.3.x)
7.1 SBOM 概述
SBOM(Software Bill of Materials,软件物料清单)是近年来软件供应链安全领域的核心概念。它记录了软件产品中包含的所有组件、库和依赖项的详细信息,包括版本号、许可证、来源等。SBOM 在以下场景中至关重要:
- 安全漏洞扫描: 当某个依赖被发现存在安全漏洞时,SBOM 可以快速确定哪些产品受到影响。
- 许可证合规: 确保软件产品不包含与许可证冲突的依赖。
- 供应链透明度: 满足政府和行业监管对软件供应链透明度的要求。
- 版本追踪: 精确追踪每个依赖的版本变化历史。
7.2 CycloneDX 插件配置
CAS 7.3.x 使用 CycloneDX Gradle Plugin 生成 SBOM:
groovy
// CAS 7.3.x - build.gradle
apply plugin: "org.cyclonedx.bom"1
2
2
properties
# gradle.properties
gradleCyclonePluginVersion=3.2.01
2
2
7.3 集成到 bootJar 流程
CycloneDX 的 SBOM 生成被集成到 CAS 的打包流程中:
groovy
// CAS 7.3.x - springboot.gradle
def wireOverlayDependency = { Task t ->
["processTestResources", "compileJava", "compileGraalJava"]
.collect { tasks.findByName(it) }
.findAll { it != null }
.each { t.dependsOn(it) }
}
tasks.named("cyclonedxBom").configure { wireOverlayDependency(delegate) }
tasks.named("cyclonedxDirectBom").configure { wireOverlayDependency(delegate) }
bootJar {
dependsOn cyclonedxBom
// ...
}1
2
3
4
5
6
7
8
9
10
11
12
13
14
2
3
4
5
6
7
8
9
10
11
12
13
14
依赖链:
compileJava → cyclonedxBom → bootJar
processTestResources → cyclonedxBom → bootJar
compileGraalJava → cyclonedxBom → bootJar1
2
3
2
3
这确保了 SBOM 是在所有编译任务完成后生成的,包含了最终的完整依赖信息。
7.4 SBOM 格式与用途
CycloneDX 插件默认生成 JSON 格式的 SBOM 文件,存放在 build/reports/cyclonedxBom/ 目录下:
build/reports/
└── cyclonedxBom/
├── bom.json # 完整 SBOM(包含传递依赖)
└── directBom.json # 直接依赖 SBOM1
2
3
4
2
3
4
SBOM 文件结构示例:
json
{
"bomFormat": "CycloneDX",
"specVersion": "1.5",
"serialNumber": "urn:uuid:xxxxx",
"version": 1,
"metadata": {
"timestamp": "2025-01-01T00:00:00Z",
"tools": [{ "name": "CycloneDX Gradle Plugin", "version": "3.2.0" }],
"component": {
"name": "cas-overlay",
"version": "7.3.4",
"type": "application"
}
},
"components": [
{
"name": "cas-server-core",
"version": "7.3.4",
"purl": "pkg:maven/org.apereo.cas/cas-server-core@7.3.4",
"type": "library"
}
]
}1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
SBOM 的实际用途:
- 与安全扫描工具集成: 将 SBOM 导入到 Grype、Trivy、Snyk 等工具中进行漏洞扫描。
- CI/CD 流水线集成: 在构建流水线中自动生成 SBOM 并归档。
- 合规审计: 作为软件供应链合规审计的证据材料。
第八章 Docker 插件(7.3.x)
8.1 插件概述
除了 Jib 之外,CAS 7.3.x 还集成了 bmuschko 的 Docker Remote API 插件,提供了基于 Dockerfile 的传统容器化构建方式。与 Jib 的"Dockerless"方式不同,Docker 插件需要本地安装 Docker 守护进程。
groovy
// CAS 7.3.x - build.gradle
apply plugin: "com.bmuschko.docker-remote-api"1
2
2
8.2 casBuildDockerImage 任务
groovy
// CAS 7.3.x - build.gradle
import com.bmuschko.gradle.docker.tasks.image.*
tasks.register("casBuildDockerImage", DockerBuildImage) {
dependsOn("build")
def imageTag = "${project.'cas.version'}"
inputDir = project.projectDir
images.add("apereo/cas:${imageTag}${imageTagPostFix}")
images.add("apereo/cas:latest${imageTagPostFix}")
if (dockerUsername != null && dockerPassword != null) {
username = dockerUsername
password = dockerPassword
}
doLast {
out.withStyle(Style.Success).println("Built CAS images successfully.")
}
}1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
配置解析:
- dependsOn("build"): 确保在构建镜像前完成 JAR 打包。
- inputDir: Docker 构建上下文目录(项目根目录),Dockerfile 应位于此目录下。
- images: 生成的镜像标签列表,包含版本标签和
latest标签。 - 认证: 支持通过系统属性传递 Docker 仓库认证信息。
使用示例:
bash
# 构建镜像
./gradlew casBuildDockerImage
# 带认证的构建
./gradlew casBuildDockerImage \
-DdockerUsername=myuser \
-DdockerPassword=mypassword
# 带自定义标签后缀
./gradlew casBuildDockerImage -DdockerImageTagPostfix=-dev1
2
3
4
5
6
7
8
9
10
2
3
4
5
6
7
8
9
10
8.3 casPushDockerImage 任务
groovy
// CAS 7.3.x - build.gradle
tasks.register("casPushDockerImage", DockerPushImage) {
dependsOn("casBuildDockerImage")
def imageTag = "${project.'cas.version'}"
images.add("apereo/cas:${imageTag}${imageTagPostFix}")
images.add("apereo/cas:latest${imageTagPostFix}")
if (dockerUsername != null && dockerPassword != null) {
username = dockerUsername
password = dockerPassword
}
doLast {
out.withStyle(Style.Success).println("Pushed CAS images successfully.")
}
}1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
任务依赖: casPushDockerImage 依赖于 casBuildDockerImage,确保先构建再推送。
使用示例:
bash
# 构建并推送镜像
./gradlew casPushDockerImage \
-DdockerUsername=myuser \
-DdockerPassword=mypassword1
2
3
4
2
3
4
8.4 镜像标签管理
CAS 7.3.x 的 Docker 镜像标签策略:
| 标签 | 格式 | 用途 |
|---|---|---|
| 版本标签 | apereo/cas:7.3.4 | 精确版本标识 |
| Latest 标签 | apereo/cas:latest | 最新版本标识 |
| 自定义后缀 | apereo/cas:7.3.4-dev | 环境/分支区分 |
标签后缀通过 dockerImageTagPostfix 系统属性控制:
bash
# 开发环境标签
./gradlew casBuildDockerImage -DdockerImageTagPostfix=-dev
# 生成: apereo/cas:7.3.4-dev, apereo/cas:latest-dev
# 测试环境标签
./gradlew casBuildDockerImage -DdockerImageTagPostfix=-staging
# 生成: apereo/cas:7.3.4-staging, apereo/cas:latest-staging1
2
3
4
5
6
7
2
3
4
5
6
7
8.5 认证配置
CAS 7.3.x 支持两种 Docker 认证方式:
方式一:系统属性传递(适用于 CI/CD)
bash
./gradlew casPushDockerImage \
-DdockerUsername=myuser \
-DdockerPassword=mypassword1
2
3
2
3
方式二:Docker 凭据辅助(适用于本地开发)
groovy
// Jib 配置中的 credHelper
jib {
to {
credHelper = "osxkeychain" // macOS Keychain
// credHelper = "osxkeychain" | "ecr-login" | "gcr"
}
}1
2
3
4
5
6
7
2
3
4
5
6
7
安全提示: 不要将 Docker 认证信息硬编码在构建脚本或属性文件中。推荐使用 CI/CD 平台的凭据管理功能(如 GitHub Secrets、GitLab CI Variables、Jenkins Credentials)。
8.6 Jib vs Docker 插件对比
| 特性 | Jib 插件 | Docker 插件 |
|---|---|---|
| 是否需要 Docker | 否(jibBuildTar/jib) | 是 |
| 构建速度 | 快(无需 Docker 守护进程) | 较慢(需要 Docker 构建) |
| 多平台支持 | 原生支持 | 需要 buildx |
| 镜像分层优化 | 自动优化 | 依赖 Dockerfile |
| 缓存机制 | Gradle 缓存 + Jib 缓存 | Docker 层缓存 |
| 适用场景 | CI/CD 流水线、无 Docker 环境 | 需要自定义 Dockerfile 的场景 |
第九章 构建优化技巧
9.1 配置缓存(Configuration Cache)
配置缓存是 Gradle 提供的最强大的性能优化特性之一。它通过缓存构建的配置阶段结果,使得后续构建可以跳过配置阶段,直接进入执行阶段。
9.1.1 配置缓存的工作原理
首次构建:
配置阶段(解析 build.gradle、计算任务图)→ 执行阶段 → 缓存配置结果
后续构建:
读取缓存 → 验证缓存有效性 → 执行阶段1
2
3
4
5
2
3
4
5
配置缓存的效果在大型项目中尤为显著,CAS Overlay 项目虽然不是多模块项目,但由于 tasks.gradle 中包含大量自定义任务和条件逻辑,配置缓存仍然能带来明显的性能提升。
9.1.2 CAS 项目中的配置缓存状态
CAS Overlay 项目目前默认关闭配置缓存:
properties
# gradle.properties(5.3.x / 6.6.x / 7.3.x 通用)
org.gradle.unsafe.configuration-cache=false
org.gradle.unsafe.configuration-cache-problems=warn1
2
3
2
3
使用 unsafe 前缀是因为配置缓存在 Gradle 7.x 中仍是实验性功能。configuration-cache-problems=warn 意味着当检测到不兼容的 API 使用时会发出警告而非报错。
9.1.3 配置缓存兼容性问题
CAS 构建脚本中与配置缓存不兼容的模式包括:
System.properties访问:bootRun中的systemProperties = System.properties在配置阶段读取了外部状态。project.hasProperty()条件逻辑: 大量使用属性检查来决定任务行为。doFirst中的配置阶段操作:validateConfiguration在配置阶段执行了文件检查。- Jib 插件: 已知不兼容配置缓存。
9.1.4 逐步启用配置缓存
如果希望在 CAS Overlay 项目中启用配置缓存,建议按以下步骤进行:
步骤 1: 在 gradle.properties 中开启配置缓存
properties
org.gradle.configuration-cache=true1
步骤 2: 运行构建并观察警告
bash
./gradlew build --configuration-cache1
步骤 3: 根据警告信息逐步修复不兼容的代码
步骤 4: 对于无法修复的任务,使用 notCompatibleWithConfigurationCache 标记
groovy
tasks.named("someTask") {
notCompatibleWithConfigurationCache("Reason for incompatibility")
}1
2
3
2
3
9.2 增量构建
增量构建是 Gradle 的默认行为,它通过检查输入和输出的变化来决定是否需要重新执行任务。
9.2.1 增量构建的工作原理
对于声明了输入和输出的任务,Gradle 会:
- 计算所有输入的指纹(基于文件内容、路径等)
- 与上次构建的指纹进行比较
- 如果输入没有变化且输出存在,跳过任务执行
- 如果输入有变化,只重新执行受影响的部分
9.2.2 CAS 项目中的增量构建
CAS Overlay 项目中,以下任务天然支持增量构建:
compileJava:只重新编译修改过的 Java 文件processResources:只复制修改过的资源文件bootJar:只在类路径变化时重新打包
自定义任务的增量构建:
createKeystore 任务可以通过添加输入输出声明来支持增量构建:
groovy
task createKeystore(group: "CAS", description: "Create CAS keystore") {
// 声明输入
inputs.property("dn", project.findProperty("certificateDn") ?: "CN=cas.example.org")
inputs.property("subjectAltName", project.findProperty("certificateSubAltName")
?: "dns:example.org,dns:localhost,ip:127.0.0.1")
// 声明输出
outputs.file("${project.getProperty('certDir')}/${project.getProperty('serverKeystore')}")
outputs.file("${project.getProperty('certDir')}/${project.getProperty('exportedServerCert')}")
doFirst {
// 只有当输入变化或输出不存在时才执行
// ...
}
}1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
2
3
4
5
6
7
8
9
10
11
12
13
14
15
9.3 并行构建
CAS Overlay 项目在 gradle.properties 中启用了并行构建:
properties
org.gradle.parallel=true1
并行构建使得 Gradle 可以同时执行多个独立的任务。在 CAS Overlay 项目中,以下任务可以并行执行:
compileJava和processResources(如果它们没有依赖关系)- 多个独立的测试任务
- 多个自定义任务(如
casVersion和springBootVersion)
并行构建的注意事项:
- 内存消耗: 并行构建会增加内存使用量,需要确保
org.gradle.jvmargs中的-Xmx值足够大。 - 线程安全: 自定义任务必须是线程安全的,避免使用共享的可变状态。
- I/O 瓶颈: 在磁盘 I/O 受限的环境中,并行构建可能不会带来明显的性能提升。
9.4 依赖缓存策略
CAS Overlay 项目配置了精细的依赖缓存策略:
groovy
// CAS 6.6.x / 7.3.x - build.gradle
configurations {
all {
resolutionStrategy {
cacheChangingModulesFor 0, "seconds"
cacheDynamicVersionsFor 0, "seconds"
preferProjectModules()
}
}
}1
2
3
4
5
6
7
8
9
10
2
3
4
5
6
7
8
9
10
策略解析:
cacheChangingModulesFor 0, "seconds": 对于changing版本(如 SNAPSHOT),不缓存,每次构建都检查远程仓库是否有更新。这确保了开发时始终使用最新的 SNAPSHOT 版本。cacheDynamicVersionsFor 0, "seconds": 对于动态版本(如1.0.+、latest.release),不缓存,每次都重新解析。preferProjectModules(): 当项目模块和外部依赖同时可用时,优先使用项目模块。这对于多模块 CAS 项目特别重要。
Gradle 构建缓存的层级:
┌─────────────────────────────────────┐
│ Local Build Cache │ ← org.gradle.caching=true
│ (~/.gradle/caches/build-cache/) │
├─────────────────────────────────────┤
│ Dependency Cache │ ← ~/.gradle/caches/modules-2/
│ (Maven Central, etc.) │
├─────────────────────────────────────┤
│ Configuration Cache │ ← org.gradle.configuration-cache
│ (配置阶段结果缓存) │
├─────────────────────────────────────┤
│ Artifact Transform Cache │ ← ~/.gradle/caches/transforms-*/
│ (依赖转换结果缓存) │
└─────────────────────────────────────┘1
2
3
4
5
6
7
8
9
10
11
12
13
2
3
4
5
6
7
8
9
10
11
12
13
9.5 cleanAllCache 任务(5.3.x)
CAS 5.3.x 提供了一个特殊的缓存清理任务:
groovy
// CAS 5.3.x - build.gradle
task cleanAllCache(type: Delete) {
delete fileTree(dir: "${System.getProperty('user.home')}/.gradle/caches")
delete 'build'
}
build.dependsOn cleanAllCache1
2
3
4
5
6
2
3
4
5
6
任务解析:
- 删除 Gradle 全局缓存: 清理
~/.gradle/caches/目录下的所有缓存文件。 - 删除项目构建目录: 清理项目本地的
build/目录。 - 绑定到 build 任务: 每次构建前都会执行缓存清理。
注意: 这个任务在 6.6.x 和 7.3.x 中被移除了。原因是每次构建都清理缓存会严重拖慢构建速度,违背了 Gradle 缓存机制的设计初衷。在 5.3.x 中使用这个任务通常是为了解决严重的依赖冲突问题,但这应该通过正确的依赖管理来解决,而非暴力清理缓存。
9.6 On-Demand 配置
properties
# gradle.properties
org.gradle.configureondemand=true1
2
2
按需配置(Configure on Demand)是 Gradle 的另一个性能优化特性。在多模块项目中,它只配置当前构建所需的项目模块,而非所有模块。虽然 CAS Overlay 通常是单模块项目,但启用此选项不会产生负面影响,且为未来可能的模块化扩展做好了准备。
9.7 Gradle Daemon 和 JVM 参数
properties
# gradle.properties
org.gradle.jvmargs=-Xms1024m -Xmx4048m -XX:TieredStopAtLevel=11
2
2
参数解析:
-Xms1024m: Gradle Daemon 的初始堆内存为 1GB。-Xmx4048m: Gradle Daemon 的最大堆内存为 4GB。CAS 项目的依赖解析和类路径计算需要较大的内存。-XX:TieredStopAtLevel=1: 限制 Gradle Daemon 自身的 JIT 编译层级。Gradle Daemon 是一个长期运行的进程,使用 C1 编译器(Tier 1)即可获得足够的性能,同时减少了 C2 编译的 CPU 开销。
9.8 构建优化效果总结
| 优化项 | 配置 | 效果 | 适用版本 |
|---|---|---|---|
| 构建缓存 | org.gradle.caching=true | 避免重复执行未变化的任务 | 5.3/6.6/7.3 |
| 并行构建 | org.gradle.parallel=true | 多任务并行执行 | 5.3/6.6/7.3 |
| 按需配置 | org.gradle.configureondemand=true | 减少配置阶段工作量 | 5.3/6.6/7.3 |
| 配置缓存 | org.gradle.configuration-cache=true | 跳过配置阶段 | 7.3(实验性) |
| Daemon JVM 优化 | -XX:TieredStopAtLevel=1 | 减少 JIT 编译开销 | 5.3/6.6/7.3 |
| 依赖缓存策略 | cacheChangingModulesFor 0 | SNAPSHOT 实时更新 | 6.6/7.3 |
| HTTP 超时优化 | connectionTimeout=60000 | 避免网络超时 | 7.3 |
第十章 版本迁移实战指南
10.1 从 5.3.x 迁移到 6.6.x
10.1.1 Gradle 版本升级
5.3.x 和 6.6.x 都使用 Gradle 7.5,因此 Gradle 本身无需升级。
10.1.2 Java 版本升级
properties
# 5.3.x
sourceCompatibility=1.8
targetCompatibility=1.8
# 6.6.x
sourceCompatibility=11
targetCompatibility=111
2
3
4
5
6
7
2
3
4
5
6
7
需要将所有 Java 源码升级到 Java 11 兼容。主要变化:
javax.*命名空间保持不变(Spring Boot 2.7.x 仍使用 Jakarta EE 8)- 可以使用 Java 11 的新特性(var、HTTP Client 等)
10.1.3 BOM 管理迁移
groovy
// 5.3.x → 6.6.x
// 移除 io.spring.dependency-management 插件
// 将 mavenBom 替换为 platform()
// 移除所有 dependencyManagement.dependencies 中的手动版本声明
// 移除所有 exclude 块,改用 configurations.all 全局排除1
2
3
4
5
2
3
4
5
10.1.4 构建脚本重组
groovy
// 将 bootRun 和 bootJar 配置从 build.gradle 移到 springboot.gradle
// 将自定义任务从 build.gradle 移到 tasks.gradle
// 在 build.gradle 中添加 apply from: 引用1
2
3
2
3
10.2 从 6.6.x 迁移到 7.3.x
10.2.1 Gradle 版本升级
properties
# gradle-wrapper.properties
# 从 gradle-7.5-bin.zip 升级到 gradle-9.1.0-bin.zip
distributionUrl=https\://mirrors.cloud.tencent.com/gradle/gradle-9.1.0-bin.zip1
2
3
2
3
10.2.2 Java 版本升级
properties
# 6.6.x → 7.3.x
sourceCompatibility=21
targetCompatibility=211
2
3
2
3
Java 21 的主要变化:
javax.*→jakarta.*命名空间迁移(Spring Boot 3.x 的要求)- Record 模式、Switch 表达式等新特性
- 虚拟线程(Virtual Threads)预览
10.2.3 Spring Boot 版本升级
properties
# 6.6.x → 7.3.x
springBootVersion=3.5.61
2
2
Spring Boot 3.x 的重大变化:
- 最低要求 Java 17
javax.*→jakarta.*命名空间迁移- Spring Security 配置变更
- Actuator 端点路径变更
10.2.4 BOM 管理迁移
groovy
// 6.6.x → 7.3.x
// 将 platform() 替换为 enforcedPlatform()(仅 CAS BOM)
// Spring Boot BOM 保持 platform()1
2
3
2
3
10.2.5 新增插件和配置
groovy
// settings.gradle 新增 Foojay Plugin
plugins {
id "org.gradle.toolchains.foojay-resolver-convention" version "1.0.0"
}
// build.gradle 新增容器化和 SBOM 插件
apply plugin: "org.cyclonedx.bom"
apply plugin: "com.google.cloud.tools.jib"
apply plugin: "com.bmuschko.docker-remote-api"1
2
3
4
5
6
7
8
9
2
3
4
5
6
7
8
9
第十一章 总结与最佳实践
11.1 核心要点回顾
通过对 CAS Overlay 5.3.x、6.6.x、7.3.x 三个版本的 Gradle 构建体系进行深度解析,我们可以总结出以下核心要点:
1. Overlay 模式的本质是"约定优于配置"
CAS Overlay 项目的目录布局、命名约定、构建逻辑都遵循严格的约定。理解这些约定是高效使用 CAS Overlay 的前提。
2. BOM 是依赖管理的基石
从 5.3.x 的 mavenBom 到 6.6.x 的 platform() 再到 7.3.x 的 enforcedPlatform(),BOM 管理策略的演进反映了 CAS 团队对依赖一致性要求的不断提升。
3. 构建脚本的模块化是可维护性的关键
6.6.x 开始将 springboot.gradle 和 tasks.gradle 从 build.gradle 中分离,这种模块化设计使得每个文件都有明确的职责边界。
4. 自定义任务体系是 CAS 开发效率的倍增器
tasks.gradle 中定义的任务覆盖了 CAS 开发的完整生命周期,从密钥库创建、模板提取到配置验证、元数据导出,每个任务都是 CAS 团队多年实践经验的结晶。
5. 容器化构建是现代部署的标准方式
7.3.x 同时集成了 Jib 和 Docker 两种容器化方案,兼顾了"Dockerless"构建和传统 Dockerfile 构建的需求。
11.2 最佳实践清单
- 始终使用 Gradle Wrapper: 不要依赖系统安装的 Gradle 版本。
- 集中管理版本号: 所有版本号放在
gradle.properties中,build.gradle通过属性引用。 - 使用
application.yml而非application.properties: 避免 CAS 默认配置被完全覆盖。 - 利用
getResource任务提取模板: 不要手动从 CAS JAR 中复制文件。 - 使用
createTheme任务创建主题: 确保主题目录结构符合 CAS 约定。 - 定期运行
exportConfigMetadata: 了解 CAS 提供的所有配置选项。 - 使用
enforcedPlatform锁定 CAS 依赖版本: 防止版本漂移。 - 在 CI/CD 中使用 Jib 构建镜像: 无需 Docker 守护进程,构建更快更可靠。
- 生成 SBOM 并归档: 满足软件供应链安全合规要求。
- 渐进式升级: 按照 5.3 → 6.6 → 7.3 的路径逐步升级,每次升级一个主版本。
11.3 展望
CAS 的 Gradle 构建体系仍在持续演进。未来的发展方向可能包括:
- Kotlin DSL 迁移: 随着 Gradle 对 Kotlin DSL 的支持日益成熟,CAS 可能会提供 Kotlin DSL 版本的构建脚本。
- 配置缓存全面启用: 随着 Jib 等插件对配置缓存的支持完善,CAS 可能会默认开启配置缓存。
- GraalVM 原生镜像: CAS 7.3.x 的
springboot.gradle中已经出现了compileGraalJava的引用,预示着对 GraalVM 原生镜像的支持正在推进。 - 更精细的 SBOM 集成: 随着软件供应链安全法规的完善,SBOM 生成可能会成为构建流程的强制环节。
附录 A:常用 Gradle 命令速查
bash
# ===== 基础构建 =====
./gradlew build # 完整构建
./gradlew clean # 清理构建输出
./gradlew clean build # 清理后重新构建
./gradlew build -x test # 跳过测试的构建
# ===== 运行和调试 =====
./gradlew bootRun # 运行 CAS
./gradlew bootRun --debug # 调试模式运行
./gradlew debug # 调试模式运行(端口 5005)
./gradlew run # 运行构建产物
# ===== CAS 专用任务 =====
./gradlew createKeystore # 创建 SSL 密钥库
./gradlew createTheme -Ptheme=mytheme # 创建自定义主题
./gradlew getResource -PresourceName=casLoginView # 提取模板
./gradlew listTemplateViews # 列出所有模板
./gradlew exportConfigMetadata # 导出配置元数据
./gradlew validateConfiguration # 验证配置
./gradlew casVersion # 查看 CAS 版本
./gradlew springBootVersion # 查看 Spring Boot 版本
# ===== CAS Shell =====
./gradlew downloadShell # 下载 CAS Shell
./gradlew runShell # 运行 CAS Shell
./gradlew debugShell # 调试模式运行 CAS Shell
# ===== 容器化构建 =====
./gradlew jibDockerBuild # 使用 Jib 构建镜像到本地 Docker
./gradlew jibBuildTar # 使用 Jib 构建镜像为 tar 文件
./gradlew jib # 使用 Jib 推送镜像到仓库
./gradlew casBuildDockerImage # 使用 Docker 插件构建镜像
./gradlew casPushDockerImage # 使用 Docker 插件推送镜像
# ===== 依赖管理 =====
./gradlew dependencies # 查看依赖树
./gradlew allDependencies # 查看所有依赖
./gradlew allDependenciesInsight # 依赖洞察
./gradlew showConfiguration # 显示配置信息
# ===== 构建优化 =====
./gradlew build --parallel # 并行构建
./gradlew build --configuration-cache # 启用配置缓存
./gradlew build --build-cache # 启用构建缓存
./gradlew build --no-configuration-cache # 禁用配置缓存
# ===== 属性覆盖 =====
./gradlew build -Pvalidate=false # 跳过配置验证
./gradlew build -Pcas.version=6.6.16 # 覆盖 CAS 版本
./gradlew bootRun -Dcas.standalone.configuration-directory=/etc/cas/config1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
附录 B:gradle.properties 完整参考
B.1 通用属性
| 属性 | 默认值 | 说明 | 版本 |
|---|---|---|---|
version | CAS 版本号 | 项目版本 | 5.3/6.6/7.3 |
cas.version | - | CAS 服务器版本(核心属性) | 5.3/6.6/7.3 |
springBootVersion | - | Spring Boot 版本 | 5.3/6.6/7.3 |
group | org.apereo.cas | 项目 Group ID | 5.3/6.6/7.3 |
artifactId | cas-overlay | 项目 Artifact ID | 5.3/6.6/7.3 |
sourceCompatibility | 8/11/21 | Java 源码兼容版本 | 5.3/6.6/7.3 |
targetCompatibility | 8/11/21 | Java 字节码目标版本 | 5.3/6.6/7.3 |
B.2 容器化属性
| 属性 | 默认值 | 说明 | 版本 |
|---|---|---|---|
containerImageOrg | apereo | 镜像组织名 | 5.3/6.6/7.3 |
containerImageName | cas | 镜像名称 | 5.3/6.6/7.3 |
baseDockerImage | - | 基础镜像 | 5.3/6.6/7.3 |
dockerImagePlatform | amd64:linux | 目标平台 | 5.3/6.6/7.3 |
allowInsecureRegistries | false | 允许非安全仓库 | 5.3/6.6/7.3 |
executable | true | 可执行 JAR | 5.3/6.6/7.3 |
B.3 服务器配置属性
| 属性 | 默认值 | 说明 | 版本 |
|---|---|---|---|
appServer | -tomcat | 嵌入式服务器 | 5.3/6.6/7.3 |
certDir | /etc/cas | 证书目录 | 5.3/6.6/7.3 |
serverKeystore | thekeystore | 密钥库文件名 | 5.3/6.6/7.3 |
exportedServerCert | cas.crt | 导出的证书文件名 | 5.3/6.6/7.3 |
storeType | PKCS12 | 密钥库类型 | 5.3/6.6/7.3 |
B.4 7.3.x 专属属性
| 属性 | 默认值 | 说明 |
|---|---|---|
jvmVendor | AMAZON | JVM Vendor |
gradleFoojayPluginVersion | 1.0.0 | Foojay Plugin 版本 |
gradleCyclonePluginVersion | 3.2.0 | CycloneDX Plugin 版本 |
lombokVersion | 1.18.42 | Lombok 版本 |
附录 C:版本兼容性矩阵
| 组件 | CAS 5.3.x | CAS 6.6.x | CAS 7.3.x |
|---|---|---|---|
| Gradle | 7.5 | 7.5 | 9.1.0 |
| Java | 8 | 11 | 21 |
| Spring Boot | 2.7.x | 2.7.x | 3.5.x |
| Spring Framework | 5.3.x | 5.3.x | 6.2.x |
| Jakarta EE | javax | javax | jakarta |
| Tomcat | 8.5.x / 9.0.x | 9.0.x | 10.1.x / 11.0.x |
| Log4j | 2.12.x | 2.17+ | 2.24+ |
| Lombok | 1.18.24 | 1.18.24 | 1.18.42 |
| BOM 类型 | mavenBom | platform | enforcedPlatform |
| Foojay Plugin | 无 | 无 | 1.0.0 |
| CycloneDX | 无 | 无 | 3.2.0 |
| Jib | 3.4.0 | 3.4.0 | 3.5.3 |
| Docker Plugin | 9.3.1 | 9.3.1 | 9.4.0 |
附录 D:仓库配置详解
D.1 仓库声明策略
CAS Overlay 项目的仓库配置随着版本演进不断丰富。理解每个仓库的用途和优先级,对于解决依赖下载失败问题至关重要。
D.1.1 CAS 5.3.x 的仓库配置
groovy
// CAS 5.3.x - build.gradle
buildscript {
repositories {
mavenCentral()
maven {
url 'https://build.shibboleth.net/nexus/content/repositories/releases'
}
}
}
repositories {
mavenCentral()
maven {
url 'https://build.shibboleth.net/nexus/content/repositories/releases'
}
}1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
2
3
4
5
6
7
8
9
10
11
12
13
14
15
5.3.x 的仓库配置非常简洁,只需要两个仓库:
- Maven Central: 大部分依赖的主要来源
- Shibboleth Releases: Shibboleth 相关依赖(CAS 的 SAML 支持依赖此仓库)
D.1.2 CAS 6.6.x / 7.3.x 的仓库配置
groovy
// CAS 6.6.x / 7.3.x - build.gradle
repositories {
if (project.privateRepoUrl) {
maven {
url project.privateRepoUrl
credentials {
username = project.privateRepoUsername
password = System.env.PRIVATE_REPO_TOKEN
}
}
}
mavenLocal()
mavenCentral()
maven { url = 'https://oss.sonatype.org/content/repositories/releases' }
maven {
url = 'https://central.sonatype.com/repository/maven-snapshots/'
mavenContent { snapshotsOnly() }
}
maven {
url = "https://repository.apache.org/content/repositories/snapshots"
mavenContent { snapshotsOnly() }
}
maven {
url = 'https://build.shibboleth.net/nexus/content/repositories/releases/'
mavenContent { releasesOnly() }
}
maven {
url = "https://build.shibboleth.net/nexus/content/repositories/snapshots"
mavenContent { snapshotsOnly() }
}
maven {
url = "https://repo.spring.io/milestone"
mavenContent { releasesOnly() }
}
}1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
各仓库用途详解:
| 仓库 | URL | 内容类型 | 用途 |
|---|---|---|---|
| 私有仓库 | project.privateRepoUrl | 自定义 | 企业内部私有依赖 |
| Maven Local | ~/.m2/repository/ | 本地缓存 | 本地安装的依赖 |
| Maven Central | repo.maven.apache.org | 正式发布 | 主要依赖来源 |
| Sonatype Releases | oss.sonatype.org/.../releases | 正式发布 | CAS 正式版发布仓库 |
| Sonatype Snapshots | central.sonatype.com/.../snapshots | 快照版 | CAS 快照版(开发中) |
| Apache Snapshots | repository.apache.org/.../snapshots | 快照版 | Apache 项目快照(Shiro 等) |
| Shibboleth Releases | build.shibboleth.net/.../releases | 正式发布 | Shibboleth/OpenSAML 依赖 |
| Shibboleth Snapshots | build.shibboleth.net/.../snapshots | 快照版 | Shibboleth 快照版 |
| Spring Milestone | repo.spring.io/milestone | 里程碑版 | Spring Boot 里程碑版本 |
仓库优先级规则:
Gradle 按声明顺序查找依赖。当多个仓库包含同一依赖时,先声明的仓库优先。CAS 的仓库声明顺序遵循以下原则:
- 私有仓库优先(企业内部依赖)
- 本地缓存优先(避免网络请求)
- Maven Central 作为主要来源
- 特定仓库作为补充(Shibboleth、Spring 等)
D.2 mavenContent 过滤器
6.6.x 和 7.3.x 使用 mavenContent 过滤器来限制仓库的内容类型:
groovy
maven {
url = 'https://central.sonatype.com/repository/maven-snapshots/'
mavenContent { snapshotsOnly() } // 只从此仓库获取快照版本
}
maven {
url = 'https://build.shibboleth.net/nexus/content/repositories/releases/'
mavenContent { releasesOnly() } // 只从此仓库获取正式版本
}1
2
3
4
5
6
7
8
2
3
4
5
6
7
8
这种过滤策略可以:
- 避免从快照仓库意外获取正式版本(浪费时间)
- 避免从正式仓库获取快照版本(获取到过期版本)
- 减少不必要的网络请求,加快依赖解析速度
D.3 私有仓库配置
CAS Overlay 支持通过属性配置私有 Maven 仓库:
properties
# gradle.properties
privateRepoUrl=
privateRepoUsername=1
2
3
2
3
bash
# 通过命令行传递私有仓库凭据
./gradlew build \
-PprivateRepoUrl=https://nexus.mycompany.com/repository/maven-releases/ \
-PprivateRepoUsername=myuser \
-DPRIVATE_REPO_TOKEN=mypassword1
2
3
4
5
2
3
4
5
安全提示: 仓库密码通过环境变量 PRIVATE_REPO_TOKEN 传递,而非属性文件,避免密码泄露到版本控制系统中。
附录 E:常见问题排查
E.1 依赖下载失败
症状: 构建时报 Could not resolve org.apereo.cas:cas-server-core:xxx
排查步骤:
- 检查网络连接: 确认可以访问 Maven Central 和 Sonatype 仓库
- 检查仓库镜像: 如果使用镜像(如阿里云、华为云),确认镜像同步状态
- 清理本地缓存: 删除
~/.gradle/caches/modules-2/下对应依赖的缓存 - 检查版本号: 确认
cas.version是否正确,某些版本可能不存在 - 检查仓库声明: 确认
build.gradle中的仓库 URL 是否可访问
bash
# 诊断依赖解析问题
./gradlew dependencies --info
./gradlew allDependenciesInsight --configuration compileClasspath1
2
3
2
3
E.2 Gradle 版本不兼容
症状: 构建时报 Unsupported class file major version 或 Could not target platform
排查步骤:
- 确认 Gradle 版本:
./gradlew --version - 确认 Java 版本:
java -version - 检查 Gradle Wrapper 配置:
cat gradle/wrapper/gradle-wrapper.properties - 参考版本兼容性矩阵: 确保使用的 Gradle 版本支持项目的 Java 版本
E.3 配置缓存相关错误
症状: 构建时报 Configuration cache problems found 或任务执行异常
解决方案:
bash
# 临时禁用配置缓存
./gradlew build --no-configuration-cache
# 或在 gradle.properties 中关闭
org.gradle.configuration-cache=false1
2
3
4
5
2
3
4
5
E.4 Jib 构建失败
症状: jibDockerBuild 或 jib 任务失败
常见原因:
- 配置缓存冲突: Jib 不兼容配置缓存,使用
--no-configuration-cache - 认证失败: 检查 Docker 仓库凭据
- 基础镜像不存在: 检查
baseDockerImage属性 - 端口冲突: 检查是否有其他进程占用 Docker 端口
bash
# 排查 Jib 问题
./gradlew jibDockerBuild --info --no-configuration-cache1
2
2
E.5 Java Toolchain 找不到 JDK
症状: 构建时报 No matching toolchain found for language version XX
解决方案(CAS 7.3.x):
- 确认 Foojay Plugin 已配置: 检查
settings.gradle中是否有foojay-resolver-convention插件 - 检查网络连接: Foojay Plugin 需要访问
api.foojay.io下载 JDK - 手动指定 JDK 路径: 如果自动下载失败,可以手动安装 JDK 并设置
JAVA_HOME - 检查 jvmVendor 配置: 确认指定的 Vendor 是否被 Foojay 支持
bash
# 诊断 Toolchain 问题
./gradlew build --info 2>&1 | grep -i toolchain1
2
2
E.6 bootRun 启动失败
症状: ./gradlew bootRun 执行后 CAS 启动失败
排查步骤:
- 检查 Java 版本: 确认使用的 JDK 版本与 CAS 版本匹配
- 检查配置文件: 确认
src/main/resources/application.yml格式正确 - 检查端口占用: 默认 8443 端口是否被其他进程占用
- 查看日志: CAS 的日志输出通常包含详细的错误信息
bash
# 以调试模式启动,查看详细日志
./gradlew bootRun --debug
# 检查端口占用
lsof -i :84431
2
3
4
5
2
3
4
5
附录 F:构建流水线集成示例
F.1 GitHub Actions 集成
yaml
# .github/workflows/cas-build.yml
name: CAS Overlay Build
on:
push:
branches: [main]
pull_request:
branches: [main]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Set up JDK
uses: actions/setup-java@v4
with:
java-version: '21'
distribution: 'temurin'
- name: Cache Gradle packages
uses: actions/cache@v4
with:
path: |
~/.gradle/caches
~/.gradle/wrapper
key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }}
restore-keys: |
${{ runner.os }}-gradle-
- name: Grant execute permission for gradlew
run: chmod +x gradlew
- name: Build with Gradle
run: ./gradlew build --no-configuration-cache
- name: Build Docker image with Jib
run: ./gradlew jibDockerBuild --no-configuration-cache
- name: Upload SBOM
uses: actions/upload-artifact@v4
with:
name: cyclonedx-sbom
path: build/reports/cyclonedxBom/1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
F.2 GitLab CI 集成
yaml
# .gitlab-ci.yml
stages:
- build
- test
- package
- deploy
variables:
GRADLE_OPTS: "-Dorg.gradle.daemon=false -Dorg.gradle.caching=true"
cache:
paths:
- .gradle/caches
- .gradle/wrapper
build:
stage: build
script:
- chmod +x gradlew
- ./gradlew build --no-configuration-cache -Pvalidate=false
artifacts:
paths:
- build/libs/
- build/reports/cyclonedxBom/
test:
stage: test
script:
- chmod +x gradlew
- ./gradlew test --no-configuration-cache
package:
stage: package
script:
- chmod +x gradlew
- ./gradlew jibDockerBuild --no-configuration-cache \
-DdockerUsername=$CI_REGISTRY_USER \
-DdockerPassword=$CI_REGISTRY_PASSWORD
only:
- main1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
F.3 Jenkinsfile 集成
groovy
// Jenkinsfile
pipeline {
agent any
environment {
DOCKER_CREDENTIALS = credentials('docker-registry-credentials')
}
tools {
gradle 'Gradle-9.1'
}
stages {
stage('Build') {
steps {
sh './gradlew clean build --no-configuration-cache'
}
post {
always {
junit 'build/test-results/test/*.xml'
}
}
}
stage('SBOM Generation') {
steps {
sh './gradlew cyclonedxBom --no-configuration-cache'
}
}
stage('Docker Build') {
steps {
sh './gradlew jibDockerBuild --no-configuration-cache ' +
"-DdockerUsername=${DOCKER_CREDENTIALS_USR} " +
"-DdockerPassword=${DOCKER_CREDENTIALS_PSW}"
}
}
stage('Docker Push') {
when {
branch 'main'
}
steps {
sh './gradlew casPushDockerImage --no-configuration-cache ' +
"-DdockerUsername=${DOCKER_CREDENTIALS_USR} " +
"-DdockerPassword=${DOCKER_CREDENTIALS_PSW}"
}
}
}
post {
always {
archiveArtifacts artifacts: 'build/libs/*.jar, build/reports/cyclonedxBom/**', fingerprint: true
}
}
}1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
附录 G:术语表
| 术语 | 全称 | 说明 |
|---|---|---|
| CAS | Central Authentication Service | Apereo 开源的企业级单点登录解决方案 |
| Overlay | Overlay | 覆盖层模式,通过覆盖默认资源实现定制化 |
| BOM | Bill of Materials | 物料清单,集中管理依赖版本 |
| SBOM | Software Bill of Materials | 软件物料清单,记录软件组件信息 |
| OCI | Open Container Initiative | 开放容器倡议,容器镜像标准 |
| Jib | - | Google 开源的 Java 容器化工具 |
| CycloneDX | - | 开源的 SBOM 标准和工具 |
| Toolchain | Java Toolchain | Gradle 的 JDK 版本管理机制 |
| Foojay | Foojay Discovery API | JDK 发行版发现和下载 API |
| JPMS | Java Platform Module System | Java 平台模块系统(Java 9+) |
| SNAPSHOT | - | Maven/Gradle 中的快照版本(开发中版本) |
| Gradle Wrapper | - | Gradle 包装器,确保构建使用指定版本 |
| Configuration Cache | - | Gradle 配置缓存,缓存构建配置阶段结果 |
| bootRun | - | Spring Boot Gradle Plugin 的运行任务 |
| bootJar | - | Spring Boot Gradle Plugin 的打包任务 |
| Thymeleaf | - | Java 模板引擎,CAS 使用它渲染页面 |
| Webflow | Spring Web Flow | Spring 的 Web 流程框架,CAS 使用它管理登录流程 |
| PAC4J | - | Java 认证/授权框架,CAS 通过它支持多种认证协议 |
版权声明: 本文为必码(bima.cc)原创技术文章,仅供学习交流。
本文内容基于实际项目源码解析整理,代码示例均为教学简化版本,仅供学习参考。
文档内容提取自项目源码与配置文件,如需获取完整项目代码,请访问 bima.cc。