- 后端
- 认证鉴权
- 单点登录
【免费下载链接】cas
Apereo CAS - Identity & Single Sign On for all earthlings and beyond.
REST 认证策略是 Apereo CAS 内置的一种可插拔认证策略(Authentication Policy),它允许 CAS 在认证流程中向外部 REST 端点发送POST请求,将已认证的 Principal(主体)作为 JSON 消息体提交,由外部系统对账号状态与登录策略进行裁决。本文围绕 Configuring-Authentication-Policy-REST.md 展开,先完整介绍该策略的配置项与 HTTP 响应码语义映射,再结合仓库源码剖析其调用链、异常映射与重试机制,最后给出可运行的端点示例与测试用例佐证,帮助你在实际部署中把账号禁用、锁定、过期、强制改密等业务规则外置到 REST 服务。
一、REST 认证策略的作用与定位
在 CAS 的认证策略体系中,REST 策略属于“外部调用型”策略:它本身不校验密码,而是在认证事件发生之后对已产生的 Principal 做二次裁决,用于检查该账号是否被禁用、锁定、过期、需要强制修改密码等状态。其典型应用场景包括:
- 账号状态由独立的用户中心、风控系统或 IAM 平台维护,CAS 需要实时查询而非同步本地数据;
- 需要对登录行为做更细粒度的策略判断(如地域、风险等级),由外部系统返回最终放行或拒绝结论;
- 希望在不修改 CAS 源码、不重启实例的前提下,通过调整外部接口逻辑动态变更登录策略。
从源码结构看,REST 策略的 Java 实现为RestfulAuthenticationPolicy(位于 RestfulAuthenticationPolicy.java),它实现自AuthenticationPolicy接口(AuthenticationPolicy.java)。该接口是 CAS 所有认证策略的公共契约,核心方法是isSatisfiedBy(...),返回AuthenticationPolicyExecutionResult表示策略是否满足。
二、配置方式与全部参数详解
REST 认证策略的配置前缀为cas.authn.policy.rest,对应配置模型类RestAuthenticationPolicyProperties(RestAuthenticationPolicyProperties.java)。该类继承自BaseRestEndpointProperties(BaseRestEndpointProperties.java),因此可配置项如下:
| 配置项 | 类型 | 默认值 | 说明 |
|---|---|---|---|
cas.authn.policy.rest[].url | String | 无(必填) | REST 端点地址,CAS 将向其发送POST请求;支持 Spring 表达式语言(@ExpressionLanguageCapable) |
cas.authn.policy.rest[].basicAuthUsername | String | 无 | 若端点受 HTTP Basic 认证保护,用于认证的用户名 |
cas.authn.policy.rest[].basicAuthPassword | String | 无 | 若端点受 HTTP Basic 认证保护,用于认证的密码 |
cas.authn.policy.rest[].headers | Map | 空 | 随请求发送的额外 HTTP 头;会覆盖 CAS 预置的同名请求头 |
cas.authn.policy.rest[].maximumRetryAttempts | int | 3 | 访问端点失败时的最大重试次数;设为 0 或负数则禁用重试 |
cas.authn.policy.rest[].enabled | boolean | false | 是否启用该策略实例(继承自BaseAuthenticationPolicyProperties,BaseAuthenticationPolicyProperties.java) |
cas.authn.policy.rest[].name | String | 无 | 策略名称 |
cas.authn.policy.rest[].order | int | Ordered.LOWEST_PRECEDENCE | 多个策略并存时的执行顺序,数值越小优先级越高 |
由于rest字段在配置模型中声明为列表(List<RestAuthenticationPolicyProperties>,见 AuthenticationPolicyProperties.java),你可以同时配置多个 REST 端点,CAS 会按顺序逐一执行,所有端点均返回成功才判定策略满足。
2.1 一个最小可用的 YAML 配置示例
cas: authn: policy: rest: - url: "https://account.example.org/api/authn/policy" basicAuthUsername: "cas" basicAuthPassword: "changeit" headers: X-Environment: "prod" maximumRetryAttempts: 2 enabled: true name: "central-account-policy" order: 10对应的 properties 写法为:
cas.authn.policy.rest[0].url=https://account.example.org/api/authn/policy cas.authn.policy.rest[0].basicAuthUsername=cas cas.authn.policy.rest[0].basicAuthPassword=changeit cas.authn.policy.rest[0].headers.X-Environment=prod cas.authn.policy.rest[0].maximumRetryAttempts=2 cas.authn.policy.rest[0].enabled=true提示:
enabled字段默认值在配置模型中是false,实际部署时若通过cas.authn.policy.rest列表定义了端点,需显式开启;url为必填项(标注了@RequiredProperty),缺失会导致配置校验失败。
2.2 请求细节:方法、请求体与请求头
从 RestfulAuthenticationPolicy.java 的源码实现看,CAS 向端点发出的请求具有以下特征:
- HTTP 方法:
POST(HttpMethod.POST); - 请求体:当前认证事件的 Principal 经 Jackson
ObjectMapper序列化后的 JSON(MAPPER.writeValueAsString(principal)),即端点收到的 body 就是一个 JSON 格式的主体信息对象,包含id、attributes等字段; - 默认请求头:
Content-Type: application/json; - 自定义请求头:配置的
headers会被追加到请求中,且会覆盖 CAS 预置的同名头; - Basic 认证:若配置了
basicAuthUsername/basicAuthPassword,请求会自动附带 HTTP Basic 认证信息; - 重试:请求失败时按
maximumRetryAttempts进行重试,0 或负数表示不重试。
也就是说,外部端点可以基于 Principal 中的id(用户名)和attributes(属性,如部门、角色、风险评分)做任意的账号状态判断,并最终通过 HTTP 状态码把结论回传给 CAS。
三、响应码语义映射:外部接口如何“说话”
REST 端点返回的 HTTP 状态码会被 CAS 翻译为具体的账号异常类型,这是整个策略的核心契约。原文档给出的映射表如下,且与源码中handleResponseStatusCode方法(RestfulAuthenticationPolicy.java)的实现完全一致:
| HTTP 状态码 | 触发结果 | 源码中的异常类型 |
|---|---|---|
200 | 认证成功,策略满足 | AuthenticationPolicyExecutionResult.success() |
403、405 | 账号被禁用 | AccountDisabledException |
401 | 登录失败 | FailedLoginException |
404 | 账号不存在 | AccountNotFoundException |
423 | 账号被锁定 | AccountLockedException |
412 | 账号已过期 | AccountExpiredException |
428 | 密码必须修改 | AccountPasswordMustChangeException |
| 其他任意状态码 | 登录失败(未知状态码) | FailedLoginException |
3.1 异常被抛出后的处理路径
值得说明的是,源码并不是直接返回异常对象,而是在非200时把对应异常包装为GeneralSecurityException抛出:
if (statusCode != HttpStatus.OK) { val ex = handleResponseStatusCode(statusCode, principal); throw new GeneralSecurityException(ex); }随后这些账号级异常会进入 CAS 的 Webflow 异常处理链。以CasCoreWebflowConfiguration(CasCoreWebflowConfiguration.java)和AuthenticationExceptionHandlerAction为代表的处理机制,会把这些异常映射到具体的登录错误页面与提示信息(如“账号已禁用”“账号已锁定”“密码已过期”等),最终呈现在登录表单上。也就是说,外部端点不需要返回任何业务报文,仅凭状态码即可驱动 CAS 展示对应的账号状态错误。
注意:文档表格中未单列
401,但源码明确将UNAUTHORIZED映射为FailedLoginException(“Could not authenticate account for …”),上表已据源码补全。同理,423在源码中对应LOCKED(AccountLockedException)、412对应PRECONDITION_FAILED(AccountExpiredException)、428对应PRECONDITION_REQUIRED(AccountPasswordMustChangeException),与文档完全吻合。
四、底层实现原理:调用链与关键代码解读
4.1 策略执行入口
RestfulAuthenticationPolicy.isSatisfiedBy(...)是策略判定的唯一入口,其执行流程如下(RestfulAuthenticationPolicy.java):
- 空值保护:若传入的
Authentication为null,直接返回failure()并记录告警日志; - 序列化主体:通过
JacksonObjectMapperFactory构建的ObjectMapper(关闭 default typing)把 Principal 序列化为 JSON 字符串; - 组装请求:使用
HttpExecutionRequest.builder()设置 URL、Basic 认证、POST方法、JSON 实体、请求头与最大重试次数; - 发送请求:调用
HttpUtils.execute(exec)执行请求; - 判定结果:将响应码转换为
HttpStatus,200返回success(),否则映射为对应账号异常并抛出GeneralSecurityException; - 资源释放:在
finally块中通过HttpUtils.close(response)关闭响应。
4.2 配置可见性
该类还实现了toConfiguration()方法,把url、basicAuthUsername、basicAuthPassword、maximumRetryAttempts、headers等关键配置以 Map 形式暴露,供 CAS 的配置审计、端点信息展示等机制使用(RestfulAuthenticationPolicy.java)。
4.3 与其他策略的组合
cas.authn.policy.rest定义于AuthenticationPolicyProperties的rest列表中,与groovy、any、all、notPrevented、uniquePrincipal、requiredAttributes等策略并列(AuthenticationPolicyProperties.java)。CAS 的CoreAuthenticationUtils.newAuthenticationPolicy(props)会将这些配置统一装配为AuthenticationPolicy实例集合,REST 策略只是其中一种可组合的判定单元。
五、测试用例佐证:响应码映射的行为验证
仓库中的单元测试 RestfulAuthenticationPolicyTests.java 使用内嵌MockWebServer完整验证了各状态码与异常类型的对应关系,可作为对接外部端点时的行为基准:
assertPolicyFails(9201, HttpStatus.UNAUTHORIZED, FailedLoginException.class); assertPolicyFails(9202, HttpStatus.LOCKED, AccountLockedException.class); assertPolicyFails(9203, HttpStatus.METHOD_NOT_ALLOWED, AccountDisabledException.class); assertPolicyFails(9204, HttpStatus.FORBIDDEN, AccountDisabledException.class); assertPolicyFails(9205, HttpStatus.NOT_FOUND, AccountNotFoundException.class); assertPolicyFails(9206, HttpStatus.PRECONDITION_FAILED, AccountExpiredException.class); assertPolicyFails(9207, HttpStatus.PRECONDITION_REQUIRED, AccountPasswordMustChangeException.class); assertPolicyFails(9208, HttpStatus.INTERNAL_SERVER_ERROR, FailedLoginException.class);同时,verifyAllowedOperation()验证了端点返回200时策略判定成功。此外,CoreAuthenticationUtilsTests.java 中的verifyAuthnPolicyRest()验证了RestAuthenticationPolicyProperties能被正确装配并完成序列化。这些测试从侧面确认:只要端点按上述状态码契约响应,CAS 侧的异常映射即告成立。
六、外部端点实现示例(伪代码/参考实现)
以下给出一个兼容 CAS REST 认证策略契约的参考端点实现(以 Java Spring 为例,仅作示意):
@RestController public class AuthenticationPolicyController { @PostMapping(value = "/api/authn/policy", consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaType.APPLICATION_JSON_VALUE) public ResponseEntity<Void> checkAccountPolicy(@RequestBody Principal principal) { String userId = principal.getId(); Account account = accountService.findByUserId(userId); if (account == null) { // 账号不存在 → CAS 抛出 AccountNotFoundException return ResponseEntity.status(HttpStatus.NOT_FOUND).build(); } if (account.isDisabled()) { // 账号禁用 → CAS 抛出 AccountDisabledException return ResponseEntity.status(HttpStatus.FORBIDDEN).build(); } if (account.isLocked()) { // 账号锁定 → CAS 抛出 AccountLockedException return ResponseEntity.status(HttpStatus.LOCKED).build(); } if (account.isExpired()) { // 账号过期 → CAS 抛出 AccountExpiredException return ResponseEntity.status(HttpStatus.PRECONDITION_FAILED).build(); } if (account.isPasswordExpired()) { // 密码必须修改 → CAS 抛出 AccountPasswordMustChangeException return ResponseEntity.status(HttpStatus.PRECONDITION_REQUIRED).build(); } // 一切正常 → 认证成功 return ResponseEntity.ok().build(); } }实现时需注意:
- 不要自定义业务响应码:只有文档与源码列出的状态码会被正确映射,其他状态码一律落入
FailedLoginException; - 请求体是 JSON 序列化的 Principal:端点应使用 JSON 反序列化来读取
id与attributes; - 状态码优先于报文:CAS 不解析响应体内容,异常提示信息由 CAS 端模板与国际化资源决定。
七、常见问题与排查建议
- 端点总是返回登录失败(
FailedLoginException):检查端点返回的状态码是否落入了上表未覆盖的范围;同时确认url配置正确、端点可达,以及maximumRetryAttempts未导致请求被过度重试。 - 自定义请求头未生效:
headers中的键值会覆盖 CAS 预置头,确认 YAML/Properties 中 Map 写法无误(如headers.X-Environment=prod)。 - REST 端点受 Basic 认证保护:务必配置
basicAuthUsername/basicAuthPassword,否则端点返回401会被映射为FailedLoginException而非预期的账号异常。 - 策略未生效:检查
enabled: true是否显式设置,并确认cas.authn.policy.rest的列表项缩进/下标与配置格式一致。
八、相关文档与源码索引
- 策略总览:Configuring-Authentication-Policy.md(其中 REST 策略为一行入口)
- 认证组件总览:Configuring-Authentication-Components.md
- 策略实现:RestfulAuthenticationPolicy.java
- 配置模型:RestAuthenticationPolicyProperties.java、BaseRestEndpointProperties.java、AuthenticationPolicyProperties.java
- 策略接口:AuthenticationPolicy.java
- 单元测试:RestfulAuthenticationPolicyTests.java、CoreAuthenticationUtilsTests.java
- 后端
- 认证鉴权
- 单点登录
【免费下载链接】cas
Apereo CAS - Identity & Single Sign On for all earthlings and beyond.
相关推荐
Apereo CAS 基于 Groovy 脚本的认证策略(Authentication Policy)配置指南
Apereo CAS 基于 Groovy 脚本的认证策略(Authentication Policy)配置指南 导读 本文围绕 Apereo CAS 的 cas
后端认证鉴权单点登录Apereo CAS 必选属性认证策略(Required Attributes Authentication Policy)配置指南
Apereo CAS 必选属性认证策略(Required Attributes Authentication Policy)配置指南 导读 在 Apereo C
后端认证鉴权单点登录Apereo CAS Required 认证策略(Required Authentication Policy)详解与源码实现
Apereo CAS Required 认证策略(Required Authentication Policy)详解与源码实现 导读 本文围绕 Apereo C
后端认证鉴权单点登录
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考