Skip to content

CAS前端框架变迁与Material Design集成实战:从多框架混战到现代UI的演进之路

作者: 必码 | bima.cc


前言

在企业级单点登录(SSO)领域,Apereo CAS(Central Authentication Service)无疑是Java生态中最成熟、最广泛使用的开源解决方案之一。从2004年最初的CAS协议诞生,到如今CAS 7.3全面拥抱Spring Boot 3和Java 21,这个项目已经走过了二十余年的发展历程。然而,在众多技术文章中,CAS的前端技术栈演进往往是一个被忽视的话题。大多数开发者关注的是后端的认证流程、票据管理、协议支持等核心功能,却很少深入探讨CAS前端UI框架的变迁史。

这并不难理解。CAS本质上是一个后端服务,前端UI只是其"门面"。但随着用户对登录体验要求的不断提升——从简单的表单提交到响应式设计,从品牌定制到无障碍访问——前端技术栈的选择和演进变得越来越重要。一个设计精良、交互流畅的登录页面,不仅能够提升用户体验,还能增强企业品牌形象,甚至在某些场景下成为安全性的加分项(例如防钓鱼UI设计)。

回顾CAS前端框架的变迁历史,我们可以清晰地看到一条从"重量级多框架并存"到"轻量化精简",再到"现代化UI组件库"的演进路径。这条路径并非一帆风顺,其中充满了技术选型的权衡、社区反馈的驱动,以及前端技术大趋势的影响。CAS 5.3时代,AngularJS、Semantic-UI、Knockout.js、D3.js、jQuery UI等多框架并存,虽然功能强大,但带来了沉重的依赖负担和维护成本。CAS 6.6果断精简为jQuery + Bootstrap的轻量化组合,完成了过渡。而到了CAS 7.3,项目全面拥抱Material Components Web,配合DataTables和Bootstrap 5,实现了真正意义上的现代化UI。

本文将基于CAS项目的实际源码和配置文件,深入分析这三个版本的前端技术栈架构、Sass/SCSS主题系统的兴衰、Thymeleaf模板目录结构的演进、前端依赖管理策略的变迁,以及Material Design组件在CAS 7.3中的具体集成方式。我们还将探讨自定义CSS/JS的最佳实践,以及生产环境下的前端优化策略。

无论你是CAS的长期使用者,还是正在评估CAS作为企业SSO解决方案的架构师,抑或是对前端技术演进感兴趣的开发者,本文都将为你提供一份详尽的技术参考。让我们从CAS 5.3那个"多框架混战"的时代开始,一步步走进CAS 7.3的Material Design世界。


一、CAS前端技术栈三代变迁总览

1.1 三代技术栈全景对比

CAS前端技术栈的演进可以分为三个清晰的阶段,每个阶段都反映了当时前端技术生态的主流趋势和CAS项目自身的技术决策。在深入每个版本的细节之前,我们先通过一个全景对比来把握整体脉络。

CAS 5.3——重量级多框架并存时代(约2018年)

CAS 5.3的前端技术栈可以用"大而全"来形容。在这个版本中,CAS同时引入了多个前端框架和库:

  • AngularJS:Google推出的前端MVW框架,用于构建动态的表单交互和数据绑定。在CAS 5.3中,AngularJS主要用于管理登录表单的状态、处理用户输入验证,以及实现部分SPA(单页应用)式的交互体验。
  • Semantic-UI:一套语义化的UI组件库,提供了丰富的预制组件。CAS 5.3使用Semantic-UI来构建登录页面的整体布局、按钮、表单控件、消息提示等UI元素。
  • Knockout.js:一个轻量级的MVVM库,用于实现声明式数据绑定。在CAS 5.3中,Knockout.js与AngularJS配合使用,处理一些较为简单的数据绑定场景。
  • D3.js:数据驱动的文档操作库,主要用于数据可视化。在CAS 5.3中,D3.js被用于管理面板中的统计图表展示。
  • jQuery UI:基于jQuery的UI组件库,提供了拖拽、排序、对话框等交互组件。CAS 5.3使用jQuery UI来增强管理面板的交互体验。

这种多框架并存的策略在当时并不罕见。2018年前后的前端生态正处于一个过渡期——React和Vue正在崛起,但AngularJS仍然拥有庞大的用户群,jQuery及其生态依然是企业级项目的主流选择。CAS作为一个需要兼顾稳定性和功能性的企业级项目,选择了"多管齐下"的策略,确保各种UI需求都能得到满足。

然而,这种策略的代价是显而易见的。多个框架的引入导致前端资源体积急剧膨胀,页面加载性能受到影响;不同框架之间的数据通信和状态管理变得复杂;维护成本也大幅增加——当某个框架发布新版本或出现安全漏洞时,CAS团队需要同时处理多个框架的升级工作。

CAS 6.6——轻量化过渡时代(约2022年)

到了CAS 6.6,项目团队做出了一个果断的决定:大幅精简前端技术栈。AngularJS、Semantic-UI、Knockout.js、D3.js和jQuery UI全部被移除,取而代之的是一个极简的组合:

  • jQuery:保留了jQuery作为基础的DOM操作和事件处理库。jQuery虽然"老旧",但在企业级项目中仍然是最可靠、最广泛支持的选择。
  • Bootstrap:引入了Bootstrap作为UI框架,替代了Semantic-UI。Bootstrap拥有更大的社区、更丰富的文档和更成熟的组件生态。

这个精简决策的背后有多重考量。首先,AngularJS在2018年就已经进入了长期支持(LTS)阶段,Google官方推荐迁移到Angular(2+),但完整的迁移成本过高。其次,Semantic-UI的维护活跃度下降,社区逐渐转向其他UI框架。第三,随着CAS用户群体的扩大,轻量化的前端方案更有利于各种部署环境下的性能表现。

从CAS 5.3到CAS 6.6的精简,是一次"做减法"的技术决策。它不仅减少了前端依赖的数量,还大幅降低了页面资源体积,提升了加载速度。但这也意味着某些高级UI功能(如D3.js驱动的数据可视化)需要通过其他方式实现,或者暂时被搁置。

CAS 7.3——现代化UI时代(2024年至今)

CAS 7.3代表了CAS前端技术栈的最新形态。在这个版本中,项目引入了Google的Material Design组件体系:

  • Material Components Web(MDC-Web):Google官方的Material Design Web组件库,版本14.0.0。MDC-Web提供了一套完整的、符合Material Design规范的UI组件,包括按钮、输入框、卡片、对话框、导航栏等。
  • Material Design Icons(MDI):版本7.4.47,提供了超过7000个矢量图标,覆盖了各种常见的UI场景。
  • DataTables:强大的表格插件,用于管理面板中的数据展示和交互。
  • Bootstrap 5:继续保留Bootstrap作为基础布局框架,与MDC-Web形成互补。

CAS 7.3的前端技术栈选择体现了几个重要的技术趋势。第一,Web Components标准的成熟使得框架无关的UI组件库成为可能,MDC-Web正是基于Web Components和Custom Elements构建的。第二,Material Design作为Google的设计语言,在企业级应用中的接受度越来越高,其一致性和可定制性得到了广泛认可。第三,CAS 7.3同时保留了Bootstrap 5,说明项目团队在追求现代化的同时,也没有放弃对现有生态的兼容。

1.2 技术栈变迁的驱动因素

CAS前端技术栈的三代变迁并非随意为之,而是多种因素共同驱动的结果。理解这些驱动因素,有助于我们在自己的项目中做出更好的技术选型决策。

社区反馈与用户体验需求

CAS是一个开源项目,社区反馈对其技术决策有着重要影响。在CAS 5.3时代,不少用户反馈登录页面加载缓慢、移动端适配不佳、自定义主题困难等问题。这些反馈直接推动了CAS 6.6的轻量化改革。而到了CAS 7.3,随着用户对UI美观度和交互体验要求的进一步提升,Material Design的引入成为了一个自然的选择。

前端技术生态的演进

前端技术生态在2018年至2024年间发生了巨大的变化。AngularJS的衰退、React和Vue的崛起、Web Components标准的成熟、CSS自定义属性的普及——这些变化都在不同程度上影响了CAS的技术选型。CAS项目团队需要在一个相对保守的企业级项目中平衡"跟进新技术"和"保持稳定性"之间的关系。

安全性与可维护性

前端依赖的安全性是企业级项目的重要考量。每引入一个前端依赖,就增加了一个潜在的安全攻击面。CAS 5.3的多框架并存策略意味着更多的依赖需要跟踪和更新,CAS 6.6的精简显著降低了这方面的风险。而CAS 7.3选择MDC-Web这样的官方组件库,也是出于安全性和长期维护性的考虑。

部署环境的多样化

CAS的部署环境非常多样——从传统的物理服务器到云平台,从内网环境到公网访问,从桌面浏览器到移动设备。不同的部署环境对前端资源的体积、加载策略、兼容性等有着不同的要求。CAS 6.6和7.3在精简技术栈的同时,也通过CDN支持、资源压缩等手段优化了不同环境下的部署体验。

1.3 版本间迁移的核心挑战

对于CAS的使用者来说,从一个版本迁移到另一个版本,前端部分的迁移往往是最大的挑战之一。这主要是因为CAS的前端定制化程度通常很高——大多数企业都会根据自己的品牌规范定制登录页面的样式、布局和交互。

从CAS 5.3迁移到CAS 6.6

这个迁移过程的核心挑战在于UI框架的切换。从Semantic-UI迁移到Bootstrap,意味着所有的UI组件类名、结构、行为都需要重新适配。例如,Semantic-UI的表单验证方式和Bootstrap就有显著差异。此外,AngularJS和Knockout.js的移除意味着之前基于这些框架实现的自定义交互逻辑需要用jQuery重写。

以下是一个教学示例,展示了CAS 5.3和CAS 6.6中登录按钮的写法差异:

html
<!-- CAS 5.3:Semantic-UI 风格(教学示例) -->
<button class="ui fluid large submit button" type="submit">
  <i class="sign in icon"></i> 登录
</button>

<!-- CAS 6.6:Bootstrap 风格(教学示例) -->
<button class="btn btn-lg btn-primary w-100" type="submit">
  登录
</button>

可以看到,仅仅是按钮的类名就从Semantic-UI的ui fluid large submit button变为了Bootstrap的btn btn-lg btn-primary w-100。对于整个登录页面来说,这种变化涉及大量的HTML模板修改。

从CAS 6.6迁移到CAS 7.3

从CAS 6.6迁移到CAS 7.3的挑战主要在于Material Design组件的引入。MDC-Web的组件使用方式与传统的Bootstrap组件有所不同,它通常需要初始化JavaScript实例才能正常工作。例如,一个Material Design的文本输入框需要同时包含HTML结构、CSS类名和JavaScript初始化代码:

html
<!-- CAS 7.3: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>
javascript
// CAS 7.3:MDC 组件初始化(教学示例)
import { MDCTextField } from '@material/textfield';
const textField = new MDCTextField(
  document.querySelector('.mdc-text-field')
);

这种从"纯HTML+CSS"到"HTML+CSS+JS组件化"的转变,要求开发者对Material Design的组件模型有深入的理解。同时,MDC-Web与Bootstrap 5的共存也需要仔细处理样式冲突问题。


二、CAS 5.3 Sass/SCSS主题源码架构深度解析

2.1 static/sass/目录结构总览

CAS 5.3的前端样式系统建立在Sass/SCSS之上,这是一个非常成熟且强大的CSS预处理器方案。在CAS 5.3的源码中,Sass/SCSS文件位于static/sass/目录下,其目录结构体现了良好的模块化设计思想。

CAS 5.3的Sass/SCSS目录大致包含以下子目录:

  • components/:存放与具体UI组件相关的样式文件,如按钮、表单、卡片、导航栏等。每个组件通常对应一个独立的SCSS文件,便于维护和复用。
  • mixins/:存放Sass mixin定义文件。Mixin是Sass的核心特性之一,它允许开发者定义可复用的样式片段,类似于编程语言中的函数。
  • partials/:存放局部的、通用的样式文件,如重置样式、排版规范、工具类等。这些文件通常通过@import指令被其他文件引用。
  • vendor/:存放第三方库的Sass/SCSS文件或覆盖样式。CAS 5.3使用了多个前端UI库,每个库可能都有自己的样式文件,vendor目录就是用来管理这些第三方样式的。

这种目录结构遵循了Sass社区的最佳实践——按功能模块组织文件,通过@import@use指令组合成最终的样式输出。这种模块化的设计使得样式代码具有良好的可维护性和可扩展性。

2.2 _variables.scss:全局变量体系

_variables.scss是CAS 5.3 Sass/SCSS主题系统的核心文件之一。它定义了全局的样式变量,包括颜色、字体、间距、边框、阴影等设计令牌(Design Tokens)。通过集中管理这些变量,CAS实现了主题的统一管理和快速定制。

以下是一个教学示例,展示了CAS 5.3中_variables.scss的核心变量定义模式:

scss
// _variables.scss(教学示例,基于CAS 5.3源码简化)

// ==================== 颜色体系 ====================
// 主色调
$cas-primary-color: #2c3e50;
$cas-secondary-color: #3498db;
$cas-accent-color: #e74c3c;

// 语义颜色
$cas-success-color: #27ae60;
$cas-warning-color: #f39c12;
$cas-danger-color: #e74c3c;
$cas-info-color: #3498db;

// 中性色
$cas-text-color: #333333;
$cas-text-muted: #777777;
$cas-border-color: #dddddd;
$cas-bg-color: #f5f5f5;

// ==================== 排版体系 ====================
$cas-font-family-base: 'Helvetica Neue', Helvetica,
  Arial, 'PingFang SC', 'Microsoft YaHei', sans-serif;
$cas-font-size-base: 14px;
$cas-font-size-lg: 18px;
$cas-font-size-sm: 12px;
$cas-line-height-base: 1.5;

// ==================== 间距体系 ====================
$cas-spacing-xs: 4px;
$cas-spacing-sm: 8px;
$cas-spacing-md: 16px;
$cas-spacing-lg: 24px;
$cas-spacing-xl: 32px;

// ==================== 圆角与阴影 ====================
$cas-border-radius: 4px;
$cas-border-radius-lg: 8px;
$cas-box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
$cas-box-shadow-lg: 0 4px 12px rgba(0, 0, 0, 0.15);

这个变量体系的设计有几个值得注意的特点。首先,所有变量名都以$cas-为前缀,这是一种命名空间约定,可以有效避免与第三方库的变量名冲突。其次,变量按照功能分组(颜色、排版、间距、圆角与阴影),每组都有清晰的注释说明,便于开发者快速定位和修改。第三,颜色值使用了十六进制表示法,这是Sass中最常用的颜色格式,便于进行颜色运算(如加深、变浅、透明度调整等)。

在实际项目中,企业可以通过修改_variables.scss中的变量值来快速定制CAS的主题外观。例如,将$cas-primary-color从深蓝色改为企业的品牌色,就能实现整体色调的切换。这种基于变量的主题定制方式,比直接修改CSS属性值更加高效和可靠。

2.3 _mixins.scss:可复用样式片段

_mixins.scss文件定义了CAS 5.3中常用的Sass mixin。Mixin是Sass提供的一种代码复用机制,类似于编程语言中的函数——它可以接受参数,返回一段样式代码。通过Mixin,开发者可以将常见的样式模式抽象为可复用的片段,减少代码重复。

以下是一个教学示例,展示了CAS 5.3中常用的Mixin定义:

scss
// _mixins.scss(教学示例,基于CAS 5.3源码简化)

// 响应式断点 Mixin
@mixin respond-to($breakpoint) {
  @if $breakpoint == 'sm' {
    @media (max-width: 576px) { @content; }
  } @else if $breakpoint == 'md' {
    @media (max-width: 768px) { @content; }
  } @else if $breakpoint == 'lg' {
    @media (max-width: 992px) { @content; }
  } @else if $breakpoint == 'xl' {
    @media (max-width: 1200px) { @content; }
  }
}

// 文本截断 Mixin
@mixin text-truncate {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

// Flex 居中 Mixin
@mixin flex-center {
  display: flex;
  align-items: center;
  justify-content: center;
}

// 卡片样式 Mixin
@mixin card-base($padding: $cas-spacing-md) {
  background: #fff;
  border-radius: $cas-border-radius;
  box-shadow: $cas-box-shadow;
  padding: $padding;
}

这些Mixin覆盖了CAS登录页面和管理面板中常见的样式模式。respond-to mixin封装了响应式断点逻辑,使得开发者可以在组件样式中方便地添加响应式规则,而不需要重复编写@media查询。text-truncate mixin处理了文本溢出的常见场景。flex-center mixin简化了Flexbox居中布局的代码。card-base mixin则提供了一个统一的卡片样式基础。

Mixin的使用方式非常直观。例如,在定义登录卡片的样式时:

scss
// 登录卡片样式(教学示例)
.login-card {
  @include card-base($cas-spacing-lg);
  max-width: 400px;
  margin: $cas-spacing-xl auto;

  @include respond-to('sm') {
    max-width: 100%;
    margin: $cas-spacing-md;
  }
}

2.4 _login.scss:登录页面样式模块

_login.scss是CAS 5.3中专门负责登录页面样式的模块。登录页面是CAS最核心的前端页面,其样式设计直接影响用户的第一印象和使用体验。

以下是一个教学示例,展示了CAS 5.3中_login.scss的核心结构:

scss
// _login.scss(教学示例,基于CAS 5.3源码简化)

// ==================== 登录页面容器 ====================
.cas-login-container {
  min-height: 100vh;
  display: flex;
  align-items: center;
  justify-content: center;
  background: linear-gradient(
    135deg,
    $cas-primary-color 0%,
    $cas-secondary-color 100%
  );

  @include respond-to('sm') {
    padding: $cas-spacing-md;
  }
}

// ==================== 登录卡片 ====================
.cas-login-card {
  @include card-base($cas-spacing-lg);
  width: 100%;
  max-width: 420px;

  &__header {
    text-align: center;
    margin-bottom: $cas-spacing-lg;

    .logo {
      max-width: 200px;
      height: auto;
      margin-bottom: $cas-spacing-md;
    }

    h2 {
      color: $cas-primary-color;
      font-size: $cas-font-size-lg;
      margin: 0;
    }
  }

  &__body {
    .form-group {
      margin-bottom: $cas-spacing-md;
    }

    .btn-login {
      @include flex-center;
      width: 100%;
      padding: $cas-spacing-sm $cas-spacing-md;
      font-size: $cas-font-size-base;
      background: $cas-primary-color;
      color: #fff;
      border: none;
      border-radius: $cas-border-radius;
      cursor: pointer;
      transition: background 0.3s ease;

      &:hover {
        background: darken($cas-primary-color, 10%);
      }
    }
  }

  &__footer {
    text-align: center;
    margin-top: $cas-spacing-md;
    color: $cas-text-muted;
    font-size: $cas-font-size-sm;
  }
}

// ==================== 错误消息 ====================
.cas-error-message {
  background: lighten($cas-danger-color, 40%);
  color: $cas-danger-color;
  padding: $cas-spacing-sm $cas-spacing-md;
  border-radius: $cas-border-radius;
  margin-bottom: $cas-spacing-md;
  font-size: $cas-font-size-sm;

  i {
    margin-right: $cas-spacing-xs;
  }
}

// ==================== 社交登录按钮 ====================
.cas-social-login {
  display: flex;
  gap: $cas-spacing-sm;
  margin-top: $cas-spacing-md;

  &__btn {
    flex: 1;
    padding: $cas-spacing-sm;
    border: 1px solid $cas-border-color;
    border-radius: $cas-border-radius;
    background: #fff;
    cursor: pointer;
    transition: all 0.2s ease;

    &:hover {
      border-color: $cas-secondary-color;
      box-shadow: $cas-box-shadow;
    }
  }
}

这个登录页面样式模块展示了CAS 5.3 Sass/SCSS架构的几个核心设计理念。首先是BEM(Block Element Modifier)命名规范的使用——.cas-login-card__header.cas-login-card__body等类名遵循了BEM的命名约定,使得样式类名具有自描述性,减少了命名冲突的可能性。其次是Sass嵌套语法的使用——通过嵌套,样式代码的层级关系更加清晰,与HTML结构形成对应。第三是Mixin的灵活运用——card-baseflex-centerrespond-to等Mixin被巧妙地组合使用,减少了代码重复。

2.5 Sass/SCSS在CAS 6.6/7.3中的移除与转变

CAS 6.6做出了一个令人意外的决定——完全移除了Sass/SCSS源码。这个决定在当时引发了不少讨论,因为Sass/SCSS作为一种成熟的CSS预处理方案,在大型项目中有着广泛的应用。CAS团队移除Sass/SCSS的原因是多方面的。

CSS自定义属性的成熟

CSS自定义属性(Custom Properties,又称CSS变量)在CAS 6.6开发期间已经得到了所有主流浏览器的支持。CSS自定义属性提供了许多与Sass变量类似的功能,但它是原生CSS特性,不需要预编译步骤:

css
/* CSS 自定义属性实现主题变量(教学示例) */
:root {
  --cas-primary-color: #2c3e50;
  --cas-secondary-color: #3498db;
  --cas-font-family-base: 'Helvetica Neue', Helvetica, Arial, sans-serif;
  --cas-border-radius: 4px;
  --cas-spacing-md: 16px;
}

.login-card {
  background: var(--cas-bg-color, #ffffff);
  border-radius: var(--cas-border-radius);
  padding: var(--cas-spacing-md);
}

CSS自定义属性相比Sass变量有一个关键优势——它可以在运行时动态修改。这意味着可以通过JavaScript在用户交互时改变主题变量,实现动态主题切换。而Sass变量在编译阶段就被替换为具体值,无法在运行时修改。

构建流程的简化

Sass/SCSS需要预编译步骤,这意味着CAS的构建流程需要包含Sass编译器。在Gradle/Maven构建中集成Sass编译虽然可行,但增加了构建的复杂度。移除Sass/SCSS后,CAS的构建流程变得更加简洁——前端资源可以直接作为静态文件处理,无需额外的编译步骤。

维护成本的降低

Sass/SCSS虽然功能强大,但也引入了额外的维护负担。CAS团队需要维护Sass源码、编译配置、输出文件等多个部分。同时,Sass的语法在不同版本间有所变化(如@import@use取代),这也增加了维护的复杂性。移除Sass/SCSS后,CAS团队可以将更多精力集中在功能开发上。

Web Components与Shadow DOM的影响

CAS 7.3引入的MDC-Web组件基于Web Components标准,使用了Shadow DOM来封装组件样式。Shadow DOM提供了一种原生的样式隔离机制,组件内部的样式不会影响外部,外部的样式也不容易穿透到组件内部。这在一定程度上减少了对全局样式预处理的需求。

当然,Sass/SCSS的移除也带来了一些不便。对于习惯使用Sass的开发者来说,直接编写原生CSS可能会感觉"退步"——失去了嵌套语法、Mixin、函数等便利特性。不过,随着CSS原生嵌套(CSS Nesting)等新特性的普及,这些差距正在逐渐缩小。


三、CAS 7.3 Material Design组件集成实战

3.1 Material Components Web核心概念

Material Components Web(MDC-Web)是Google官方推出的Material Design Web组件库。与许多基于JavaScript框架的UI库不同,MDC-Web的设计理念是"框架无关"——它既不依赖Angular、React或Vue,也不需要特定的构建工具。MDC-Web基于Web Components标准和原生JavaScript构建,可以在任何Web项目中使用。

CAS 7.3集成的MDC-Web版本为14.0.0,这是一个相对成熟的版本,提供了完整的Material Design组件集。MDC-Web的组件架构遵循"基础CSS + 增强JS"的设计模式——每个组件都提供了一套CSS类名用于定义样式,同时提供了可选的JavaScript模块用于添加交互行为(如涟漪效果、表单验证、动画过渡等)。

MDC-Web的核心设计原则包括:

  • 一致性:所有组件遵循统一的Material Design规范,确保视觉和交互的一致性。
  • 无障碍性:组件内置了ARIA属性和键盘导航支持,符合WCAG标准。
  • 可定制性:通过CSS自定义属性和Sass变量(MDC-Web本身使用Sass编译),可以深度定制组件外观。
  • 渐进增强:即使不加载JavaScript,组件也能以基本样式正常显示;加载JavaScript后,交互体验得到增强。

3.2 material-components-web:14.0.0集成详解

CAS 7.3通过Gradle/Maven的WebJar机制引入了material-components-web:14.0.0。WebJar是一种将前端库打包为JAR文件的方式,便于在Java项目中管理前端依赖。

以下是一个教学示例,展示了CAS 7.3中MDC-Web依赖的声明方式:

groovy
// build.gradle(教学示例,基于CAS 7.3源码简化)
dependencies {
    // Material Components Web
    implementation "org.webjars.npm:material-components-web:14.0.0"

    // Material Design Icons Font
    implementation "org.webjars.npm:mdi__font:7.4.47"

    // Normalize.css
    implementation "org.webjars.npm:normalize.css:8.0.1"
}

在Thymeleaf模板中,通过webjar前缀来引用WebJar中的资源:

html
<!-- Thymeleaf 模板中引用 MDC-Web 资源(教学示例) -->
<link rel="stylesheet"
      th:href="@{/webjars/material-components-web/14.0.0/css/material-components-web.min.css}" />
<script th:src="@{/webjars/material-components-web/14.0.0/js/material-components-web.min.js}"></script>

MDC-Web 14.0.0提供了以下核心组件(部分列举):

组件类别组件名称用途
按钮MDCButton主要操作、次要操作、文本按钮
输入框MDCTextField文本输入、密码输入、文本域
卡片MDCCard内容容器、登录表单容器
对话框MDCDialog确认弹窗、信息提示
列表MDCList导航菜单、选项列表
标签页MDCTabBar内容切换、设置面板
涟漪MDCRipple点击反馈效果
图标MDCIconMaterial Icons展示
菜单MDCMenu下拉菜单、上下文菜单
SnackbarMDCSnackbar轻量级消息提示

3.3 mdi__font:7.4.47图标体系集成

Material Design Icons(MDI)是一个庞大的开源图标库,由Pictogrammers社区维护。CAS 7.3集成的mdi__font:7.4.47版本包含了超过7000个矢量图标,覆盖了几乎所有常见的UI场景。

MDI的使用方式非常简单。首先,在页面中引入MDI的CSS文件:

html
<!-- 引入 Material Design Icons(教学示例) -->
<link rel="stylesheet"
      th:href="@{/webjars/mdi__font/7.4.47/css/materialdesignicons.min.css}" />

然后,通过CSS类名来使用图标:

html
<!-- 使用 MDI 图标(教学示例) -->
<i class="mdi mdi-account"></i>         <!-- 用户图标 -->
<i class="mdi mdi-lock"></i>            <!-- 锁图标 -->
<i class="mdi mdi-eye"></i>             <!-- 查看图标 -->
<i class="mdi mdi-eye-off"></i>         <!-- 隐藏图标 -->
<i class="mdi mdi-login"></i>           <!-- 登录图标 -->
<i class="mdi mdi-alert-circle"></i>    <!-- 警告图标 -->
<i class="mdi mdi-check-circle"></i>    <!-- 成功图标 -->

MDI图标支持多种自定义方式,包括大小调整、颜色修改、旋转动画等:

css
/* MDI 图标自定义(教学示例) */
.mdi {
  &.icon-lg {
    font-size: 24px;
  }
  &.icon-primary {
    color: var(--cas-primary-color);
  }
  &.icon-spin {
    animation: mdi-spin 1s linear infinite;
  }
}

@keyframes mdi-spin {
  from { transform: rotate(0deg); }
  to { transform: rotate(360deg); }
}

在CAS 7.3的登录页面中,MDI图标被广泛应用于各种UI元素——输入框的前缀/后缀图标、按钮图标、状态提示图标、导航图标等。图标的使用不仅提升了页面的视觉效果,还增强了用户对功能含义的理解。

3.4 normalize.css:8.0.1的引入意义

normalize.css是一个轻量级的CSS重置库,其作用是使不同浏览器对HTML元素的默认渲染保持一致。CAS 7.3集成的normalize.css:8.0.1是该库的最新稳定版本。

在现代Web开发中,normalize.css几乎是必备的基础依赖。不同浏览器对HTML元素(如<h1><h6>的字号、<ul><ol>的缩进、<input><button>的样式等)有着不同的默认样式,这会导致页面在不同浏览器中呈现不一致的外观。normalize.css通过精确的CSS规则修正这些差异,为后续的样式定义提供一个一致的基线。

在CAS 7.3中,normalize.css通常作为第一个被加载的CSS文件,确保后续的MDC-Web样式和自定义样式能够在一个一致的基线上工作:

html
<!-- CAS 7.3 CSS 加载顺序(教学示例) -->
<!-- 1. 浏览器样式重置 -->
<link rel="stylesheet"
      th:href="@{/webjars/normalize.css/8.0.1/normalize.min.css}" />
<!-- 2. Material Components Web 基础样式 -->
<link rel="stylesheet"
      th:href="@{/webjars/material-components-web/14.0.0/css/material-components-web.min.css}" />
<!-- 3. Material Design Icons -->
<link rel="stylesheet"
      th:href="@{/webjars/mdi__font/7.4.47/css/materialdesignicons.min.css}" />
<!-- 4. 自定义样式覆盖 -->
<link rel="stylesheet" th:href="@{/css/cas.css}" />

3.5 custom.css / custom.js / material.js自定义扩展体系

CAS 7.3提供了一套完善的自定义扩展机制,允许开发者在不修改CAS核心代码的情况下定制前端行为和外观。这套机制的核心是三个文件:custom.csscustom.jsmaterial.js

custom.css——样式覆盖与主题定制

custom.css是CAS 7.3中最主要的样式定制入口。开发者可以在这个文件中覆盖CAS的默认样式、定义自定义组件样式、设置CSS自定义属性等。

以下是一个教学示例,展示了custom.css的典型内容结构:

css
/* custom.css(教学示例,基于CAS 7.3源码简化) */

/* ==================== CSS 自定义属性(主题变量) ==================== */
:root {
  /* 品牌色覆盖 */
  --cas-primary: #1565c0;
  --cas-primary-light: #1e88e5;
  --cas-primary-dark: #0d47a1;
  --cas-secondary: #00897b;
  --cas-accent: #ff6f00;

  /* 背景色 */
  --cas-bg-primary: #ffffff;
  --cas-bg-secondary: #f5f7fa;
  --cas-bg-login: linear-gradient(135deg, #1565c0 0%, #00897b 100%);

  /* 文本色 */
  --cas-text-primary: #212121;
  --cas-text-secondary: #757575;

  /* 间距 */
  --cas-spacing-unit: 8px;

  /* 圆角 */
  --cas-radius-sm: 4px;
  --cas-radius-md: 8px;
  --cas-radius-lg: 12px;

  /* 阴影 */
  --cas-shadow-sm: 0 1px 3px rgba(0, 0, 0, 0.12);
  --cas-shadow-md: 0 4px 6px rgba(0, 0, 0, 0.15);
  --cas-shadow-lg: 0 10px 25px rgba(0, 0, 0, 0.2);
}

/* ==================== 登录页面定制 ==================== */
#login {
  .mdc-card {
    border-radius: var(--cas-radius-lg);
    box-shadow: var(--cas-shadow-lg);
    overflow: hidden;
  }

  .mdc-text-field--focused {
    .mdc-notched-outline__leading,
    .mdc-notched-outline__notch,
    .mdc-notched-outline__trailing {
      border-color: var(--cas-primary);
    }
  }

  .mdc-button--raised {
    background-color: var(--cas-primary);
    border-radius: var(--cas-radius-md);
    height: 48px;
    font-size: 16px;
    letter-spacing: 0.5px;

    &:hover {
      background-color: var(--cas-primary-light);
    }
  }
}

/* ==================== 管理面板定制 ==================== */
#dashboard {
  .mdc-data-table {
    border-radius: var(--cas-radius-md);
    overflow: hidden;
  }

  .mdc-tab--active {
    color: var(--cas-primary);
    border-bottom-color: var(--cas-primary);
  }
}

custom.js——前端交互扩展

custom.js用于扩展CAS的前端交互逻辑。开发者可以在这个文件中添加自定义的表单验证、事件处理、动态UI更新等功能。

以下是一个教学示例,展示了custom.js的典型内容:

javascript
// custom.js(教学示例,基于CAS 7.3源码简化)

document.addEventListener('DOMContentLoaded', function() {

  // ==================== 密码可见性切换 ====================
  const togglePasswordBtns = document.querySelectorAll(
    '[data-toggle-password]'
  );
  togglePasswordBtns.forEach(function(btn) {
    btn.addEventListener('click', function() {
      const input = this.previousElementSibling;
      const icon = this.querySelector('i');
      if (input.type === 'password') {
        input.type = 'text';
        icon.classList.remove('mdi-eye-off');
        icon.classList.add('mdi-eye');
      } else {
        input.type = 'password';
        icon.classList.remove('mdi-eye');
        icon.classList.add('mdi-eye-off');
      }
    });
  });

  // ==================== 登录表单提交增强 ====================
  const loginForm = document.getElementById('fm1');
  if (loginForm) {
    loginForm.addEventListener('submit', function(e) {
      const username = document.getElementById('username');
      const password = document.getElementById('password');

      // 清除之前的错误状态
      clearFieldError(username);
      clearFieldError(password);

      // 基本的前端验证
      let isValid = true;
      if (!username.value.trim()) {
        showFieldError(username, '请输入用户名');
        isValid = false;
      }
      if (!password.value) {
        showFieldError(password, '请输入密码');
        isValid = false;
      }

      if (!isValid) {
        e.preventDefault();
      }
    });
  }

  // ==================== 工具函数 ====================
  function showFieldError(field, message) {
    field.closest('.mdc-text-field').classList.add('mdc-text-field--invalid');
    const helperText = field.closest('.mdc-text-field')
      .querySelector('.mdc-text-field-helper-text');
    if (helperText) {
      helperText.textContent = message;
      helperText.classList.add('mdc-text-field-helper-text--validation-msg');
    }
  }

  function clearFieldError(field) {
    field.closest('.mdc-text-field').classList.remove('mdc-text-field--invalid');
    const helperText = field.closest('.mdc-text-field')
      .querySelector('.mdc-text-field-helper-text');
    if (helperText) {
      helperText.textContent = '';
      helperText.classList.remove('mdc-text-field-helper-text--validation-msg');
    }
  }
});

material.js——MDC组件初始化与配置

material.js是CAS 7.3中专门用于初始化和配置MDC-Web组件的脚本文件。由于MDC-Web的许多组件需要通过JavaScript初始化才能获得完整的交互功能(如涟漪效果、浮动标签动画等),material.js承担了这一职责。

以下是一个教学示例,展示了material.js的核心内容:

javascript
// material.js(教学示例,基于CAS 7.3源码简化)

// MDC 组件初始化
function initMaterialComponents() {

  // 初始化文本输入框
  document.querySelectorAll('.mdc-text-field').forEach(function(el) {
    new mdc.textField.MDCTextField(el);
  });

  // 初始化按钮涟漪效果
  document.querySelectorAll('.mdc-button').forEach(function(el) {
    new mdc.ripple.MDCRipple(el);
  });

  // 初始化卡片涟漪效果
  document.querySelectorAll('.mdc-card__primary-action').forEach(function(el) {
    new mdc.ripple.MDCRipple(el);
  });

  // 初始化 Snackbar
  const snackbarEl = document.querySelector('.mdc-snackbar');
  if (snackbarEl) {
    window.casSnackbar = new mdc.snackbar.MDCSnackbar(snackbarEl);
  }

  // 初始化对话框
  document.querySelectorAll('.mdc-dialog').forEach(function(el) {
    new mdc.dialog.MDCDialog(el);
  });

  // 初始化标签页
  const tabBar = document.querySelector('.mdc-tab-bar');
  if (tabBar) {
    new mdc.tabBar.MDCTabBar(tabBar);
  }
}

// 页面加载完成后初始化
if (document.readyState === 'loading') {
  document.addEventListener('DOMContentLoaded', initMaterialComponents);
} else {
  initMaterialComponents();
}

3.6 MDC按钮组件在登录页面的使用

MDC按钮是CAS 7.3登录页面中最常用的交互组件之一。MDC-Web提供了多种按钮变体,CAS主要使用了以下几种:

  • Raised Button(凸起按钮):用于主要操作,如登录按钮。具有阴影效果和背景色填充。
  • Outlined Button(轮廓按钮):用于次要操作,如"忘记密码"、"注册"等链接。
  • Text Button(文本按钮):用于低优先级的操作,如"帮助"、"隐私政策"等。

以下是一个教学示例,展示了MDC按钮在CAS 7.3登录页面中的使用方式:

html
<!-- MDC 按钮在登录页面的使用(教学示例) -->

<!-- 主要操作:凸起按钮 -->
<button class="mdc-button mdc-button--raised"
        type="submit" id="loginButton">
  <span class="mdc-button__ripple"></span>
  <i class="mdi mdi-login mdc-button__icon" aria-hidden="true"></i>
  <span class="mdc-button__label">登录</span>
</button>

<!-- 次要操作:轮廓按钮 -->
<a class="mdc-button mdc-button--outlined" th:href="@{/forgot}">
  <span class="mdc-button__ripple"></span>
  <span class="mdc-button__label">忘记密码</span>
</a>

<!-- 辅助操作:文本按钮 -->
<a class="mdc-button mdc-button--text" th:href="@{/help}">
  <span class="mdc-button__ripple"></span>
  <span class="mdc-button__label">需要帮助?</span>
</a>

MDC按钮的CSS自定义也非常直观:

css
/* MDC 按钮样式定制(教学示例) */
.mdc-button--raised {
  /* 使用CSS自定义属性实现主题色 */
  background-color: var(--cas-primary);
  color: #ffffff;
  border-radius: var(--cas-radius-md);
  height: 48px;
  padding: 0 32px;
  font-weight: 500;
  text-transform: none; /* 取消大写转换,适合中文场景 */
}

.mdc-button--raised:hover {
  background-color: var(--cas-primary-light);
}

.mdc-button--outlined {
  border-color: var(--cas-primary);
  color: var(--cas-primary);
  border-radius: var(--cas-radius-md);
}

.mdc-button--outlined:hover {
  background-color: rgba(
    var(--cas-primary-rgb), 0.08
  );
}

3.7 MDC输入框组件在登录页面的使用

MDC文本输入框(MDCTextField)是CAS 7.3登录页面中最核心的表单组件。它提供了"浮动标签"(Floating Label)效果——当输入框获得焦点或有值时,标签从输入框内部浮动到上方边框处。这种设计既节省了页面空间,又提供了清晰的输入提示。

以下是一个教学示例,展示了MDC输入框在CAS 7.3登录页面中的使用方式:

html
<!-- MDC 输入框在登录页面的使用(教学示例) -->

<!-- 用户名输入框 -->
<div class="mdc-text-field mdc-text-field--outlined">
  <input type="text"
         id="username"
         name="username"
         class="mdc-text-field__input"
         autocomplete="username"
         required
         aria-labelledby="username-label" />
  <div class="mdc-notched-outline">
    <div class="mdc-notched-outline__leading"></div>
    <div class="mdc-notched-outline__notch">
      <label for="username" id="username-label"
             class="mdc-floating-label">用户名</label>
    </div>
    <div class="mdc-notched-outline__trailing"></div>
  </div>
  <i class="mdi mdi-account mdc-text-field__icon"
     aria-hidden="true"></i>
</div>
<div class="mdc-text-field-helper-line">
  <div class="mdc-text-field-helper-text"
       aria-hidden="true"></div>
</div>

<!-- 密码输入框(带可见性切换) -->
<div class="mdc-text-field mdc-text-field--outlined
            mdc-text-field--with-trailing-icon">
  <input type="password"
         id="password"
         name="password"
         class="mdc-text-field__input"
         autocomplete="current-password"
         required
         aria-labelledby="password-label" />
  <div class="mdc-notched-outline">
    <div class="mdc-notched-outline__leading"></div>
    <div class="mdc-notched-outline__notch">
      <label for="password" id="password-label"
             class="mdc-floating-label">密码</label>
    </div>
    <div class="mdc-notched-outline__trailing"></div>
  </div>
  <button type="button"
          class="mdc-text-field__icon mdc-text-field__icon--trailing"
          data-toggle-password
          aria-label="切换密码可见性">
    <i class="mdi mdi-eye-off"></i>
  </button>
</div>
<div class="mdc-text-field-helper-line">
  <div class="mdc-text-field-helper-text"
       aria-hidden="true"></div>
</div>

MDC输入框的JavaScript初始化和验证逻辑:

javascript
// MDC 输入框初始化与验证(教学示例)
const usernameField = new mdc.textField.MDCTextField(
  document.querySelector('#username').closest('.mdc-text-field')
);
const passwordField = new mdc.textField.MDCTextField(
  document.querySelector('#password').closest('.mdc-text-field')
);

// 使用 MDC 的验证框架
usernameField.valid = false;
usernameField.useNativeValidation = false;
usernameField.setCustomValidity(
  function(value) {
    return value.trim().length > 0 ? '' : '请输入用户名';
  }
);

3.8 MDC卡片组件在登录页面的使用

MDC卡片(MDCCard)是CAS 7.3登录页面的容器组件。登录表单被包裹在一个MDC卡片中,卡片提供了圆角、阴影、背景色等视觉属性,使登录表单在页面中呈现出"浮起"的效果。

以下是一个教学示例,展示了MDC卡片在CAS 7.3登录页面中的使用方式:

html
<!-- MDC 卡片作为登录表单容器(教学示例) -->
<div class="mdc-card cas-login-card">
  <!-- 卡片头部:Logo 和标题 -->
  <div class="cas-login-card__header">
    <img th:src="@{/images/cas-logo.png}"
         alt="CAS Logo"
         class="cas-login-card__logo" />
    <h2 class="cas-login-card__title">统一认证平台</h2>
  </div>

  <!-- 分割线 -->
  <div class="mdc-divider"></div>

  <!-- 卡片主体:登录表单 -->
  <div class="cas-login-card__body">
    <form method="post" id="fm1"
          th:action="@{/login}">
      <!-- 错误消息区域 -->
      <div th:if="${#fields.hasErrors('*')}"
           class="mdc-card cas-error-banner"
           role="alert">
        <i class="mdi mdi-alert-circle"></i>
        <span th:errors="*">认证失败</span>
      </div>

      <!-- 用户名输入框 -->
      <!-- ... (MDC 输入框代码) ... -->

      <!-- 密码输入框 -->
      <!-- ... (MDC 输入框代码) ... -->

      <!-- 记住我复选框 -->
      <div class="mdc-form-field">
        <div class="mdc-checkbox">
          <input type="checkbox"
                 class="mdc-checkbox__native-control"
                 id="rememberMe"
                 name="rememberMe" />
          <div class="mdc-checkbox__background">
            <svg class="mdc-checkbox__checkmark"
                 viewBox="0 0 24 24">
              <path class="mdc-checkbox__checkmark-path"
                    fill="none"
                    d="M1.73,12.91 8.1,19.28 22.79,4.59" />
            </svg>
            <div class="mdc-checkbox__mixedmark"></div>
          </div>
          <div class="mdc-checkbox__ripple"></div>
        </div>
        <label for="rememberMe">记住我</label>
      </div>

      <!-- 登录按钮 -->
      <button class="mdc-button mdc-button--raised cas-login-btn"
              type="submit">
        <span class="mdc-button__ripple"></span>
        <span class="mdc-button__label">登录</span>
      </button>
    </form>
  </div>

  <!-- 卡片底部:辅助链接 -->
  <div class="mdc-divider"></div>
  <div class="cas-login-card__footer">
    <a th:href="@{/help}">需要帮助?</a>
    <span>|</span>
    <a th:href="@{/privacy}">隐私政策</a>
  </div>
</div>

MDC卡片的样式定制:

css
/* MDC 卡片样式定制(教学示例) */
.cas-login-card {
  max-width: 440px;
  width: 100%;
  margin: 0 auto;
  border-radius: var(--cas-radius-lg);
  box-shadow: var(--cas-shadow-lg);
  overflow: hidden;

  &__header {
    text-align: center;
    padding: 32px 24px 16px;

    .cas-login-card__logo {
      width: 80px;
      height: 80px;
      margin-bottom: 16px;
    }

    .cas-login-card__title {
      font-size: 20px;
      font-weight: 500;
      color: var(--cas-text-primary);
      margin: 0;
    }
  }

  &__body {
    padding: 24px;

    .cas-login-btn {
      width: 100%;
      margin-top: 8px;
    }
  }

  &__footer {
    display: flex;
    justify-content: center;
    gap: 12px;
    padding: 16px 24px;
    font-size: 14px;
    color: var(--cas-text-secondary);

    a {
      color: var(--cas-primary);
      text-decoration: none;

      &:hover {
        text-decoration: underline;
      }
    }
  }
}

四、Thymeleaf模板目录结构跨版本对比

4.1 CAS 5.3扁平化模板结构

CAS 5.3的Thymeleaf模板目录结构相对扁平,大部分模板文件直接存放在根模板目录下,没有按功能模块进行细分。这种结构在CAS功能较少的早期版本中是合理的,但随着CAS功能的不断扩展,扁平化结构的局限性逐渐显现。

CAS 5.3的模板目录大致结构如下(教学示例):

templates/
├── casLoginView.html          # 登录页面
├── casLogoutView.html         # 登出页面
├── casGenericSuccess.html     # 通用成功页面
├── casErrorView.html          # 错误页面
├── casConfirmView.html        # 确认页面
├── casRegisterView.html       # 注册页面
├── casForgotUsernameView.html # 忘记用户名
├── casForgotPasswordView.html # 忘记密码
├── casOauthConfirmView.html   # OAuth确认页面
├── casSamlPostView.html       # SAML POST页面
├── casConsentView.html        # 授权同意页面
├── casServiceErrorView.html   # 服务错误页面
├── fragments/
│   ├── cas-header.html        # 页面头部片段
│   ├── cas-footer.html        # 页面底部片段
│   └── cas-scripts.html       # 脚本片段
└── layout/
    └── default.html           # 默认布局模板

这种扁平化结构的特点是简单直观——所有模板文件一目了然,开发者可以快速找到需要修改的文件。但随着CAS支持的功能越来越多(OAuth2、SAML、OpenID Connect、WebAuthn、密码less认证等),模板文件数量急剧增加,扁平化结构变得难以管理。开发者需要在大量文件中寻找目标模板,文件命名冲突的风险也增加了。

4.2 CAS 6.6/7.3模块化模板结构

从CAS 6.6开始,模板目录结构进行了重大重构,采用了按功能模块分子目录的方式。CAS 7.3在此基础上进一步完善,新增了管理面板等模块的模板。

CAS 7.3的模板目录大致结构如下(教学示例):

templates/
├── casLoginView.html              # 主登录页面
├── casLogoutView.html             # 主登出页面
├── casGenericSuccess.html         # 通用成功页面
├── casErrorView.html              # 错误页面
├── acct/                          # 账户管理模块
│   ├── casAccountProfileView.html     # 账户资料
│   ├── casAccountActivityView.html    # 账户活动
│   └── casAccountPasswordView.html    # 密码修改
├── adaptive-authn/                # 自适应认证模块
│   ├── casAdaptiveAuthnDisplay.html   # 自适应认证展示
│   └── casRiskView.html              # 风险评估页面
├── delegated-authn/               # 委托认证模块
│   ├── casDelegatedAuthnView.html     # 委托认证页面
│   └── casDelegatedSelection.html     # 委托选择页面
├── passwordless/                  # 无密码认证模块
│   ├── casPasswordlessRegister.html   # 无密码注册
│   ├── casPasswordlessLogin.html      # 无密码登录
│   └── casPasswordlessWebAuthn.html   # WebAuthn无密码
├── gauth/                         # Google Authenticator模块
│   ├── casGoogleAuthnView.html        # GA认证页面
│   └── casGoogleAuthnRegister.html    # GA注册页面
├── webauthn/                      # WebAuthn模块
│   ├── casWebAuthnLoginView.html      # WebAuthn登录
│   ├── casWebAuthnRegisterView.html   # WebAuthn注册
│   └── casWebAuthnManageView.html     # WebAuthn管理
├── oauth2/                        # OAuth2模块
│   ├── casOAuth2ConfirmView.html      # OAuth2确认
│   ├── casOAuth2ApprovalView.html     # OAuth2审批
│   └── casOAuth2ErrorView.html        # OAuth2错误
├── oidc/                          # OpenID Connect模块
│   ├── casOidcConsentView.html        # OIDC同意页面
│   └── casOidcErrorView.html          # OIDC错误页面
├── saml2/                         # SAML2模块
│   ├── casSaml2PostView.html          # SAML2 POST
│   └── casSaml2RedirectView.html      # SAML2重定向
├── palantir/                      # 管理面板模块(7.3新增)
│   ├── casPalantirView.html           # 管理面板主页
│   ├── casPalantirDashboard.html      # 仪表盘
│   ├── casPalantirServices.html       # 服务管理
│   ├── casPalantirUsers.html          # 用户管理
│   ├── casPalantirSessions.html       # 会话管理
│   └── casPalantirConfiguration.html  # 配置管理
├── fragments/                     # 公共片段
│   ├── cas-header.html
│   ├── cas-footer.html
│   ├── cas-scripts.html
│   ├── loginForm.html                 # 登录表单片段
│   └── errorMessage.html              # 错误消息片段
└── layout/                        # 布局模板
    ├── default.html
    └── material.html                  # Material Design布局

这种模块化结构的优势是显而易见的。首先,按功能模块组织模板使得目录结构清晰有序,开发者可以根据功能快速定位目标模板。其次,不同模块的模板相互隔离,减少了命名冲突的可能性。第三,新增功能模块时只需添加新的子目录,不会影响现有结构。第四,模块化的目录结构也更利于团队协作——不同开发者可以负责不同模块的模板开发,减少代码冲突。

4.3 模板片段(Fragment)的复用机制

Thymeleaf的模板片段(Fragment)机制是CAS模板架构中的重要组成部分。通过将公共的UI元素(如页面头部、底部、导航栏、错误消息等)抽取为独立的片段文件,CAS实现了模板的高度复用。

以下是一个教学示例,展示了CAS 7.3中模板片段的定义和使用:

html
<!-- fragments/loginForm.html:登录表单片段(教学示例) -->
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<body>
  <div th:fragment="loginForm(formAction, errorMessage)">
    <form method="post"
          id="fm1"
          th:action="${formAction}">

      <!-- 错误消息 -->
      <div th:if="${errorMessage}"
           class="mdc-card cas-error-banner"
           role="alert">
        <i class="mdi mdi-alert-circle"></i>
        <span th:text="${errorMessage}">认证失败</span>
      </div>

      <!-- 用户名 -->
      <div class="mdc-text-field mdc-text-field--outlined"
           th:classappend="${#fields.hasErrors('username')} ? 'mdc-text-field--invalid'">
        <input type="text"
               id="username"
               name="username"
               class="mdc-text-field__input"
               th:value="${credential?.username}"
               autocomplete="username" />
        <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>

      <!-- 密码 -->
      <div class="mdc-text-field mdc-text-field--outlined
                  mdc-text-field--with-trailing-icon">
        <input type="password"
               id="password"
               name="password"
               class="mdc-text-field__input"
               autocomplete="current-password" />
        <div class="mdc-notched-outline">
          <div class="mdc-notched-outline__leading"></div>
          <div class="mdc-notched-outline__notch">
            <label for="password"
                   class="mdc-floating-label">密码</label>
          </div>
          <div class="mdc-notched-outline__trailing"></div>
        </div>
      </div>

      <!-- 登录按钮 -->
      <button class="mdc-button mdc-button--raised"
              type="submit">
        <span class="mdc-button__ripple"></span>
        <span class="mdc-button__label">登录</span>
      </button>
    </form>
  </div>
</body>
</html>

在主登录页面中引用这个片段:

html
<!-- casLoginView.html(教学示例) -->
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
  <title>统一认证平台 - 登录</title>
</head>
<body>
  <div class="cas-login-container">
    <div class="mdc-card cas-login-card">
      <!-- 引入登录表单片段 -->
      <div th:replace="~{fragments/loginForm :: loginForm(
        ${T(org.apereo.cas.web.support.WebUtils)
          .getAuthenticationResultCode(httpServletRequest)
          != null ? '/login?error=true' : '/login'},
        ${#strings.isEmpty(sessionScope
          ['SPRING_SECURITY_LAST_EXCEPTION']
          ?.message) ? null :
          sessionScope['SPRING_SECURITY_LAST_EXCEPTION']
          ?.message}
      )}"></div>
    </div>
  </div>
</body>
</html>

4.4 CAS 7.3新增palantir/管理面板模板

CAS 7.3引入了一个重要的新功能——Palantir管理面板。Palantir是CAS内置的管理控制台,提供了服务管理、用户会话管理、系统配置监控等功能。这个管理面板的模板位于templates/palantir/目录下。

Palantir管理面板的模板设计充分利用了MDC-Web的组件能力,包括DataTables用于数据表格展示、MDC标签页用于内容切换、MDC对话框用于确认操作等。

以下是一个教学示例,展示了Palantir管理面板主页的模板结构:

html
<!-- palantir/casPalantirView.html(教学示例) -->
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
  <title>CAS 管理面板</title>
  <!-- DataTables CSS -->
  <link rel="stylesheet"
        th:href="@{/webjars/datatables/1.13.6/css/jquery.dataTables.min.css}" />
</head>
<body>
  <div class="mdc-layout-grid">
    <div class="mdc-layout-grid__inner">

      <!-- 顶部导航标签页 -->
      <div class="mdc-layout-grid__cell--span-12">
        <nav class="mdc-tab-bar" role="tablist">
          <button class="mdc-tab mdc-tab--active"
                  role="tab" aria-selected="true"
                  data-tab="dashboard">
            <span class="mdc-tab__content">
              <i class="mdi mdi-view-dashboard mdc-tab__icon"></i>
              <span class="mdc-tab__text-label">仪表盘</span>
            </span>
            <span class="mdc-tab-indicator">
              <span class="mdc-tab-indicator__content mdc-tab-indicator__content--underline"></span>
            </span>
          </button>
          <button class="mdc-tab" role="tab"
                  data-tab="services">
            <span class="mdc-tab__content">
              <i class="mdi mdi-server-security mdc-tab__icon"></i>
              <span class="mdc-tab__text-label">服务管理</span>
            </span>
            <span class="mdc-tab-indicator">
              <span class="mdc-tab-indicator__content mdc-tab-indicator__content--underline"></span>
            </span>
          </button>
          <button class="mdc-tab" role="tab"
                  data-tab="sessions">
            <span class="mdc-tab__content">
              <i class="mdi mdi-account-group mdc-tab__icon"></i>
              <span class="mdc-tab__text-label">会话管理</span>
            </span>
            <span class="mdc-tab-indicator">
              <span class="mdc-tab-indicator__content mdc-tab-indicator__content--underline"></span>
            </span>
          </button>
        </nav>
      </div>

      <!-- 仪表盘面板 -->
      <div class="mdc-layout-grid__cell--span-12"
           id="tab-dashboard">
        <div class="mdc-layout-grid__inner">
          <!-- 统计卡片 -->
          <div class="mdc-layout-grid__cell--span-3">
            <div class="mdc-card mdc-card--outlined cas-stat-card">
              <div class="cas-stat-card__content">
                <i class="mdi mdi-account-check cas-stat-card__icon"></i>
                <div class="cas-stat-card__value"
                     th:text="${activeSessions}">1,234</div>
                <div class="cas-stat-card__label">活跃会话</div>
              </div>
            </div>
          </div>
          <!-- 更多统计卡片... -->
        </div>
      </div>

      <!-- 服务管理面板 -->
      <div class="mdc-layout-grid__cell--span-12"
           id="tab-services" style="display:none;">
        <table class="mdc-data-table__table"
               id="servicesTable">
          <thead>
            <tr class="mdc-data-table__header-row">
              <th class="mdc-data-table__header-cell">服务ID</th>
              <th class="mdc-data-table__header-cell">名称</th>
              <th class="mdc-data-table__header-cell">状态</th>
              <th class="mdc-data-table__header-cell">操作</th>
            </tr>
          </thead>
          <tbody>
            <tr th:each="service : ${services}"
                class="mdc-data-table__row">
              <td class="mdc-data-table__cell"
                  th:text="${service.id}">...</td>
              <td class="mdc-data-table__cell"
                  th:text="${service.name}">...</td>
              <td class="mdc-data-table__cell">
                <span class="mdc-chip"
                      th:classappend="${service.enabled}
                        ? 'cas-chip--success'
                        : 'cas-chip--danger'">
                  <span class="mdc-chip__text"
                        th:text="${service.enabled}
                          ? '启用' : '禁用'">...</span>
                </span>
              </td>
              <td class="mdc-data-table__cell">
                <button class="mdc-icon-button"
                        data-action="edit"
                        th:data-id="${service.id}">
                  <i class="mdi mdi-pencil"></i>
                </button>
              </td>
            </tr>
          </tbody>
        </table>
      </div>

    </div>
  </div>

  <!-- DataTables JS -->
  <script th:src="@{/webjars/datatables/1.13.6/js/jquery.dataTables.min.js}"></script>
  <script>
    // DataTables 初始化(教学示例)
    $(document).ready(function() {
      $('#servicesTable').DataTable({
        language: {
          search: '搜索:',
          lengthMenu: '每页 _MENU_ 条',
          info: '第 _START_ 至 _END_ 条,共 _TOTAL_ 条',
          paginate: {
            first: '首页',
            last: '末页',
            next: '下一页',
            previous: '上一页'
          }
        },
        pageLength: 10,
        order: [[1, 'asc']]
      });
    });
  </script>
</body>
</html>

五、前端依赖管理策略

5.1 Gradle中的前端依赖声明

CAS项目同时支持Gradle和Maven两种构建工具。在CAS 7.3中,前端依赖主要通过WebJar机制在构建脚本中声明。WebJar将前端库打包为标准的JAR文件,使得前端依赖可以像Java依赖一样通过构建工具管理。

以下是一个教学示例,展示了CAS 7.3中Gradle构建脚本的前端依赖声明:

groovy
// build.gradle(教学示例,基于CAS 7.3源码简化)

dependencies {
    // ==================== CAS 核心 ====================
    implementation "org.apereo.cas:cas-server-webapp:${casServerVersion}"

    // ==================== Material Design 前端组件 ====================
    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"

    // ==================== Bootstrap 5 ====================
    implementation "org.webjars.npm:bootstrap:5.3.2"

    // ==================== jQuery ====================
    implementation "org.webjars:jquery:3.7.1"

    // ==================== DataTables ====================
    implementation "org.webjars.npm:datatables.net:1.13.6"
    implementation "org.webjars.npm:datatables.net-bs5:1.13.6"
}

Gradle的WebJar依赖管理有几个优势。首先,版本号集中管理,可以通过Gradle的版本目录(Version Catalog)统一控制所有依赖的版本。其次,依赖解析和传递依赖处理由Gradle自动完成,开发者不需要手动管理依赖树。第三,WebJar的依赖声明与Java依赖声明方式一致,降低了学习成本。

5.2 Maven中的前端依赖声明

对于使用Maven构建的CAS项目,前端依赖的声明方式类似:

xml
<!-- pom.xml(教学示例,基于CAS 7.3源码简化) -->
<dependencies>
    <!-- Material Design 前端组件 -->
    <dependency>
        <groupId>org.webjars.npm</groupId>
        <artifactId>material-components-web</artifactId>
        <version>14.0.0</version>
    </dependency>
    <dependency>
        <groupId>org.webjars.npm</groupId>
        <artifactId>mdi__font</artifactId>
        <version>7.4.47</version>
    </dependency>
    <dependency>
        <groupId>org.webjars.npm</groupId>
        <artifactId>normalize.css</artifactId>
        <version>8.0.1</version>
    </dependency>

    <!-- Bootstrap 5 -->
    <dependency>
        <groupId>org.webjars.npm</groupId>
        <artifactId>bootstrap</artifactId>
        <version>5.3.2</version>
    </dependency>

    <!-- jQuery -->
    <dependency>
        <groupId>org.webjars</groupId>
        <artifactId>jquery</artifactId>
        <version>3.7.1</version>
    </dependency>

    <!-- DataTables -->
    <dependency>
        <groupId>org.webjars.npm</groupId>
        <artifactId>datatables.net</artifactId>
        <version>1.13.6</version>
    </dependency>
</dependencies>

Maven的WebJar依赖管理与Gradle类似,但XML的声明方式更为冗长。在实际项目中,建议使用Maven的<dependencyManagement>来集中管理版本号,避免在多个模块中重复声明版本。

5.3 WebJar vs 静态资源的选择策略

在CAS项目中,前端依赖可以通过两种方式管理:WebJar和静态资源(直接放在src/main/resources/static/目录下)。两种方式各有优劣,需要根据具体场景选择。

WebJar的优势

  • 版本管理:通过构建工具统一管理版本,升级方便。
  • 依赖解析:自动处理传递依赖,避免遗漏。
  • 缓存友好:WebJar资源路径中包含版本号,便于浏览器缓存和CDN缓存策略。
  • 安全更新:通过构建工具的安全扫描插件,可以及时发现和更新有漏洞的依赖。
  • 多项目复用:在多模块项目中,WebJar依赖可以在不同模块间共享。

静态资源的优势

  • 完全控制:可以直接修改前端库的源码,进行深度定制。
  • 无构建依赖:不需要通过构建工具解析,构建速度更快。
  • 灵活性高:可以使用任何前端构建工具(Webpack、Vite等)进行预处理。
  • 离线友好:不需要从Maven Central或npm仓库下载依赖。

CAS项目的推荐策略

对于CAS项目,推荐以下策略:

  1. 标准前端库(如MDC-Web、Bootstrap、jQuery等):使用WebJar管理,享受版本管理和依赖解析的便利。
  2. 自定义样式和脚本(如custom.css、custom.js):使用静态资源方式,放在src/main/resources/static/css/src/main/resources/static/js/目录下。
  3. 需要深度定制的前端库:将前端库的源码下载到本地,通过前端构建工具处理后放入静态资源目录。
  4. 企业内部前端组件:打包为内部WebJar,通过私有Maven/npm仓库分发。

5.4 前端构建工具的集成

虽然CAS 7.3本身不强制使用前端构建工具,但对于需要深度定制前端资源的项目,集成前端构建工具是一个值得考虑的选择。

Gradle + Node.js插件

CAS 7.3基于Gradle构建,可以通过Gradle插件集成Node.js前端构建流程:

groovy
// build.gradle(教学示例)
plugins {
    id "com.github.node-gradle.node" version "7.0.1"
}

node {
    version = '18.17.0'
    npmVersion = '9.6.7'
    workDir = file("${project.buildDir}/nodejs")
    npmWorkDir = file("${project.buildDir}/npm")
}

task buildFrontend(type: NpmTask) {
    args = ['run', 'build']
    dependsOn npmInstall
}

task copyFrontend(type: Copy) {
    from 'dist'
    into "${buildDir}/resources/main/static"
    dependsOn buildFrontend
}

processResources {
    dependsOn copyFrontend
}

package.json配置示例

json
{
  "name": "cas-frontend",
  "version": "1.0.0",
  "scripts": {
    "dev": "vite",
    "build": "vite build",
    "preview": "vite preview"
  },
  "devDependencies": {
    "vite": "^5.0.0",
    "sass": "^1.69.0",
    "autoprefixer": "^10.4.16",
    "postcss": "^8.4.32",
    "cssnano": "^6.0.1",
    "terser": "^5.24.0"
  }
}

这种集成方式使得CAS项目可以使用现代前端构建工具链——Vite进行开发和构建,Sass编写样式,PostCSS和Autoprefixer处理浏览器兼容性,cssnano和Terser进行资源压缩。构建完成后,前端资源被复制到Spring Boot的静态资源目录,最终打包到CAS的JAR文件中。


六、自定义CSS/JS的最佳实践

6.1 custom.css覆盖CAS默认样式的策略

在CAS项目中,custom.css是开发者最常用的样式定制文件。通过合理地使用CSS选择器和CSS自定义属性,开发者可以在不修改CAS核心代码的情况下实现深度的样式定制。

CSS选择器优先级策略

覆盖CAS默认样式时,选择器的优先级是一个需要特别注意的问题。如果选择器的优先级不够高,自定义样式可能不会生效。以下是几种提高选择器优先级的方法:

css
/* custom.css 覆盖策略(教学示例) */

/* 策略1:使用更具体的选择器 */
/* 低优先级(可能不生效) */
.mdc-button--raised {
  background-color: #1565c0;
}

/* 高优先级(推荐) */
#login .mdc-button--raised {
  background-color: #1565c0;
}

/* 策略2:使用 !important(谨慎使用) */
.mdc-button--raised {
  background-color: #1565c0 !important;
}

/* 策略3:利用CSS自定义属性(最佳实践) */
:root {
  --cas-primary: #1565c0;
}

推荐优先使用策略1和策略3。策略1通过增加选择器的上下文(如添加父级选择器#login)来提高优先级,既有效又不引入!important。策略3通过修改CSS自定义属性的值来间接改变组件样式,这是最优雅的方式,前提是CAS的原始样式使用了CSS自定义属性。

CSS自定义属性的主题方案

CAS 7.3的MDC-Web组件内部大量使用了CSS自定义属性,这为主题定制提供了极大的便利。开发者可以通过覆盖这些属性来实现全局的主题切换:

css
/* 基于 CSS 自定义属性的主题方案(教学示例) */

/* 默认主题 */
:root {
  --mdc-theme-primary: #1565c0;
  --mdc-theme-secondary: #00897b;
  --mdc-theme-surface: #ffffff;
  --mdc-theme-background: #f5f7fa;
  --mdc-theme-error: #c62828;

  /* 自定义扩展变量 */
  --cas-font-family: 'Inter', 'PingFang SC',
    'Microsoft YaHei', sans-serif;
  --cas-login-bg: linear-gradient(
    135deg, #1565c0 0%, #00897b 100%
  );
  --cas-card-shadow: 0 8px 32px rgba(0, 0, 0, 0.12);
  --cas-transition-speed: 0.3s;
}

/* 深色主题 */
[data-theme="dark"] {
  --mdc-theme-primary: #90caf9;
  --mdc-theme-secondary: #80cbc4;
  --mdc-theme-surface: #1e1e1e;
  --mdc-theme-background: #121212;
  --mdc-theme-error: #ef5350;

  --cas-login-bg: linear-gradient(
    135deg, #1a237e 0%, #004d40 100%
  );
  --cas-card-shadow: 0 8px 32px rgba(0, 0, 0, 0.4);
}

这种基于CSS自定义属性的主题方案有几个显著优势。第一,修改主题只需要改变CSS自定义属性的值,不需要修改组件的样式规则。第二,可以通过JavaScript在运行时动态切换主题(如支持用户选择浅色/深色模式)。第三,CSS自定义属性具有继承性,可以在不同的DOM层级上覆盖,实现局部主题定制。

6.2 custom.js扩展前端交互的模式

custom.js是CAS项目中扩展前端交互逻辑的主要入口。在实际项目中,custom.js通常需要处理以下几类交互逻辑:

表单增强

javascript
// 表单增强(教学示例)
document.addEventListener('DOMContentLoaded', function() {

  // ==================== 自动聚焦用户名输入框 ====================
  var usernameInput = document.getElementById('username');
  if (usernameInput && !usernameInput.value) {
    // 延迟聚焦,确保MDC组件已初始化
    setTimeout(function() {
      usernameInput.focus();
    }, 300);
  }

  // ==================== Enter键导航 ====================
  var inputs = document.querySelectorAll(
    '#fm1 input[type="text"], #fm1 input[type="password"]'
  );
  inputs.forEach(function(input, index) {
    input.addEventListener('keydown', function(e) {
      if (e.key === 'Enter' && index < inputs.length - 1) {
        e.preventDefault();
        inputs[index + 1].focus();
      }
    });
  });

  // ==================== 密码强度指示器 ====================
  var passwordInput = document.getElementById('password');
  if (passwordInput) {
    var strengthBar = document.createElement('div');
    strengthBar.className = 'cas-password-strength';
    strengthBar.innerHTML =
      '<div class="cas-password-strength__bar">' +
      '<div class="cas-password-strength__fill"></div>' +
      '</div>' +
      '<span class="cas-password-strength__text"></span>';
    passwordInput.closest('.mdc-text-field')
      .after(strengthBar);

    passwordInput.addEventListener('input', function() {
      var strength = calculatePasswordStrength(this.value);
      updateStrengthBar(strength);
    });
  }

  function calculatePasswordStrength(password) {
    var score = 0;
    if (password.length >= 8) score++;
    if (password.length >= 12) score++;
    if (/[A-Z]/.test(password)) score++;
    if (/[0-9]/.test(password)) score++;
    if (/[^A-Za-z0-9]/.test(password)) score++;
    return Math.min(score, 5);
  }

  function updateStrengthBar(score) {
    var fill = document.querySelector(
      '.cas-password-strength__fill'
    );
    var text = document.querySelector(
      '.cas-password-strength__text'
    );
    var levels = [
      { width: '0%', color: '#eee', label: '' },
      { width: '20%', color: '#f44336', label: '非常弱' },
      { width: '40%', color: '#ff9800', label: '弱' },
      { width: '60%', color: '#ffc107', label: '一般' },
      { width: '80%', color: '#8bc34a', label: '强' },
      { width: '100%', color: '#4caf50', label: '非常强' }
    ];
    var level = levels[score];
    fill.style.width = level.width;
    fill.style.backgroundColor = level.color;
    text.textContent = level.label;
    text.style.color = level.color;
  }
});

消息提示系统

javascript
// 基于 MDC Snackbar 的消息提示系统(教学示例)
var CasMessage = {
  show: function(message, type, duration) {
    type = type || 'info';
    duration = duration || 5000;

    var snackbarEl = document.querySelector('.mdc-snackbar');
    if (!snackbarEl) {
      snackbarEl = document.createElement('div');
      snackbarEl.className = 'mdc-snackbar';
      snackbarEl.innerHTML =
        '<div class="mdc-snackbar__surface">' +
        '<div class="mdc-snackbar__label" role="status" aria-live="polite"></div>' +
        '</div>';
      document.body.appendChild(snackbarEl);
      new mdc.snackbar.MDCSnackbar(snackbarEl);
    }

    var icons = {
      info: 'mdi-information',
      success: 'mdi-check-circle',
      warning: 'mdi-alert',
      error: 'mdi-alert-circle'
    };

    snackbarEl.querySelector('.mdc-snackbar__label').textContent =
      message;
    window.casSnackbar.open();
  },

  success: function(msg) { this.show(msg, 'success'); },
  error: function(msg) { this.show(msg, 'error'); },
  warning: function(msg) { this.show(msg, 'warning'); },
  info: function(msg) { this.show(msg, 'info'); }
};

6.3 主题定制的CSS变量方案

CAS 7.3的主题定制方案基于CSS自定义属性(CSS Variables),这是一种原生CSS特性,不需要任何预处理器或构建工具的支持。CSS自定义属性可以在运行时通过JavaScript动态修改,这为动态主题切换提供了基础。

以下是一个完整的教学示例,展示了CAS 7.3中基于CSS变量的主题定制方案:

css
/* theme-variables.css(教学示例) */

/* ==================== 颜色系统 ==================== */
:root {
  /* 主色板 */
  --cas-color-primary-50: #e3f2fd;
  --cas-color-primary-100: #bbdefb;
  --cas-color-primary-200: #90caf9;
  --cas-color-primary-300: #64b5f6;
  --cas-color-primary-400: #42a5f5;
  --cas-color-primary-500: #2196f3;
  --cas-color-primary-600: #1e88e5;
  --cas-color-primary-700: #1976d2;
  --cas-color-primary-800: #1565c0;
  --cas-color-primary-900: #0d47a1;

  /* 语义颜色映射 */
  --cas-primary: var(--cas-color-primary-700);
  --cas-primary-light: var(--cas-color-primary-400);
  --cas-primary-dark: var(--cas-color-primary-900);
  --cas-on-primary: #ffffff;

  /* 次要色 */
  --cas-secondary: #00897b;
  --cas-secondary-light: #4db6ac;
  --cas-secondary-dark: #00695c;

  /* 语义色 */
  --cas-success: #2e7d32;
  --cas-warning: #ed6c02;
  --cas-error: #d32f2f;
  --cas-info: #0288d1;

  /* 中性色 */
  --cas-neutral-0: #ffffff;
  --cas-neutral-50: #fafafa;
  --cas-neutral-100: #f5f5f5;
  --cas-neutral-200: #eeeeee;
  --cas-neutral-300: #e0e0e0;
  --cas-neutral-400: #bdbdbd;
  --cas-neutral-500: #9e9e9e;
  --cas-neutral-600: #757575;
  --cas-neutral-700: #616161;
  --cas-neutral-800: #424242;
  --cas-neutral-900: #212121;

  /* ==================== 排版系统 ==================== */
  --cas-font-family-sans: 'Inter', -apple-system,
    BlinkMacSystemFont, 'Segoe UI', 'PingFang SC',
    'Microsoft YaHei', sans-serif;
  --cas-font-family-mono: 'JetBrains Mono',
    'Fira Code', 'Source Code Pro', monospace;

  --cas-font-size-xs: 0.75rem;    /* 12px */
  --cas-font-size-sm: 0.875rem;   /* 14px */
  --cas-font-size-base: 1rem;     /* 16px */
  --cas-font-size-lg: 1.125rem;   /* 18px */
  --cas-font-size-xl: 1.25rem;    /* 20px */
  --cas-font-size-2xl: 1.5rem;    /* 24px */
  --cas-font-size-3xl: 1.875rem;  /* 30px */

  --cas-font-weight-normal: 400;
  --cas-font-weight-medium: 500;
  --cas-font-weight-semibold: 600;
  --cas-font-weight-bold: 700;

  --cas-line-height-tight: 1.25;
  --cas-line-height-normal: 1.5;
  --cas-line-height-relaxed: 1.75;

  /* ==================== 间距系统 ==================== */
  --cas-space-0: 0;
  --cas-space-1: 0.25rem;  /* 4px */
  --cas-space-2: 0.5rem;   /* 8px */
  --cas-space-3: 0.75rem;  /* 12px */
  --cas-space-4: 1rem;     /* 16px */
  --cas-space-5: 1.5rem;   /* 24px */
  --cas-space-6: 2rem;     /* 32px */
  --cas-space-8: 3rem;     /* 48px */
  --cas-space-10: 4rem;    /* 64px */
  --cas-space-12: 6rem;    /* 96px */

  /* ==================== 圆角系统 ==================== */
  --cas-radius-none: 0;
  --cas-radius-sm: 0.25rem;   /* 4px */
  --cas-radius-md: 0.5rem;    /* 8px */
  --cas-radius-lg: 0.75rem;   /* 12px */
  --cas-radius-xl: 1rem;      /* 16px */
  --cas-radius-2xl: 1.5rem;   /* 24px */
  --cas-radius-full: 9999px;

  /* ==================== 阴影系统 ==================== */
  --cas-shadow-xs: 0 1px 2px rgba(0, 0, 0, 0.05);
  --cas-shadow-sm: 0 1px 3px rgba(0, 0, 0, 0.1),
    0 1px 2px rgba(0, 0, 0, 0.06);
  --cas-shadow-md: 0 4px 6px rgba(0, 0, 0, 0.1),
    0 2px 4px rgba(0, 0, 0, 0.06);
  --cas-shadow-lg: 0 10px 15px rgba(0, 0, 0, 0.1),
    0 4px 6px rgba(0, 0, 0, 0.05);
  --cas-shadow-xl: 0 20px 25px rgba(0, 0, 0, 0.1),
    0 10px 10px rgba(0, 0, 0, 0.04);

  /* ==================== 动画系统 ==================== */
  --cas-transition-fast: 150ms ease;
  --cas-transition-normal: 300ms ease;
  --cas-transition-slow: 500ms ease;

  /* ==================== Z-Index 层级 ==================== */
  --cas-z-dropdown: 1000;
  --cas-z-sticky: 1020;
  --cas-z-fixed: 1030;
  --cas-z-modal-backdrop: 1040;
  --cas-z-modal: 1050;
  --cas-z-popover: 1060;
  --cas-z-tooltip: 1070;
}

JavaScript动态主题切换的实现:

javascript
// theme-switcher.js(教学示例)
var ThemeManager = {
  // 可用主题列表
  themes: {
    'light': {
      '--cas-primary': '#1976d2',
      '--cas-primary-light': '#42a5f5',
      '--cas-primary-dark': '#0d47a1',
      '--mdc-theme-surface': '#ffffff',
      '--mdc-theme-background': '#f5f7fa',
      '--cas-neutral-900': '#212121',
      '--cas-neutral-0': '#ffffff'
    },
    'dark': {
      '--cas-primary': '#90caf9',
      '--cas-primary-light': '#e3f2fd',
      '--cas-primary-dark': '#42a5f5',
      '--mdc-theme-surface': '#1e1e1e',
      '--mdc-theme-background': '#121212',
      '--cas-neutral-900': '#e0e0e0',
      '--cas-neutral-0': '#1e1e1e'
    }
  },

  // 获取当前主题
  getCurrentTheme: function() {
    return localStorage.getItem('cas-theme') || 'light';
  },

  // 应用主题
  applyTheme: function(themeName) {
    var theme = this.themes[themeName];
    if (!theme) return;

    var root = document.documentElement;
    Object.keys(theme).forEach(function(prop) {
      root.style.setProperty(prop, theme[prop]);
    });

    root.setAttribute('data-theme', themeName);
    localStorage.setItem('cas-theme', themeName);
  },

  // 初始化
  init: function() {
    this.applyTheme(this.getCurrentTheme());
  },

  // 切换主题
  toggle: function() {
    var current = this.getCurrentTheme();
    var next = current === 'light' ? 'dark' : 'light';
    this.applyTheme(next);
  }
};

// 页面加载时初始化主题
ThemeManager.init();

七、生产环境前端优化建议

7.1 资源压缩与合并

在生产环境中,前端资源的体积直接影响页面的加载速度。CAS 7.3的WebJar依赖已经提供了压缩版本(如material-components-web.min.css),但自定义的CSS和JS文件也需要进行压缩处理。

CSS压缩

对于自定义的CSS文件,可以通过以下方式进行压缩:

  1. 在线工具:使用CSSNano、CleanCSS等在线压缩工具。
  2. 构建工具集成:在Gradle构建流程中集成CSS压缩插件。
  3. 前端构建工具:使用Vite、Webpack等前端构建工具的CSS压缩功能。

以下是一个教学示例,展示了在Gradle中集成CSS压缩的方式:

groovy
// build.gradle(教学示例)
task minifyCss(type: Copy) {
    from 'src/main/resources/static/css'
    into "${buildDir}/resources/main/static/css"
    rename { filename ->
        filename.replace('.css', '.min.css')
    }
    filter { line ->
        // 简单的CSS压缩:移除注释和多余空白
        line.replaceAll(/\/\*[\s\S]*?\*\//, '')
            .replaceAll(/\s+/, ' ')
            .trim()
    }
}

JavaScript压缩

JavaScript的压缩通常包括代码混淆、变量名缩短、移除注释和空白等步骤。常用的JavaScript压缩工具包括Terser、UglifyJS、Closure Compiler等。

groovy
// build.gradle(教学示例)
task minifyJs(type: Exec) {
    commandLine 'npx', 'terser',
        'src/main/resources/static/js/custom.js',
        '-o',
        "${buildDir}/resources/main/static/js/custom.min.js",
        '--compress', '--mangle'
}

资源合并策略

CAS 7.3的页面通常需要加载多个CSS和JS文件。在生产环境中,将多个文件合并为一个可以减少HTTP请求数量,提升加载性能:

html
<!-- 开发环境:多个文件(教学示例) -->
<link rel="stylesheet" href="/css/normalize.css" />
<link rel="stylesheet" href="/css/material-components-web.min.css" />
<link rel="stylesheet" href="/css/mdi.min.css" />
<link rel="stylesheet" href="/css/custom.css" />

<!-- 生产环境:合并为一个文件 -->
<link rel="stylesheet" href="/css/cas-bundle.min.css" />

7.2 CDN部署策略

CDN(Content Delivery Network)是提升前端资源加载速度的有效手段。通过将静态资源部署到CDN,可以利用CDN的边缘节点缓存和就近访问能力,显著降低用户的访问延迟。

CDN部署方案

CAS项目的CDN部署有以下几种方案:

  1. WebJar CDN映射:使用jsDelivr或cdnjs等公共CDN服务,将WebJar的依赖映射到CDN地址。
  2. 自建CDN:将CAS的静态资源上传到企业自建的CDN或云服务商的CDN(如阿里云CDN、腾讯云CDN、AWS CloudFront等)。
  3. Spring Boot CDN配置:通过Spring Boot的配置属性,将静态资源的URL前缀指向CDN地址。

以下是一个教学示例,展示了Spring Boot中CDN配置的方式:

yaml
# application.yml(教学示例)
cas:
  theme:
    # CDN 基础路径
    cdn-base-url: https://cdn.example.com/cas

spring:
  web:
    resources:
      static-locations: classpath:/static/
  mvc:
    resource-chain:
      enabled: true
      strategy:
        content:
          enabled: true
          paths: /css/**,/js/**,/images/**

在Thymeleaf模板中,可以通过配置变量来动态切换本地资源和CDN资源:

html
<!-- Thymeleaf 模板中的 CDN 适配(教学示例) -->
<link rel="stylesheet"
      th:href="${@casThemeResolver.cdnEnabled}
        ? 'https://cdn.example.com/cas/css/cas-bundle.min.css'
        : @{/css/cas-bundle.min.css}" />

CDN缓存策略

CDN缓存策略的设置对性能优化至关重要。以下是一些推荐的缓存策略:

nginx
# Nginx CDN 缓存配置(教学示例)
location ~* \.(css|js)$ {
    expires 1y;
    add_header Cache-Control "public, immutable";
    add_header Vary "Accept-Encoding";
}

location ~* \.(png|jpg|jpeg|gif|ico|svg|webp)$ {
    expires 6M;
    add_header Cache-Control "public";
}

location ~* \.(html)$ {
    expires 1h;
    add_header Cache-Control "public, must-revalidate";
}

对于CSS和JS等不经常变化的资源,设置较长的缓存时间(如1年),并通过文件名中的哈希值(如cas-bundle.a1b2c3.min.css)来实现缓存失效。对于HTML等可能频繁变化的资源,设置较短的缓存时间,并使用must-revalidate指令确保缓存新鲜度。

7.3 浏览器兼容性策略

CAS作为企业级SSO解决方案,需要兼容各种浏览器环境。CAS 7.3基于Spring Boot 3和Java 21,其前端资源主要面向现代浏览器,但在实际部署中可能还需要兼容一些旧版浏览器。

目标浏览器范围

CAS 7.3的推荐目标浏览器范围如下:

浏览器最低版本说明
Chrome90+最近两个主要版本
Firefox88+最近两个主要版本
Safari14+macOS和iOS
Edge90+Chromium内核版本
IE不支持CAS 7.3不再支持IE

Polyfill策略

对于需要支持的浏览器特性,可以通过Polyfill来补充:

html
<!-- Polyfill 加载(教学示例) -->
<script th:if="${#arrays.contains(supportedBrowsers, 'legacy')}"
        src="https://polyfill.io/v3/polyfill.min.js?features=es6,fetch,IntersectionObserver">
</script>

CSS兼容性处理

使用Autoprefixer自动添加浏览器前缀:

css
/* 输入(教学示例) */
.cas-login-card {
  display: flex;
  user-select: none;
  backdrop-filter: blur(10px);
}

/* Autoprefixer 输出 */
.cas-login-card {
  display: -webkit-box;
  display: -ms-flexbox;
  display: flex;
  -webkit-user-select: none;
  -moz-user-select: none;
  -ms-user-select: none;
  user-select: none;
  -webkit-backdrop-filter: blur(10px);
  backdrop-filter: blur(10px);
}

Browserslist配置

通过Browserslist配置来统一管理目标浏览器范围,Autoprefixer、Babel等工具都会读取这个配置:

json
// package.json 中的 Browserslist 配置(教学示例)
{
  "browserslist": [
    "> 0.5%",
    "last 2 versions",
    "not dead",
    "not IE 11"
  ]
}

渐进增强与优雅降级

CAS 7.3的前端设计遵循渐进增强的原则——核心功能(如表单提交、认证流程)在所有目标浏览器中都能正常工作,而增强功能(如动画效果、涟漪反馈等)只在支持这些特性的浏览器中启用:

css
/* 渐进增强示例(教学示例) */

/* 基础样式——所有浏览器都支持 */
.cas-login-card {
  background: var(--cas-primary);
  color: white;
  border-radius: 8px;
  padding: 24px;
}

/* 增强样式——支持 backdrop-filter 的浏览器 */
@supports (backdrop-filter: blur(10px)) {
  .cas-login-card {
    background: rgba(21, 101, 192, 0.8);
    backdrop-filter: blur(10px);
    -webkit-backdrop-filter: blur(10px);
  }
}

/* 增强样式——支持 CSS 动画的浏览器 */
@supports (animation: fade-in 0.3s ease) {
  .cas-login-card {
    animation: fade-in 0.3s ease;
  }
}

@keyframes fade-in {
  from { opacity: 0; transform: translateY(10px); }
  to { opacity: 1; transform: translateY(0); }
}

7.4 性能监控与持续优化

生产环境的前端优化不是一次性的工作,而是需要持续监控和迭代改进的过程。以下是一些推荐的性能监控和优化策略。

Core Web Vitals监控

Google的Core Web Vitals是衡量用户体验的核心指标,包括:

  • LCP(Largest Contentful Paint):最大内容绘制时间,衡量页面主要内容加载速度。目标值:2.5秒以内。
  • FID(First Input Delay):首次输入延迟,衡量页面对用户交互的响应速度。目标值:100毫秒以内。
  • CLS(Cumulative Layout Shift):累积布局偏移,衡量页面视觉稳定性。目标值:0.1以内。

对于CAS登录页面,可以通过以下方式优化这些指标:

html
<!-- LCP 优化:预加载关键资源(教学示例) -->
<link rel="preload" as="style"
      th:href="@{/css/cas-bundle.min.css}" />
<link rel="preload" as="script"
      th:href="@{/js/material.min.js}" />

<!-- CLS 优化:为图片和动态内容预留空间 -->
<img th:src="@{/images/cas-logo.png}"
     alt="CAS Logo"
     width="80" height="80" />

<!-- FID 优化:延迟加载非关键脚本 -->
<script th:src="@{/js/analytics.js}"
        defer></script>

资源加载优化

html
<!-- 资源加载优化策略(教学示例) -->

<!-- 1. 关键CSS内联 -->
<style>
  /* 内联关键渲染路径CSS */
  .cas-login-container {
    min-height: 100vh;
    display: flex;
    align-items: center;
    justify-content: center;
  }
</style>

<!-- 2. 非关键CSS异步加载 -->
<link rel="preload" as="style"
      th:href="@{/css/cas-bundle.min.css}"
      onload="this.onload=null;this.rel='stylesheet'" />
<noscript>
  <link rel="stylesheet"
        th:href="@{/css/cas-bundle.min.css}" />
</noscript>

<!-- 3. JavaScript 异步/延迟加载 -->
<script th:src="@{/js/material.min.js}"
        defer></script>
<script th:src="@{/js/custom.js}"
        defer></script>

<!-- 4. 预连接到CDN域名 -->
<link rel="preconnect"
      href="https://cdn.example.com" />
<link rel="dns-prefetch"
      href="https://cdn.example.com" />

HTTP/2 Server Push

如果CAS部署在支持HTTP/2的服务器上,可以利用Server Push来主动推送关键资源,减少请求延迟:

nginx
# Nginx HTTP/2 Server Push 配置(教学示例)
location /login {
    http2_push /css/cas-bundle.min.css;
    http2_push /js/material.min.js;
    http2_push /images/cas-logo.png;
}

八、CAS前端框架选型的经验总结

8.1 从CAS变迁中学到的选型原则

回顾CAS前端技术栈的三代变迁,我们可以总结出几条适用于企业级项目的前端框架选型原则。

原则一:够用就好,避免过度工程

CAS 5.3的多框架并存是一个典型的"过度工程"案例。虽然每个框架都有其适用场景,但在CAS这个以表单交互为主的项目中,同时使用AngularJS、Knockout.js、jQuery UI等多个框架是不必要的。CAS 6.6的精简证明了"少即是多"——jQuery + Bootstrap的组合足以满足CAS的大部分前端需求。

在实际项目中,我们应该根据项目的实际需求来选择前端技术栈,而不是盲目追求"最新"或"最全"。一个简单的管理后台可能只需要jQuery + Bootstrap,而一个复杂的SPA应用才需要React或Vue。

原则二:关注长期维护性

前端技术栈的选择不仅要考虑当前的开发效率,还要考虑长期的维护成本。CAS 5.3选择AngularJS就是一个教训——AngularJS在2018年就已经停止了积极开发,CAS团队不得不在后续版本中将其移除,这给用户带来了迁移成本。

在选择前端框架时,应该关注以下几个因素:

  • 框架的社区活跃度和维护状态
  • 框架的版本更新频率和长期支持计划
  • 框架的生态系统成熟度(插件、工具、文档等)
  • 框架的学习曲线和人才市场供给

原则三:优先选择原生Web标准

CAS 7.3选择MDC-Web的一个重要原因是它基于Web Components标准。Web Components是W3C的标准规范,不依赖于任何特定的JavaScript框架。这意味着即使未来MDC-Web不再维护,基于Web Components标准的组件仍然可以在现代浏览器中正常工作。

在可能的情况下,优先使用原生Web标准(如Web Components、CSS自定义属性、ES Modules、Fetch API等)可以降低对特定框架的依赖,提高代码的长期可维护性。

原则四:渐进式升级,避免大爆炸式重构

CAS从5.3到6.6再到7.3的演进是渐进式的——每个版本都在前一个版本的基础上进行改进,而不是完全推翻重来。这种渐进式的升级策略降低了迁移风险,使得用户可以在自己的节奏下完成升级。

在实际项目中,如果需要进行前端技术栈的迁移,建议采用渐进式的方式——先在新页面或新功能中使用新的技术栈,逐步替换旧页面,最终完成整体迁移。

8.2 Material Design集成的实战经验

CAS 7.3集成Material Design的实践为我们提供了一些有价值的实战经验。

MDC-Web与Bootstrap的共存策略

CAS 7.3同时使用了MDC-Web和Bootstrap 5,这两个UI框架在某些方面存在重叠(如栅格系统、按钮、表单等)。在实际使用中,需要明确划分两个框架的职责边界,避免样式冲突。

推荐的职责划分策略:

  • Bootstrap 5:负责页面布局(栅格系统)、响应式断点、基础排版。
  • MDC-Web:负责交互组件(按钮、输入框、卡片、对话框等)。
css
/* MDC-Web 与 Bootstrap 共存的样式隔离(教学示例) */

/* 使用 Bootstrap 的栅格系统 */
.cas-page-container {
  @extend .container;
  @extend .mt-5;
}

/* 使用 MDC-Web 的交互组件 */
.cas-form-input {
  /* 不使用 Bootstrap 的 .form-control */
  /* 使用 MDC-Web 的 .mdc-text-field */
}

MDC-Web组件的按需加载

MDC-Web的完整包体积较大(CSS约200KB,JS约500KB,压缩后)。在生产环境中,建议只加载实际使用的组件:

javascript
// 按需导入 MDC-Web 组件(教学示例)
// 而不是 import 'material-components-web';

import { MDCTextField } from '@material/textfield';
import { MDCButton } from '@material/button';
import { MDCRipple } from '@material/ripple';
import { MDCCard } from '@material/card';
import { MDCDialog } from '@material/dialog';
import { MDCSnackbar } from '@material/snackbar';
import { MDCTabBar } from '@material/tab-bar';

通过按需加载,可以将MDC-Web的实际加载体积减少50%以上。

Material Design的本地化适配

Material Design最初是为英文环境设计的,在中文环境下需要进行一些适配:

css
/* Material Design 中文适配(教学示例) */

/* 中文字体栈 */
:root {
  --mdc-typography-font-family: 'PingFang SC',
    'Microsoft YaHei', 'Helvetica Neue',
    Helvetica, Arial, sans-serif;
}

/* 中文文本不需要大写转换 */
.mdc-button__label {
  text-transform: none !important;
  letter-spacing: normal !important;
}

/* 中文输入框的行高调整 */
.mdc-text-field__input {
  line-height: 1.5;
}

/* 中文环境下的间距微调 */
.mdc-text-field {
  height: 56px; /* 中文输入需要更大的高度 */
}

8.3 对CAS使用者的建议

基于CAS前端框架变迁的分析,我们对CAS的使用者提出以下建议:

对于正在使用CAS 5.3的用户

如果你的CAS实例仍在使用5.3版本,建议尽快规划升级。CAS 5.3已经进入了生命周期末期,安全更新和技术支持将逐渐减少。升级到CAS 6.6或7.3时,前端部分的迁移是最大的工作量——你需要将Semantic-UI的组件替换为Bootstrap/MDC-Web的组件,将AngularJS/Knockout.js的逻辑重写为jQuery或原生JavaScript。建议先在测试环境中完成迁移,充分测试后再部署到生产环境。

对于正在使用CAS 6.6的用户

CAS 6.6是一个相对稳定的版本,如果你对当前的UI满意,可以暂时保持不变。但如果计划升级到CAS 7.3,建议提前熟悉Material Design的设计规范和MDC-Web的组件用法。可以从小范围的主题定制开始,逐步引入MDC-Web的组件,为后续的全面升级做准备。

对于新部署CAS 7.3的用户

如果你正在新部署CAS 7.3,建议充分利用Material Design的设计体系来构建登录页面。从CSS自定义属性开始定义你的主题变量,使用MDC-Web的标准组件构建表单和交互,通过custom.css和custom.js进行品牌定制。同时,建议建立前端资源的版本管理和自动化构建流程,确保后续升级的顺畅。


总结与展望

CAS前端框架的三代变迁——从5.3的多框架并存,到6.6的轻量化精简,再到7.3的Material Design集成——不仅是一部技术演进史,更是一部关于"如何为企业级项目选择前端技术栈"的实践指南。

CAS 5.3告诉我们,前端技术栈不是越多越好。虽然AngularJS、Semantic-UI、Knockout.js、D3.js、jQuery UI各有其优势,但在一个以表单交互为主的SSO项目中,多框架并存带来的复杂性远超其带来的收益。CAS 6.6的精简是一次必要的"瘦身",它证明了jQuery + Bootstrap这对"老搭档"在企业级项目中的可靠性和高效性。

CAS 7.3的Material Design集成则展示了企业级项目如何在不牺牲稳定性的前提下拥抱现代前端技术。MDC-Web基于Web Components标准,不依赖特定的JavaScript框架,这与CAS作为Java后端项目的定位高度契合。CSS自定义属性的主题方案、Thymeleaf模板片段的复用机制、custom.css/custom.js/material.js的扩展体系,共同构成了一个灵活而强大的前端定制框架。

展望未来,CAS的前端技术栈可能会继续沿着以下方向演进:

第一,Web Components的深入应用。随着Web Components标准的进一步完善和浏览器支持的普及,CAS可能会更多地采用Web Components来构建前端组件,实现更好的封装性和可复用性。

第二,服务端渲染(SSR)与流式渲染的探索。虽然CAS的登录页面不需要复杂的客户端路由,但服务端渲染和流式渲染技术可以进一步提升首屏加载性能,特别是在网络条件较差的环境下。

第三,无障碍访问(Accessibility)的持续增强。随着全球范围内对数字无障碍访问要求的提高,CAS可能会在前端组件中进一步加强ARIA支持、键盘导航、屏幕阅读器兼容等方面的能力。

第四,AI辅助的前端定制。随着AI技术的发展,CAS可能会引入AI辅助的主题生成、布局优化、用户体验分析等能力,降低前端定制的门槛。

无论CAS的前端技术栈如何演进,其核心目标始终不变——为用户提供安全、可靠、美观的认证体验。作为CAS的使用者和贡献者,我们需要持续关注前端技术的发展趋势,同时保持对CAS项目设计理念的深入理解,才能在技术变迁中做出正确的决策。


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

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

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