- 后端
- 认证鉴权
- 单点登录
【免费下载链接】cas
Apereo CAS - Identity & Single Sign On for all earthlings and beyond.
导读
本文以 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" ] |
responseMode | JARM 的开关,取值query.jwt、fragment.jwt或form_post.jwt | query.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.jwt | 302 重定向 | URL query(?response=...) | 通用后端回调 | OidcResponseModeQueryJwtBuilder |
fragment.jwt | 302 重定向 | URL fragment(#response=...) | 单页应用(SPA) | OidcResponseModeFragmentJwtBuilder |
form_post.jwt | HTML 表单自动提交(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 时,建议按以下清单逐项确认:
- 依赖方类型:服务定义必须是
OidcRegisteredService(见上文 JSON 示例),并显式配置responseMode为query.jwt、fragment.jwt或form_post.jwt之一; - 模块依赖:确认部署已引入
cas-server-support-oidc模块,否则cas.authn.oidc.jarm.*配置与 JARM 行为均不会生效; - 密钥就绪:由于响应 JWT 与 ID Token 共用签名/加密管线,需确保 OIDC JWKS 配置(
cas.authn.oidc.jwks.*)已正确就绪,依赖方持有可用的验签公钥; - 有效期匹配:按需调整
cas.authn.oidc.jarm.expiration(默认 60 秒),确保客户端在回调处理链路中的验签与取码逻辑能在 JWT 过期前完成; - 回调适配:
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.
相关推荐
Apereo CAS OpenID Connect JWT Bearer 授权模式(JWT Authorization Grant)接入指南
Apereo CAS OpenID Connect JWT Bearer 授权模式(JWT Authorization Grant)接入指南 JWT Beare
后端认证鉴权单点登录Spring Authorization Server OIDC支持:OpenID Connect完整配置指南
Spring Authorization Server OIDC支持:OpenID Connect完整配置指南 Spring Authorization Ser
后端认证鉴权身份认证Apereo CAS OAuth 客户端 Response Mode 配置指南:query、fragment 与 form_post 的完整实现解析
Apereo CAS OAuth 客户端 Response Mode 配置指南:query、fragment 与 form_post 的完整实现解析 本指南以
后端认证鉴权单点登录
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考