news 2026/9/26 8:22:29

CAS OpenID Connect JARM(JWT Secured Authorization Response Mode)完整指南:配置、响应模式与源码解析

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
CAS OpenID Connect JARM(JWT Secured Authorization Response Mode)完整指南:配置、响应模式与源码解析
  • 后端
  • 认证鉴权
  • 单点登录

【免费下载链接】cas

Apereo CAS - Identity & Single Sign On for all earthlings and beyond.

项目地址:https://gitcode.com/gh_mirrors/ca/cas
点击查看免费下载

导读

本文以 Apereo CAS 官方文档《JWT Secured Authorization Response Mode (JARM) - OpenID Connect Authentication》为主体,结合仓库源码(support/cas-server-support-oidc与support/cas-server-support-oidc-core-api),系统讲解如何在 CAS 中以 JWT 形式编码 OIDC 授权响应。读完本文,你将掌握:如何为依赖方(Relying Party)启用 JARM、三种响应模式(query.jwt、fragment.jwt、form_post.jwt)的区别与选型、JWT 响应的签名与加密机制,以及cas.authn.oidc.jarm.*配置项的底层实现原理。

JARM 是什么:用 JWT 加固 OIDC 授权响应

JWT Secured Authorization Response(JARM)是 OpenID Connect 的一种扩展机制,允许授权服务器(Authorization Server)不再将授权响应参数(如code、state)以明文形式逐项返回给客户端,而是将它们整体编码进一个 JWT 中。在 CAS 中,这意味着授权响应可以像 ID Token 或 Access Token 一样,被签名和可选加密后再交付给依赖方。

从安全角度看,JARM 带来四个核心价值:

  • 消息完整性(Message Integrity):签名保证响应内容在传输途中未被篡改;
  • 发送方认证(Sender Authentication):依赖方可通过验签确认响应确实由授权服务器(CAS)签发;
  • 受众限制(Audience Restriction):JWT 携带aud声明,可校验响应是否专门发给当前客户端;
  • 防混合攻击(Protection from Mix-up Attacks):绑定iss/aud后可有效抵御跨客户端混淆攻击。

此外,对响应进行加密还能提供机密性(Confidentiality),防止code、state等参数值在 URL 或日志中被泄露。需要特别说明的是,签名/加密策略与 CAS 处理 ID Token、Access Token 的策略完全一致(官方文档原文:“should quite similar to that of ID tokens or access tokens”),底层复用同一套 JWKS 与密码学执行器,因此已配置过 OIDC 令牌签名/加密的部署几乎零成本即可启用 JARM。

为依赖方启用 JARM:responseMode配置

在 CAS 的 JSON 服务注册表中,一个OidcRegisteredService可以通过responseMode属性标记为“使用 JWT 授权响应”。官方文档给出的最小配置示例如下:

{ "@class": "org.apereo.cas.services.OidcRegisteredService", "clientId": "client", "serviceId": "https://app.example.org/redirect", "name": "Sample", "id": 1, "scopes" : [ "java.util.HashSet", [ "profile", "openid" ] ], "supportedResponseTypes": [ "java.util.HashSet", [ "code" ] ], "responseMode": "query.jwt" }

关键字段说明:

字段作用示例值
@class必须是OidcRegisteredService,JARM 仅对 OIDC 依赖方生效org.apereo.cas.services.OidcRegisteredService
clientId客户端标识,同时将作为 JWT 中aud声明的取值来源client
serviceId客户端的 redirect URI,JARM 响应将发送到此地址https://app.example.org/redirect
supportedResponseTypes本示例只支持code流程,JARM 与授权码流程配合最典型[ "code" ]
responseModeJARM 的开关,取值query.jwt、fragment.jwt或form_post.jwtquery.jwt

需要强调的是,responseMode也可由客户端在授权请求中通过response_mode参数动态指定;当服务定义中显式配置了responseMode时,CAS 将按该值处理授权响应。启用后,CAS 不再返回逐个明文参数(code=...&state=...),而是只返回一个response参数,其值为完整 JWT。

JWT 授权响应的内容与 Claims 结构

当授权成功时,CAS 将授权响应参数打包进 JWT。官方文档给出了一个成功的code授权响应的 JWT Claims 示例:

{ "iss": "https://sso.example.com/cas/oidc", "aud": "client", "exp": 1311281970, "code": "OC-1-...", "state": "..." }

各声明含义如下:

  • iss:签发者,即 CAS 的 OIDC 签发者地址(如https://sso.example.com/cas/oidc);
  • aud:受众,即依赖方的clientId,用于校验响应只面向该客户端;
  • exp:JWT 过期时间(Unix 时间戳),由cas.authn.oidc.jarm.expiration控制(默认 60 秒,详见下文配置章节);
  • code:本次授权生成的授权码;
  • state:客户端在授权请求中携带的状态值,原样回传,用于 CSRF 防护。

从源码可以印证上述 claims 的构建过程:在 BaseOAuth20JwtResponseModeBuilder.java 中,CAS 使用 Nimbus JOSE 的JWTClaimsSet.Builder依次设置issuer(取自ctx.getIssuerService().determineIssuer(...))、expirationTime(取自 JARM 配置的过期时长)、audience(取自oidcService.getClientId()),随后将授权响应产生的全部参数(code、state等)通过parameters.forEach(claimsBuilder::claim)逐一写入 claims,最后交给ctx.getResponseModeJwtBuilder().build(...)完成签名/加密并输出 JWT 字符串。

三种 JWT 响应模式详解

CAS 支持三种 JWT 响应模式,区别仅在于“把 JWT 塞进 HTTP 响应的哪个位置、用什么方式传给客户端”。官方文档以 tab 形式分别给出了每种模式的行为与 HTTP 示例,下面逐一展开,并给出对应的源码实现佐证。

模式一:query.jwt(Query 组件传输)

query.jwt模式下,CAS 将授权响应以 HTTP 302 重定向发送到客户端的 redirect URI,并把名为response的参数(值为 JWT)附加到重定向地址的query 组件(?之后):

HTTP/1.1 302 Found Location: https://app.example.org/redirect?response=eyJraWQiOiJsYWViIiwiYWxnIjoiRVMyN...

这是 JARM 最常用的默认模式,兼容性最好。源码层面由 OidcResponseModeQueryJwtBuilder.java 实现:构建出 JWT 后,返回new RedirectView(redirectUrl)与Map.of("response", token)组成的ModelAndView,Spring MVC 据此生成带 query 参数的 302 重定向;该构建器的getResponseMode()返回OAuth20ResponseModeTypes.QUERY_JWT。

模式二:fragment.jwt(Fragment 组件传输)

fragment.jwt模式下,CAS 同样以 HTTP 302 重定向发送授权响应,但response参数(JWT)被放到重定向地址的fragment 组件(#之后):

HTTP/1.1 302 Found Location: https://app.example.org/redirect#response=eyJraWQiOiJsYWViIiwiYWxnIjoiRVMyN...

该模式对单页应用(SPA)尤其友好:fragment 不会随请求发送到服务器,因此 JWT 不会出现在服务器访问日志中;客户端 JavaScript 可直接从location.hash中读取response。其实现对应 OidcResponseModeFragmentJwtBuilder.java,逻辑与query.jwt构建器一致,仅返回视图的组件位置不同,getResponseMode()返回FRAGMENT_JWT。

模式三:form_post.jwt(HTTP POST 表单传输)

form_post.jwt模式下,CAS 不再使用重定向,而是向客户端的 redirect URI 发起 HTTPPOST:response参数(JWT)被编码为 HTML 表单的隐藏字段值,页面加载后自动提交(auto-submit),表单体以application/x-www-form-urlencoded格式传输。

该模式适合希望授权响应走 POST 语义、且对 URL 长度敏感的部署场景(JWT 通常比明文参数串更长,query/fragment 模式可能触碰 URL 长度上限)。源码实现见 OidcResponseModeFormPostJwtBuilder.java:构建 JWT 后,以CasWebflowConstants.VIEW_ID_POST_RESPONSE作为视图名渲染一个自动提交表单(模型包含originalUrl与Map.of("response", token)),并以 HTTP 200 状态返回;getResponseMode()返回FORM_POST_JWT。

三种模式对比小结

响应模式传输方式参数位置典型场景源码实现类
query.jwt302 重定向URL query(?response=...)通用后端回调OidcResponseModeQueryJwtBuilder
fragment.jwt302 重定向URL fragment(#response=...)单页应用(SPA)OidcResponseModeFragmentJwtBuilder
form_post.jwtHTML 表单自动提交(POST)请求体(application/x-www-form-urlencoded)避免 URL 过长、需要 POST 语义OidcResponseModeFormPostJwtBuilder

签名与加密:与 ID Token / Access Token 同源的安全机制

JARM 生成的 JWT 响应与 CAS 签发的 ID Token、Access Token 使用同一套签名/加密管线,其入口是 OidcJwtResponseModeCipherExecutor.java。该类继承自BaseOidcJwtCipherExecutor,构造时接收 CAS 的默认 JWKS 缓存(LoadingCache<OidcJsonWebKeyCacheKey, JsonWebKeySet>)与OidcIssuerService,其逻辑名称为"OpenID Connect Response Mode JWT"。

这意味着:

  • 签名:CAS 使用其 OIDC 签名密钥(JWKS 中的 RSA/EC 私钥或共享 HMAC 密钥)对 JWT 响应签名,依赖方可用 CAS 的 JWKS 公钥验签;
  • 加密(可选):可进一步用依赖方的公钥对 JWT 响应加密,保证code、state等参数的机密性;
  • 密钥管理:与 ID Token 共用cas.authn.oidc.jwks.*体系,无需为 JARM 单独维护密钥。

该执行器有专门的单元测试覆盖,见 OidcJwtResponseModeCipherExecutorTests.java,可用于验证签名/加密在本地环境的行为。

配置参数:cas.authn.oidc.jarm

JARM 的全局配置通过cas.authn.oidc.jarm.*属性完成(官方文档以{% include_cached casproperties.html properties="cas.authn.oidc.jarm" %}的形式挂载了完整属性参考)。目前核心配置项定义在 OidcJwtAuthorizationResponseModeProperties.java 中,并在 OidcProperties.java 中通过jarm字段挂载到 OIDC 属性树。

配置属性类型默认值说明
cas.authn.oidc.jarm.expiration时长(@DurationCapable,支持如PT60S、5m)PT60S(60 秒)JARM 响应 JWT 的硬超时时间,到期后 JWT 失效

以application.properties为例:

# 将 JARM 响应 JWT 的有效期调整为 2 分钟 cas.authn.oidc.jarm.expiration=PT2M

底层行为可从 BaseOAuth20JwtResponseModeBuilder.getExpirationDate() 验证:CAS 读取casProperties.getAuthn().getOidc().getJarm().getExpiration(),通过Beans.newDuration(...)将其解析为秒数,然后以 UTC 当前时间为基准向后累加,得到exp声明的时间戳。需要留意的是,该模块通过@RequiresModule(name = "cas-server-support-oidc")声明依赖,即只有引入 OIDC 支持模块(cas-server-support-oidc)时该配置项才生效。

源码结构全景:JARM 相关类速查

若希望深入阅读 JARM 的实现,可按以下路径在仓库中定位核心代码:

  • 响应模式构建器基类:BaseOAuth20JwtResponseModeBuilder.java(claims 组装与过期时间)
  • 三种模式构建器:OidcResponseModeQueryJwtBuilder.java、OidcResponseModeFragmentJwtBuilder.java、OidcResponseModeFormPostJwtBuilder.java
  • 签名/加密执行器:OidcJwtResponseModeCipherExecutor.java 及同包下的 OidcRegisteredServiceJwtResponseModeCipherExecutor.java
  • 配置模型:OidcJwtAuthorizationResponseModeProperties.java
  • 装配与测试:OidcConfiguration.java(各 builder 的 Bean 装配入口)、OidcJwtResponseModeCipherExecutorTests.java(测试用例)

实战校验清单

接入 JARM 时,建议按以下清单逐项确认:

  1. 依赖方类型:服务定义必须是OidcRegisteredService(见上文 JSON 示例),并显式配置responseMode为query.jwt、fragment.jwt或form_post.jwt之一;
  2. 模块依赖:确认部署已引入cas-server-support-oidc模块,否则cas.authn.oidc.jarm.*配置与 JARM 行为均不会生效;
  3. 密钥就绪:由于响应 JWT 与 ID Token 共用签名/加密管线,需确保 OIDC JWKS 配置(cas.authn.oidc.jwks.*)已正确就绪,依赖方持有可用的验签公钥;
  4. 有效期匹配:按需调整cas.authn.oidc.jarm.expiration(默认 60 秒),确保客户端在回调处理链路中的验签与取码逻辑能在 JWT 过期前完成;
  5. 回调适配:query.jwt/fragment.jwt客户端从 URL 的 query/fragment 中解析response参数;form_post.jwt客户端需接受自动提交的表单 POST 并从请求体中读取response。

至此,从依赖方配置、三种响应模式选型到签名/加密与过期策略,CAS 的 JARM 能力已完整覆盖,可依据上述路径直接在生产环境中落地并继续深入源码排障。

  • 后端
  • 认证鉴权
  • 单点登录

【免费下载链接】cas

Apereo CAS - Identity & Single Sign On for all earthlings and beyond.

项目地址:https://gitcode.com/gh_mirrors/ca/cas
点击查看免费下载

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/26 8:22:04

Git Worktree 并行多会话:Claude Code 开发效率提升实战

1. 为什么单会话模式正在拖垮你的开发效率 如果你现在还在一个终端窗口里跟 AI 编程助手一问一答&#xff0c;那你大概率已经感受到了那种"排队等回复"的窒息感。我最初用 Claude Code 的时候也是这样&#xff0c;一个会话跑到底&#xff0c;改完一个模块再改下一个&…

作者头像 李华
网站建设 2026/9/26 8:20:49

AIO Sandbox:把浏览器、Shell、MCP和VSCode装进同一个Agent沙箱

做 Agent 项目的朋友应该都经历过这种循环&#xff1a;先配好 Playwright 环境&#xff0c;跑通一个浏览器自动化脚本&#xff1b;接着要执行清理命令&#xff0c;又得切到另一套容器&#xff1b;数据落到文件里&#xff0c;还得把卷挂出来让另一个服务读到。我自己之前维护的工…

作者头像 李华
网站建设 2026/9/26 8:19:34

OpenTTD 货运分配链路图(Link Graph)机制与性能调优指南

游戏开发 【免费下载链接】OpenTTD OpenTTD is an open source simulation game based upon Transport Tycoon Deluxe 项目地址&#xff1a; https://gitcode.com/gh_mirrors/op/OpenTTD 点击查看 免费下载 本文以 docs/linkgraph.md 为主线&#xff0c;结合 OpenTTD 源码中 s…

作者头像 李华
网站建设 2026/9/26 8:19:02

移动端反作弊主动干预实战:Frida与Hook检测对抗

1. 反作弊攻防的战场早已从"特征对抗"转向"运行时博弈"做移动端安全的人这两年应该有个明显感受&#xff1a;单纯靠静态特征扫描已经很难拦住真正有威胁的作弊行为。原因不复杂——作弊工具本身在进化&#xff0c;从早期改内存、改返回值&#xff0c;到现在…

作者头像 李华
网站建设 2026/9/26 8:17:23

VMware虚拟机磁盘空间清理与压缩全攻略:从原理到实战

1. 虚拟机磁盘为什么会越用越大用 VMware Workstation 的人基本都会碰到同一个问题&#xff1a;虚拟机用着用着&#xff0c;宿主机上的那个文件夹就膨胀到几十个 G&#xff0c;明明虚拟机里删了一堆东西&#xff0c;宿主机上的 vmdk 文件却一点没变小。我自己的主力开发机上有三…

作者头像 李华
网站建设 2026/9/26 8:17:22

豆包网页版批量删除历史对话:三种技术路线与实操指南

1. 为什么“批量删除历史对话”是个真需求豆包网页版用久了&#xff0c;侧边栏的历史对话会像滚雪球一样越积越多。我自己的账号用了不到三个月&#xff0c;侧边栏就攒了四百多条记录&#xff0c;往下翻的时候浏览器明显卡顿&#xff0c;找一条上周的对话得滚动半天。更麻烦的是…

作者头像 李华