☰
Apereo CAS 中的 OAuth 2.0 设备授权流(Device Authorization Grant):无浏览器设备的令牌签发实战指南
2026/9/27 3:09:42 网站建设 项目流程
  • 后端
  • 认证鉴权
  • 单点登录

【免费下载链接】cas

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

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

导读

本文聚焦 Apereo CAS 中 OAuth 2.0设备授权授予(Device Authorization Grant,旧称 Device Flow)的完整实现与使用方式。该协议扩展让 Apple TV 这类没有浏览器、或仅具备有限输入能力的设备(如可向 YouTube 频道推流的硬件编码器)也能安全地获取访问令牌。读完本文,你将掌握 CAS 设备流的两个核心调用方式(申请 device code / user code、用 device code 换取 access token)、与设备流相关的全部可配置参数(令牌有效期、轮询间隔、用户码长度等),以及底层控制器、票据工厂与异常映射的源码级工作原理。


一、什么是 OAuth 2.0 设备授权授予

OAuth 2.0 设备授权授予是标准 OAuth 2.0 的一个扩展,专门解决没有浏览器或输入能力受限的设备如何完成授权的问题。典型场景包括:

  • Apple TV 等智能电视应用;
  • 可向 YouTube 频道推流的硬件视频编码器;
  • 打印机、路由器等 IoT 设备。

其核心思路是:设备本身不做用户交互,而是先向授权服务器申请一个device code(设备码)和一个user code(用户码);用户在另一台具备浏览器的设备(如手机、电脑)上打开验证页面、输入 user code 完成授权;设备随后用 device code 轮询令牌端点,直到用户批准后拿到 access token。

在 Apereo CAS 中,该流程定义于官方文档 OAuth-ProtocolFlow-DeviceAuthorization.md,涉及两个端点调用方式,整理如下:

端点参数响应
/oauth2.0/accessTokenresponse_type=device_code&client_id=<ID>设备授权 URL、device code 与 user code
/oauth2.0/accessTokenresponse_type=device_code&client_id=<ID>&code=<DEVICE_CODE>用户码被批准后返回新的 access token

二、两步式设备流调用详解

第一步:申请设备码与用户码

设备首先向 CAS 的令牌端点发起首次请求,使用response_type=device_code表明这是一次设备授权申请,并携带注册过的client_id:

GET/POST /oauth2.0/accessToken ?response_type=device_code &client_id=<CLIENT_ID>

CAS 在收到该请求后,会生成一对凭证:

  • device code(设备码):设备自身持有,用于后续轮询换取令牌,相当于"设备会话凭证";
  • user code(用户码):展示给用户、供其在另一台设备上输入的短码;
  • verification_uri(验证 URL):用户需要用浏览器访问的授权页面地址。

从源码看,这三个响应对应的参数名定义在 OAuth20Constants.java 中:

  • verification_uri(DEVICE_VERIFICATION_URI):设备验证页 URL;
  • user_code(DEVICE_USER_CODE):用户码;
  • device_code(DEVICE_CODE):设备码;
  • interval(DEVICE_INTERVAL):建议的轮询间隔。

在 CAS 实现里,response_type=device_code与标准的grant_type=urn:ietf:params:oauth:grant-type:device_code(常量定义于 OAuth20GrantTypes.java)是等价的。负责识别该请求的提取器 AccessTokenDeviceCodeResponseRequestExtractor.java 的supports方法会同时检查这两种写法:

val validRequest = OAuth20Utils.isResponseType(responseType, OAuth20ResponseTypes.DEVICE_CODE) || OAuth20Utils.isGrantType(grantType, OAuth20GrantTypes.DEVICE_CODE); return validRequest && StringUtils.isNotBlank(clientId);

即:只要请求中带有response_type=device_code或grant_type=urn:ietf:params:oauth:grant-type:device_code,且client_id非空,该请求即被判定为设备流申请。

第二步:用设备码轮询换取访问令牌

设备随后以固定间隔向同一端点发起轮询,携带client_id与第一步拿到的device_code(参数名为code):

GET/POST /oauth2.0/accessToken ?response_type=device_code &client_id=<CLIENT_ID> &code=<DEVICE_CODE>

只有当用户已经完成 user code 的批准后,CAS 才会返回新的access token(以及按注册服务配置决定是否生成的 refresh token)。AccessTokenDeviceCodeResponseRequestExtractor在提取请求时会根据client_id定位注册服务(OAuth20Utils.getRegisteredOAuthServiceByClientId),解析 scopes 并执行服务访问策略校验,随后把deviceCode一并写入AccessTokenRequestContext,交由 OAuth20DefaultTokenGenerator.java 完成令牌签发。

三、设备流的错误处理与协议语义

设备流轮询过程中,用户可能尚未批准或已拒绝,也可能设备轮询过快。CAS 在 OAuth20AccessTokenEndpointController.java 中对设备流异常做了标准映射:

异常类返回的 OAuth 错误码语义
InvalidOAuth20DeviceTokenExceptionaccess_denied无法识别/提取设备令牌请求
UnapprovedOAuth20DeviceUserCodeExceptionauthorization_pending用户码尚未批准,设备应继续轮询
ThrottledOAuth20DeviceUserCodeApprovalExceptionslow_down请求过于频繁被限流,应放慢轮询速度
其他未匹配异常invalid_grant无效或未授权的授予

其中authorization_pending(AUTHORIZATION_PENDING)、slow_down(SLOW_DOWN)、access_denied(ACCESS_DENIED)等常量同样定义在 OAuth20Constants.java 中,与 RFC 8628 的设备授权扩展错误语义保持一致。对应异常类型位于 device 包下,并由同目录的校验器在令牌签发前抛出。

四、用户码批准流程与端点

设备流的关键一环是用户在另一台设备上完成授权。CAS 提供了专用的用户码批准端点/oauth2.0/device,由 OAuth20DeviceUserCodeApprovalEndpointController.java 实现:

  • GET/oauth2.0/device:渲染用户码输入页面(视图oauthDeviceCodeApprovalView);
  • POST/oauth2.0/device:提交表单参数usercode,CAS 会:
  1. 通过OAuth20DeviceUserCodeFactory的normalizeUserCode规范化用户码并定位对应票据;
  2. 若该用户码已被批准则返回codeapproved错误提示(视图仍为批准页);
  3. 获取当前登录用户(OAuth20Utils.getAuthenticatedUserProfile)与票据授予票据(TGT);
  4. 将认证信息写入用户码票据(deviceUserCode.setAuthentication(...))、标记setUserCodeApproved(true);
  5. 调用ticketRegistry.updateTicket(...)持久化,并渲染成功视图oauthDeviceCodeApprovedView。

如果提交的用户码为空或票据查找失败,则返回codenotfound错误。整个批准流程完成后,设备端下一次轮询即可拿到 access token。

五、设备流相关配置参数

设备流的有效期、轮询频率与用户码长度等行为,由 CAS 的 OAuth 配置节(cas.authn.oauth.*)中的两个嵌套配置对象控制,其默认值定义于配置模型 OAuthDeviceTokenProperties.java 与 OAuthDeviceUserCodeProperties.java,并挂载在 OAuthProperties.java 下。

设备令牌(device token)配置:cas.authn.oauth.device-token

配置项默认值说明
cas.authn.oauth.device-token.max-time-to-live-in-secondsPT5M设备令牌的硬超时时间,到期即被销毁
cas.authn.oauth.device-token.refresh-intervalPT15S设备轮询间隔。客户端应按此速率向令牌端点 POST,尝试获取 access token
cas.authn.oauth.device-token.storage-nameoauthDeviceTokensCache设备令牌在底层 ticket registry 中使用的存储对象名

其中refresh-interval即响应中interval字段的建议来源,客户端应按照该间隔进行轮询,避免触发slow_down限流。

用户码(user code)配置:cas.authn.oauth.device-user-code

配置项默认值说明
cas.authn.oauth.device-user-code.max-time-to-live-in-secondsPT1M用户码的硬超时时间,到期即失效
cas.authn.oauth.device-user-code.user-code-length8生成用户码的长度(字符数)
cas.authn.oauth.device-user-code.storage-nameoauthDeviceUserCodesCache用户码在底层 ticket registry 中使用的存储对象名

一个贴近实际的最小 YAML 配置示例:

cas: authn: oauth: device-token: max-time-to-live-in-seconds: PT10M refresh-interval: PT30S device-user-code: max-time-to-live-in-seconds: PT2M user-code-length: 10

注意:设备令牌与用户码均以CAS Ticket(票据)的形式存放在 ticket registry 中,因此有效期策略也支持按注册服务覆盖。从 OAuth20DefaultDeviceTokenFactory.java 的实现可以看到,生成 device code 时会调用OAuth20DeviceTokenUtils.determineExpirationPolicyForService(...),按服务解析其专属过期策略;对应服务注册模型中的策略类型为 RegisteredServiceOAuthDeviceTokenExpirationPolicy.java。

六、从源码看设备流的完整调用链

设备流在 CAS 中的执行链路可概括为:

  1. 请求入口:POST /oauth2.0/accessToken命中 OAuth20AccessTokenEndpointController,先由verifyAccessTokenRequest遍历注册的accessTokenGrantRequestValidators校验请求合法性;
  2. 请求识别:AccessTokenDeviceCodeResponseRequestExtractor.supports(...)判断response_type=device_code或grant_type=urn:ietf:params:oauth:grant-type:device_code;
  3. 请求提取:extractRequest(...)解析client_id、定位注册服务、解析 scopes、构建匿名认证上下文,并携带device_code;
  4. 令牌生成:OAuth20DefaultTokenGenerator.generate(...)依据已批准的 device code 生成 access token(及可选的 refresh token);
  5. 响应编码:OAuth20DefaultAccessTokenResponseGenerator输出包含access_token、token_type、expires_in等字段的标准响应;
  6. 异常兜底:未批准返回authorization_pending,过快轮询返回slow_down,无效设备码返回access_denied,其余返回invalid_grant。

用户侧则在GET/POST /oauth2.0/device完成 user code 的提交与批准,二者配合构成完整的授权闭环。上述链路均有对应的测试用例覆盖,例如 OAuth20AccessTokenEndpointControllerTests.java、AccessTokenDeviceCodeResponseRequestExtractorTests.java、OAuth20DeviceCodeResponseTypeRequestValidatorTests.java 与 OAuth20DeviceUserCodeApprovalEndpointControllerTests.java,可作为验证行为与排查问题的参考。

七、使用前提与注意事项

  • 依赖模块:设备流功能位于cas-server-support-oauth模块,需要先引入该依赖;相关配置类均标注了@RequiresModule(name = "cas-server-support-oauth")。
  • 客户端注册:请求中使用的client_id必须在 CAS 服务注册表中存在且具备访问权限,否则提取器会因定位不到注册服务而失败。
  • 刷新令牌:是否随 access token 一并签发 refresh token,取决于注册服务配置中的generateRefreshToken开关(见AccessTokenDeviceCodeResponseRequestExtractor中generateRefreshToken(registeredService != null && registeredService.isGenerateRefreshToken())一行)。
  • 安全提示:设备流本质上是"用户码 + 设备码"双因素式授权,建议按需收紧max-time-to-live-in-seconds,避免设备码长时间有效;同时引导设备遵守interval轮询,防止触发slow_down限流。

八、总结

Apereo CAS 完整实现了 OAuth 2.0 设备授权授予:设备通过/oauth2.0/accessToken端点以response_type=device_code申请设备码与用户码,用户通过/oauth2.0/device批准用户码,设备再以code=<DEVICE_CODE>轮询换取 access token。其有效期、轮询间隔、用户码长度均可通过cas.authn.oauth.device-token与cas.authn.oauth.device-user-code两个配置节精细控制,底层以 CAS 票据机制存储、支持按服务定制过期策略,并遵循authorization_pending、slow_down、access_denied等标准错误语义,可安全地服务于 Apple TV、硬件编码器等无浏览器设备场景。

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

【免费下载链接】cas

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

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

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

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询