Appearance
CAS Overlay Template 5.x 版本指南
官方介绍
CAS Overlay Template 5.x 是 Apereo CAS 项目的官方推荐部署方式,基于 Spring Boot 1.5.x 构建。5.x 版本是 CAS 项目的重要版本系列,引入了许多现代化特性和改进,包括对 Java 8+ 的全面支持、更好的云原生支持以及改进的模块化架构。这个版本标志着 CAS 向现代化微服务架构的演进。
CAS (Central Authentication Service) 5.x 是一个企业级的单点登录系统,支持多种认证协议,包括 SAML、OAuth2、OpenID Connect、WS-Federation、SCIM 等。
版本特性
核心特性
- Spring Boot 1.5+ 集成: 完全基于 Spring Boot 1.5.x,提供现代化部署和配置
- Java 8+ 支持: 全面支持 Java 8 及更高版本
- 模块化架构: 更好的插件化扩展机制
- 协议支持: 支持 CAS、SAML、OAuth2、OpenID Connect、WS-Federation、SCIM
- 多认证源: 支持 LDAP、数据库、REST、JWT、Radius、OAuth、SAML 等多种认证方式
- 安全性: 内置高级安全机制,支持 MFA(多因素认证)
- 配置管理: 基于 Spring Cloud Config 的集中配置管理
- 云原生支持: 原生支持 Kubernetes、Docker 等云原生环境
5.x 版本演进
- 5.0.x: 初始版本,从传统架构向 Spring Boot 迁移
- 5.1.x: 增强了配置管理,改进了安全性
- 5.2.x: 支持更多认证协议,改进了性能和监控
- 5.3.x: 增强了 Docker 支持,改进了开发体验
- 5.4.x: 最终版本,增加了对 JWT、OAuth2 的更好支持
- 5.5.x: 预计未来版本,将进一步增强云原生支持
官方获取地址
bash
# 通过 Git Clone 获取 CAS Overlay Template 5.x
git clone -b 5.3.x https://github.com/apereo/cas-overlay-template.git cas-overlay-5.x1
2
2
或者指定特定版本:
bash
git clone -b 5.3.6 https://github.com/apereo/cas-overlay-template.git cas-overlay-5.3.61
环境要求
- Java: JDK 11 或更高版本(推荐 JDK 11/17)
- Maven: 3.6.3 或更高版本
- Git: 用于版本控制
- 操作系统: 跨平台支持(Linux、Windows、macOS)
部署方法
1. 克隆项目
bash
git clone -b 5.3.x https://github.com/apereo/cas-overlay-template.git cas-overlay-5.x
cd cas-overlay-5.x1
2
2
2. 构建项目
bash
# 清理并构建项目
./mvnw clean compile
# 构建 WAR 包
./mvnw clean package
# 直接运行(开发模式)
./mvnw clean spring-boot:run
# 或者使用包装器脚本
bash build.sh package
bash build.sh run1
2
3
4
5
6
7
8
9
10
11
12
2
3
4
5
6
7
8
9
10
11
12
3. 配置文件说明
CAS 6.x 使用以下配置文件:
application.properties
properties
# 服务器配置
server.port=8443
server.servlet.context-path=/cas
# CAS 服务器配置
cas.server.name=https://cas.example.org:8443
cas.server.prefix=https://cas.example.org:8443/cas
# 服务注册配置
cas.serviceRegistry.initFromJson=true
cas.serviceRegistry.location=classpath:/services
# 认证配置
cas.authn.accept.users=casuser::Mellon
# 日志配置
logging.config=classpath:log4j2.xml
# Ticket 配置
cas.ticket.st.timeToKillInSeconds=10
cas.ticket.tgt.timeToKillInSeconds=7200
# Redis 配置
cas.ticket.registry.redis.host=localhost
cas.ticket.registry.redis.port=6379
# 集群配置
cas.cluster.machineName=localhost
cas.cluster.hostname=localhost
# 监控配置
management.endpoints.web.exposure.include=health,info,metrics,cas
# 安全配置
cas.authn.pm.enabled=true
cas.authn.pm.reset.mail.subject=Password Reset Request1
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
4. 使用 Docker 部署
bash
# 构建 Docker 镜像
docker build -t cas-overlay:5.x .
# 运行容器
docker run -p 8443:8443 -v /path/to/config:/etc/cas/config cas-overlay:5.x
# 使用 Docker Compose
docker-compose -f docker-compose.yml up -d1
2
3
4
5
6
7
8
2
3
4
5
6
7
8
二次开发
自定义认证处理器
创建自定义认证处理器示例:
src/main/java/com/example/CustomAuthenticationHandler.java
java
package com.example;
import lombok.extern.slf4j.Slf4j;
import org.apereo.cas.authentication.AuthenticationHandlerExecutionResult;
import org.apereo.cas.authentication.Credential;
import org.apereo.cas.authentication.exceptions.AccountDisabledException;
import org.apereo.cas.authentication.handler.support.AbstractPreAndPostProcessingAuthenticationHandler;
import org.apereo.cas.authentication.principal.PrincipalFactory;
import org.apereo.cas.services.ServicesManager;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Component;
import javax.security.auth.login.AccountLockedException;
import javax.security.auth.login.AccountNotFoundException;
import javax.security.auth.login.FailedLoginException;
import java.security.GeneralSecurityException;
@Slf4j
@Component("customAuthenticationHandler")
public class CustomAuthenticationHandler extends AbstractPreAndPostProcessingAuthenticationHandler {
public CustomAuthenticationHandler() {
super();
}
@Autowired
public CustomAuthenticationHandler(@Qualifier("servicesManager") final ServicesManager servicesManager,
@Qualifier("defaultPrincipalFactory") final PrincipalFactory principalFactory) {
super(null, servicesManager, principalFactory, null);
}
@Override
protected AuthenticationHandlerExecutionResult doAuthentication(final Credential credential, final String originalPassword) throws GeneralSecurityException {
String username = credential.getId();
String password = originalPassword;
log.info("Attempting authentication for user: {}", username);
// 自定义认证逻辑
if (authenticateUser(username, password)) {
log.info("Authentication successful for user: {}", username);
return createHandlerResult(credential, this.principalFactory.createPrincipal(username), null);
} else {
log.warn("Authentication failed for user: {}", username);
throw new FailedLoginException("Authentication failed");
}
}
private boolean authenticateUser(String username, String password) {
// 实现您的认证逻辑
return "admin".equals(username) && "password".equals(password);
}
@Override
public boolean supports(Credential credential) {
return 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
57
58
59
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
57
58
59
自定义属性解析器
src/main/java/com/example/CustomPersonAttributeDao.java
java
package com.example;
import lombok.extern.slf4j.Slf4j;
import org.apereo.services.persondir.IPersonAttributes;
import org.apereo.services.persondir.support.NamedStubPersonAttributeDao;
import org.springframework.stereotype.Component;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@Slf4j
@Component("customPersonAttributeDao")
public class CustomPersonAttributeDao extends NamedStubPersonAttributeDao {
@Override
public IPersonAttributes getPerson(String uid) {
log.info("Retrieving attributes for user: {}", uid);
Map<String, List<Object>> attributes = new HashMap<>();
// 添加自定义属性
attributes.put("mail", new ArrayList<>(List.of(uid + "@example.org")));
attributes.put("displayName", new ArrayList<>(List.of("User " + uid)));
attributes.put("memberOf", new ArrayList<>(List.of("GROUP1", "GROUP2")));
attributes.put("department", new ArrayList<>(List.of("IT")));
attributes.put("employeeType", new ArrayList<>(List.of("Full-time")));
attributes.put("telephoneNumber", new ArrayList<>(List.of("+1-555-123-4567")));
log.debug("Returning attributes for user: {} - {}", uid, attributes.keySet());
return new org.apereo.services.persondir.support.StubPersonAttributeTO(uid, attributes);
}
}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
自定义事件监听器
src/main/java/com/example/CustomCasEventListener.java
java
package com.example;
import lombok.extern.slf4j.Slf4j;
import org.apereo.cas.authentication.adaptive.AdaptiveAuthenticationPrincipalResolver;
import org.apereo.cas.event.CasEventRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.autoconfigure.RefreshAutoConfiguration;
import org.springframework.context.event.EventListener;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Component;
import java.util.concurrent.CompletableFuture;
@Slf4j
@Component("customCasEventListener")
public class CustomCasEventListener {
@Autowired(required = false)
private CasEventRepository casEventRepository;
@EventListener
@Async
public CompletableFuture<Void> handleCasAuthenticationSuccessEvent(
final org.apereo.cas.support.events.authentication.CasAuthenticationSuccessEvent event) {
log.info("Authentication success for: {}", event.getCredential().getId());
// 记录认证成功事件
if (casEventRepository != null) {
casEventRepository.save(event);
}
// 异步处理认证成功逻辑
return CompletableFuture.runAsync(() -> {
try {
Thread.sleep(100); // 模拟异步处理
log.debug("Processed authentication success for: {}", event.getCredential().getId());
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
log.error("Interrupted while processing authentication success", e);
}
});
}
@EventListener
public void handleCasAuthenticationFailureEvent(
final org.apereo.cas.support.events.authentication.CasAuthenticationFailureEvent event) {
log.warn("Authentication failure for: {}, reason: {}",
event.getCredential().getId(), event.getException().getMessage());
}
}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
自定义控制器
src/main/java/com/example/CustomController.java
java
package com.example;
import lombok.extern.slf4j.Slf4j;
import org.apereo.cas.CasProtocolConstants;
import org.apereo.cas.authentication.principal.Principal;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.servlet.http.HttpServletRequest;
import java.util.HashMap;
import java.util.Map;
@Slf4j
@RestController
public class CustomController {
@GetMapping("/api/userinfo")
public Map<String, Object> getUserInfo(HttpServletRequest request) {
Map<String, Object> response = new HashMap<>();
// 从请求中获取认证的主体信息
Principal principal = (Principal) request.getAttribute(CasProtocolConstants.CONST_PARAM_PRINCIPAL);
if (principal != null) {
response.put("username", principal.getId());
response.put("attributes", principal.getAttributes());
response.put("message", "Successfully authenticated user");
log.info("User {} accessed userinfo endpoint", principal.getId());
} else {
response.put("error", "User not authenticated");
log.warn("Unauthenticated access attempt to userinfo endpoint");
}
return response;
}
@GetMapping("/api/health")
public Map<String, Object> getHealthInfo() {
Map<String, Object> response = new HashMap<>();
response.put("status", "UP");
response.put("version", "5.x");
response.put("timestamp", System.currentTimeMillis());
response.put("component", "Custom Health Endpoint");
log.debug("Health check performed");
return response;
}
}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
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
自定义视图
在 src/main/resources/templates 目录下可以自定义 CAS 的视图模板。例如,创建自定义登录页面:
src/main/resources/templates/casLoginView.html
html
<!DOCTYPE html>
<html xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout"
xmlns:th="http://www.thymeleaf.org"
layout:decorate="~{layout}">
<head>
<meta charset="UTF-8"/>
<title>CAS Login</title>
<link rel="stylesheet" th:href="@{/css/cas.css}" />
<style>
.login-card {
max-width: 400px;
margin: 2rem auto;
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
border-radius: 8px;
}
.form-control:focus {
border-color: #007bff;
box-shadow: 0 0 0 0.2rem rgba(0, 123, 255, 0.25);
}
.btn-primary {
background-color: #007bff;
border-color: #007bff;
}
</style>
</head>
<body>
<main role="main" class="container mt-3 mb-3">
<div layout:fragment="content">
<div class="card login-card">
<div class="card-header bg-primary text-white text-center">
<h4><i class="fas fa-lock"></i> Secure Login</h4>
</div>
<div class="card-body">
<form method="post" th:object="${credential}" id="fm1" class="fm-v clearfix">
<div class="form-group row">
<label for="username" class="col-sm-12 col-form-label">Username:</label>
<div class="col-sm-12">
<div class="input-group">
<div class="input-group-prepend">
<span class="input-group-text"><i class="fas fa-user"></i></span>
</div>
<input class="form-control required" id="username" size="25" tabindex="1"
type="text" th:field="*{username}"
placeholder="Enter your username"
autocomplete="off" />
</div>
</div>
</div>
<div class="form-group row">
<label for="password" class="col-sm-12 col-form-label">Password:</label>
<div class="col-sm-12">
<div class="input-group">
<div class="input-group-prepend">
<span class="input-group-text"><i class="fas fa-lock"></i></span>
</div>
<input class="form-control required" id="password" size="25" tabindex="2"
type="password" th:field="*{password}"
placeholder="Enter your password"
autocomplete="off" />
</div>
</div>
</div>
<div class="form-group row">
<div class="col-sm-12">
<input type="hidden" name="execution" th:value="${flowExecutionKey}" />
<input type="hidden" name="_eventId" value="submit" />
<input type="hidden" name="geolocation" />
<button class="btn btn-primary btn-block" name="submit" accesskey="l"
th:text="#{screen.welcome.button.login}"
tabindex="6" type="submit">
Sign In
</button>
</div>
</div>
<div class="form-group row">
<div class="col-sm-12 text-center">
<small>
<a href="#" th:text="#{screen.button.forgotpwd}" th:href="@{/forgotPassword}">Forgot Password?</a> |
<a href="#" th:text="#{screen.button.register}" th:href="@{/register}">Register</a>
</small>
</div>
</div>
</form>
</div>
</div>
<div class="text-center mt-3">
<small class="text-muted">
Powered by <a href="https://apereo.github.io/cas" target="_blank">Apereo CAS</a>
</small>
</div>
</div>
</main>
</body>
</html>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
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
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
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
配置示例
LDAP 认证配置
application.properties
properties
# LDAP 配置
cas.authn.ldap[0].type=AUTHENTICATED
cas.authn.ldap[0].ldapUrl=ldaps://ldap.example.org:636
cas.authn.ldap[0].baseDn=ou=people,dc=example,dc=org
cas.authn.ldap[0].userFilter=uid={user}
cas.authn.ldap[0].bindDn=cn=admin,dc=example,dc=org
cas.authn.ldap[0].bindCredential=password
cas.authn.ldap[0].principalAttributeList=uid,sn,givenName,mail,memberOf,employeeType
cas.authn.ldap[0].principalIdAttribute=uid
cas.authn.ldap[0].providerClass=org.ldaptive.provider.unboundid.UnboundIDProvider
cas.authn.ldap[0].useSsl=true
cas.authn.ldap[0].enhanceWithEntryResolver=true
# 连接池配置
cas.authn.ldap[0].minPoolSize=3
cas.authn.ldap[0].maxPoolSize=10
cas.authn.ldap[0].validateOnCheckout=true
cas.authn.ldap[0].validatePeriodically=true
cas.authn.ldap[0].blockWaitTime=3000
cas.authn.ldap[0].idleTime=6000
cas.authn.ldap[0].prunePeriod=6000
cas.authn.ldap[0].validatePeriod=6000
# 属性配置
cas.authn.ldap[0].returnAttributes=uid,cn,sn,givenName,mail,telephoneNumber,memberOf,employeeType1
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
数据库认证配置
application.properties
properties
# 数据库认证配置
cas.authn.jdbc.query[0].name=EXAMPLE_QUERY
cas.authn.jdbc.query[0].sql=SELECT password FROM users WHERE username=?
cas.authn.jdbc.query[0].healthQuery=SELECT 1
cas.authn.jdbc.query[0].isolateInternalQueries=false
cas.authn.jdbc.query[0].url=jdbc:mysql://localhost:3306/cas
cas.authn.jdbc.query[0].failFast=true
cas.authn.jdbc.query[0].isolationLevelName=ISOLATION_READ_COMMITTED
cas.authn.jdbc.query[0].dialect=org.hibernate.dialect.MySQL8Dialect
cas.authn.jdbc.query[0].leakThreshold=10
cas.authn.jdbc.query[0].propagationBehaviorName=PROPAGATION_REQUIRED
cas.authn.jdbc.query[0].batchSize=1
cas.authn.jdbc.query[0].user=database_user
cas.authn.jdbc.query[0].password=database_password
cas.authn.jdbc.query[0].driverClass=com.mysql.cj.jdbc.Driver
cas.authn.jdbc.query[0].idleTimeout=5000
# 连接池配置
cas.authn.jdbc.query[0].pool.suspension=false
cas.authn.jdbc.query[0].pool.minSize=6
cas.authn.jdbc.query[0].pool.maxSize=18
cas.authn.jdbc.query[0].pool.maxWait=2000
# 属性查询配置
cas.authn.jdbc.query[1].name=ATTRIBUTES_QUERY
cas.authn.jdbc.query[1].sql=SELECT uid, mail, department, telephoneNumber FROM users WHERE username=?
cas.authn.jdbc.query[1].fieldAttributeMappings.uid=uid
cas.authn.jdbc.query[1].fieldAttributeMappings.mail=mail
cas.authn.jdbc.query[1].fieldAttributeMappings.department=department
cas.authn.jdbc.query[1].fieldAttributeMappings.telephoneNumber=telephoneNumber
cas.authn.jdbc.query[1].singleRow=true1
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
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
OAuth2 客户端配置
application.properties
properties
# OAuth2 客户端配置
cas.authn.oauth.google.clientId=GOOGLE_CLIENT_ID
cas.authn.oauth.google.clientSecret=GOOGLE_CLIENT_SECRET
cas.authn.oauth.facebook.clientId=FACEBOOK_CLIENT_ID
cas.authn.oauth.facebook.clientSecret=FACEBOOK_CLIENT_SECRET
cas.authn.oauth.github.clientId=GITHUB_CLIENT_ID
cas.authn.oauth.github.clientSecret=GITHUB_CLIENT_SECRET
# OAuth2 服务配置
cas.authn.oauth.userProfileViewType=NESTED
cas.authn.oauth.callbackUrl=https://cas.example.org:8443/cas/oauth2/callbackAuthorize
# OAuth2 客户端注册
cas.authn.oauth.clients[0].clientId=web-client
cas.authn.oauth.clients[0].clientSecret=web-client-secret
cas.authn.oauth.clients[0].name=Web Client Application
cas.authn.oauth.clients[0].redirectUri=https://client.example.org/callback
cas.authn.oauth.clients[0].scopes=openid,profile,email
cas.authn.oauth.clients[0].grantTypes=authorization_code,refresh_token,password,client_credentials
cas.authn.oauth.clients[0].responseTypes=code,token,id_token1
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
OpenID Connect 配置
application.properties
properties
# OpenID Connect 配置
cas.authn.oidc.issuer=https://cas.example.org:8443/cas/oidc
cas.authn.oidc.jwksFile=file:/etc/cas/config/keystore.jwks
# OIDC 客户端注册
cas.authn.oidc.clients[0].clientId=oidc-client
cas.authn.oidc.clients[0].clientSecret=oidc-client-secret
cas.authn.oidc.clients[0].name=OIDC Client Application
cas.authn.oidc.clients[0].redirectUri=https://client.example.org/oidc-callback
cas.authn.oidc.clients[0].scopes=openid,profile,email,address,phone,offline_access
cas.authn.oidc.clients[0].grantTypes=authorization_code,refresh_token,password,client_credentials
cas.authn.oidc.clients[0].responseTypes=code,token,id_token
# JWT 配置
cas.authn.oidc.jwksEncryptionAlg=A256GCM
cas.authn.oidc.jwksEncryptionEncoding=BASE641
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
Demo 示例
简单的健康检查端点示例
创建一个简单的健康检查端点来演示 CAS 集成:
src/main/java/com/example/HealthCheckController.java
java
package com.example;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.actuate.health.Health;
import org.springframework.boot.actuate.health.HealthIndicator;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.HashMap;
import java.util.Map;
@Slf4j
@RestController
public class HealthCheckController implements HealthIndicator {
private boolean externalServiceAvailable = true;
@Override
public Health health() {
if (externalServiceAvailable) {
Map<String, String> details = new HashMap<>();
details.put("status", "UP");
details.put("component", "External Service Mock");
details.put("response_time", "50ms");
return Health.up().withDetails(details).build();
} else {
return Health.down().withDetail("reason", "External service unavailable").build();
}
}
@GetMapping("/api/custom-health")
public Map<String, Object> customHealthCheck() {
Map<String, Object> response = new HashMap<>();
response.put("status", "UP");
response.put("timestamp", System.currentTimeMillis());
response.put("version", "5.x");
response.put("uptime", "Running for several hours");
response.put("custom_component", "Healthy");
log.info("Custom health check performed");
return response;
}
@GetMapping("/api/system-info")
public Map<String, Object> systemInfo() {
Map<String, Object> response = new HashMap<>();
Runtime runtime = Runtime.getRuntime();
response.put("total_memory", runtime.totalMemory());
response.put("free_memory", runtime.freeMemory());
response.put("max_memory", runtime.maxMemory());
response.put("available_processors", runtime.availableProcessors());
response.put("java_version", System.getProperty("java.version"));
response.put("os_name", System.getProperty("os.name"));
response.put("cas_version", "5.x");
log.debug("System info retrieved");
return response;
}
}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
57
58
59
60
61
62
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
57
58
59
60
61
62
自定义服务注册示例
src/main/java/com/example/CustomServiceRegistry.java
java
package com.example;
import lombok.extern.slf4j.Slf4j;
import org.apereo.cas.services.RegisteredService;
import org.apereo.cas.services.ServiceRegistry;
import org.springframework.stereotype.Component;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.stream.Collectors;
@Slf4j
@Component("customServiceRegistry")
public class CustomServiceRegistry implements ServiceRegistry {
private final List<RegisteredService> registeredServices = new ArrayList<>();
@Override
public RegisteredService save(RegisteredService registeredService) {
log.info("Saving registered service: {}", registeredService.getName());
// 检查是否已存在相同 ID 的服务
RegisteredService existing = findServiceById(registeredService.getId());
if (existing != null) {
registeredServices.remove(existing);
}
registeredServices.add(registeredService);
log.info("Saved service: {} with ID: {}", registeredService.getName(), registeredService.getId());
return registeredService;
}
@Override
public boolean delete(RegisteredService registeredService) {
log.info("Deleting registered service: {}", registeredService.getName());
return registeredServices.remove(registeredService);
}
@Override
public Collection<RegisteredService> load() {
log.info("Loading {} registered services", registeredServices.size());
return new ArrayList<>(registeredServices);
}
@Override
public RegisteredService findServiceById(long id) {
return registeredServices.stream()
.filter(service -> service.getId() == id)
.findFirst()
.orElse(null);
}
@Override
public RegisteredService findServiceById(String id) {
return registeredServices.stream()
.filter(service -> service.getServiceId().equals(id))
.findFirst()
.orElse(null);
}
@Override
public long size() {
return registeredServices.size();
}
public List<RegisteredService> findByServiceName(String serviceName) {
return registeredServices.stream()
.filter(service -> service.getName().contains(serviceName))
.collect(Collectors.toList());
}
}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
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
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
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
常见问题和解决方案
1. SSL 证书问题
如果遇到 SSL 证书问题,可以配置信任证书:
properties
# SSL 配置
cas.httpClient.trustStore.file=file:/etc/cas/truststore.jks
cas.httpClient.trustStore.psw=changeit
cas.httpClient.followRedirects=true
cas.httpClient.readTimeout=5000
cas.httpClient.connectionTimeout=50001
2
3
4
5
6
2
3
4
5
6
2. 认证失败问题
检查日志文件以确定认证失败的原因:
bash
tail -f /path/to/catalina.out1
3. 内存溢出问题
增加 JVM 内存设置:
bash
export JAVA_OPTS="-Xms1g -Xmx2g -XX:MetaspaceSize=256m -XX:MaxMetaspaceSize=512m"1
4. 服务注册问题
确保服务注册文件位于正确位置:
properties
cas.serviceRegistry.initFromJson=true
cas.serviceRegistry.location=classpath:/services
cas.serviceRegistry.schedule.repeatInterval=120
cas.serviceRegistry.managementType=DEFAULT1
2
3
4
2
3
4
服务注册文件示例 (src/main/resources/services/HTTPSandIMAPS-10000001.json):
json
{
"@class" : "org.apereo.cas.services.RegexRegisteredService",
"serviceId" : "^(https|imaps)://.*",
"name" : "HTTPS and IMAPS",
"id" : 10000001,
"evaluationOrder" : 10000,
"description" : "This service definition authorizes all application urls that support HTTPS and IMAPS protocols.",
"attributeReleasePolicy" : {
"@class" : "org.apereo.cas.services.ReturnAllAttributeReleasePolicy",
"authorizedToReleaseCredentialPassword" : false,
"authorizedToReleaseProxyGrantingTicket" : false
},
"accessStrategy" : {
"@class" : "org.apereo.cas.services.DefaultRegisteredServiceAccessStrategy",
"enabled" : true,
"ssoEnabled" : true,
"requireAllAttributes" : false
}
}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
性能优化
连接池配置
properties
# 数据库连接池
cas.jdbc.pool.platform=mysql
cas.jdbc.pool.minSize=2
cas.jdbc.pool.maxSize=10
cas.jdbc.pool.maxWait=2000
cas.jdbc.pool.acquireIncrement=2
cas.jdbc.pool.idleConnectionTestPeriod=30
cas.jdbc.pool.maxIdleTime=300
# LDAP 连接池
cas.authn.ldap[0].minPoolSize=3
cas.authn.ldap[0].maxPoolSize=10
cas.authn.ldap[0].validateOnCheckout=true
cas.authn.ldap[0].validatePeriodically=true
cas.authn.ldap[0].blockWaitTime=3000
cas.authn.ldap[0].idleTime=6000
cas.authn.ldap[0].prunePeriod=6000
cas.authn.ldap[0].validatePeriod=60001
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
缓存配置
properties
# Redis 缓存配置
cas.ticket.registry.redis.host=localhost
cas.ticket.registry.redis.port=6379
cas.ticket.registry.redis.password=
cas.ticket.registry.redis.database=0
cas.ticket.registry.redis.timeout=2000
cas.ticket.registry.redis.useSsl=false
cas.ticket.registry.redis.usePool=true
# Redis 连接池配置
cas.ticket.registry.redis.pool.maxTotal=20
cas.ticket.registry.redis.pool.maxIdle=8
cas.ticket.registry.redis.pool.minIdle=0
cas.ticket.registry.redis.pool.maxWaitMillis=3000
cas.ticket.registry.redis.pool.testOnCreate=false
cas.ticket.registry.redis.pool.testOnBorrow=false
cas.ticket.registry.redis.pool.testOnReturn=false
cas.ticket.registry.redis.pool.testWhileIdle=true
# EhCache 配置
cas.cache.core.tickets.ehcache.maxElementsOnDisk=1000
cas.cache.core.tickets.ehcache.maxElementsInMemory=100
cas.cache.core.tickets.ehcache.eternal=false
cas.cache.core.tickets.ehcache.timeToLiveSeconds=7200
cas.cache.core.tickets.ehcache.timeToIdleSeconds=18001
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
JVM 优化
bash
# 推荐的 JVM 参数
JAVA_OPTS="-Xms1g -Xmx2g \
-XX:MetaspaceSize=256m -XX:MaxMetaspaceSize=512m \
-XX:+UseG1GC -XX:MaxGCPauseMillis=200 \
-XX:+UseStringDeduplication \
-XX:+OptimizeStringConcat \
-Djava.awt.headless=true \
-Dfile.encoding=UTF-8"1
2
3
4
5
6
7
8
2
3
4
5
6
7
8
升级指南
从 6.2.x 升级到 6.3.x:
- 备份现有配置
- 更新 pom.xml 中的 CAS 版本
- 检查废弃的配置属性
- 更新服务注册文件格式
- 测试认证流程
- 部署到生产环境
Docker 部署示例
Dockerfile
dockerfile
FROM eclipse-temurin:11-jre-focal
LABEL maintainer="Your Name <your.email@example.com>"
RUN apt-get update && apt-get install -y \
ca-certificates \
&& rm -rf /var/lib/apt/lists/*
WORKDIR /etc/cas/config
COPY . /etc/cas/
EXPOSE 8443
CMD ["bash", "-c", "java -Djava.security.egd=file:/dev/./urandom -jar /etc/cas/webapp/cas.war"]
HEALTHCHECK --interval=30s --timeout=3s --start-period=60s --retries=3 \
CMD curl -f https://localhost:8443/cas/actuator/health || exit 11
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
Docker Compose 配置
yaml
version: '3.8'
services:
cas:
build: .
ports:
- "8443:8443"
volumes:
- ./config:/etc/cas/config
environment:
- CAS_SERVER_NAME=https://localhost:8443
- CAS_SERVER_PREFIX=https://localhost:8443/cas
- CAS_TICKET_REGISTRY_REDIS_HOST=redis
- SPRING_PROFILES_ACTIVE=redis
depends_on:
- redis
- ldap
- mysql
redis:
image: redis:6-alpine
ports:
- "6379:6379"
volumes:
- redis_data:/data
ldap:
image: osixia/openldap:1.4.0
ports:
- "389:389"
- "636:636"
environment:
- LDAP_ORGANISATION=Example Inc.
- LDAP_DOMAIN=example.org
- LDAP_ADMIN_PASSWORD=adminpassword
volumes:
- ldap_data:/var/lib/ldap
- ldap_config:/etc/ldap/slapd.d
mysql:
image: mysql:8.0
ports:
- "3306:3306"
environment:
- MYSQL_ROOT_PASSWORD=rootpassword
- MYSQL_DATABASE=cas
- MYSQL_USER=casuser
- MYSQL_PASSWORD=caspass
volumes:
- mysql_data:/var/lib/mysql
volumes:
redis_data:
ldap_data:
ldap_config:
mysql_data: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
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
Kubernetes 部署示例
Deployment 配置
yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: cas-server
labels:
app: cas-server
spec:
replicas: 2
selector:
matchLabels:
app: cas-server
template:
metadata:
labels:
app: cas-server
spec:
containers:
- name: cas
image: cas-overlay:5.x
ports:
- containerPort: 8443
env:
- name: CAS_SERVER_NAME
value: "https://cas.example.org:8443"
- name: CAS_SERVER_PREFIX
value: "https://cas.example.org:8443/cas"
- name: CAS_TICKET_REGISTRY_REDIS_HOST
value: "redis-service"
- name: SPRING_PROFILES_ACTIVE
value: "redis,kubernetes"
resources:
requests:
memory: "1Gi"
cpu: "500m"
limits:
memory: "2Gi"
cpu: "1000m"
livenessProbe:
httpGet:
path: /cas/actuator/health/liveness
port: 8443
scheme: HTTPS
initialDelaySeconds: 120
periodSeconds: 30
readinessProbe:
httpGet:
path: /cas/actuator/health/readiness
port: 8443
scheme: HTTPS
initialDelaySeconds: 30
periodSeconds: 10
---
apiVersion: v1
kind: Service
metadata:
name: cas-service
spec:
selector:
app: cas-server
ports:
- protocol: TCP
port: 443
targetPort: 8443
type: LoadBalancer1
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
57
58
59
60
61
62
63
64
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
57
58
59
60
61
62
63
64
总结
CAS Overlay Template 5.x 是一个功能强大且高度可定制的单点登录解决方案。它提供了现代化的架构、丰富的认证方式和灵活的配置选项,支持云原生部署。5.x 版本引入了对 Spring Boot 1.5+ 的全面支持、改进的安全机制以及更好的微服务集成能力,使其成为现代企业认证架构的理想选择。