- 后端
- 认证鉴权
- 单点登录
【免费下载链接】cas
Apereo CAS - Identity & Single Sign On for all earthlings and beyond.
导读
本文聚焦 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/accessToken | response_type=device_code&client_id=<ID> | 设备授权 URL、device code 与 user code |
/oauth2.0/accessToken | response_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 错误码 | 语义 |
|---|---|---|
InvalidOAuth20DeviceTokenException | access_denied | 无法识别/提取设备令牌请求 |
UnapprovedOAuth20DeviceUserCodeException | authorization_pending | 用户码尚未批准,设备应继续轮询 |
ThrottledOAuth20DeviceUserCodeApprovalException | slow_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 会:
- 通过
OAuth20DeviceUserCodeFactory的normalizeUserCode规范化用户码并定位对应票据; - 若该用户码已被批准则返回
codeapproved错误提示(视图仍为批准页); - 获取当前登录用户(
OAuth20Utils.getAuthenticatedUserProfile)与票据授予票据(TGT); - 将认证信息写入用户码票据(
deviceUserCode.setAuthentication(...))、标记setUserCodeApproved(true); - 调用
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-seconds | PT5M | 设备令牌的硬超时时间,到期即被销毁 |
cas.authn.oauth.device-token.refresh-interval | PT15S | 设备轮询间隔。客户端应按此速率向令牌端点 POST,尝试获取 access token |
cas.authn.oauth.device-token.storage-name | oauthDeviceTokensCache | 设备令牌在底层 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-seconds | PT1M | 用户码的硬超时时间,到期即失效 |
cas.authn.oauth.device-user-code.user-code-length | 8 | 生成用户码的长度(字符数) |
cas.authn.oauth.device-user-code.storage-name | oauthDeviceUserCodesCache | 用户码在底层 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 中的执行链路可概括为:
- 请求入口:
POST /oauth2.0/accessToken命中 OAuth20AccessTokenEndpointController,先由verifyAccessTokenRequest遍历注册的accessTokenGrantRequestValidators校验请求合法性; - 请求识别:
AccessTokenDeviceCodeResponseRequestExtractor.supports(...)判断response_type=device_code或grant_type=urn:ietf:params:oauth:grant-type:device_code; - 请求提取:
extractRequest(...)解析client_id、定位注册服务、解析 scopes、构建匿名认证上下文,并携带device_code; - 令牌生成:
OAuth20DefaultTokenGenerator.generate(...)依据已批准的 device code 生成 access token(及可选的 refresh token); - 响应编码:
OAuth20DefaultAccessTokenResponseGenerator输出包含access_token、token_type、expires_in等字段的标准响应; - 异常兜底:未批准返回
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.
相关推荐
Apereo CAS OAuth 2.0 授权码流程(Authorization Code)与 PKCE 扩展实战指南
Apereo CAS OAuth 2.0 授权码流程(Authorization Code)与 PKCE 扩展实战指南 导读 授权码(Authorization
后端认证鉴权单点登录Apereo CAS OAuth 2.0 Refresh Token 授权流程(Refresh Token Grant)完全解析
Apereo CAS OAuth 2.0 Refresh Token 授权流程(Refresh Token Grant)完全解析 本文围绕 Apereo CAS
后端认证鉴权单点登录Apereo CAS OpenID Connect JWT Bearer 授权模式(JWT Authorization Grant)接入指南
Apereo CAS OpenID Connect JWT Bearer 授权模式(JWT Authorization Grant)接入指南 JWT Beare
后端认证鉴权单点登录
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考