ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

Apereo CAS OAuth 2.0 Refresh Token 授权流程(Refresh Token Grant)完全解析

Apereo CAS OAuth 2.0 Refresh Token 授权流程(Refresh Token Grant)完全解析 后端认证鉴权单点登录【免费下载链接】casApereo CAS - Identity Single Sign On for all earthlings and beyond.项目地址https://gitcode.com/gh_mirrors/ca/cas点击查看免费下载本文围绕 Apereo CAS 官方文档中关于OAuth 2.0 Refresh Token 授权流程OAuth-ProtocolFlow-RefreshToken.md展开系统讲解如何在 CAS 中通过/oauth2.0/accessToken端点用已颁发的 refresh token 换取新的 access token并结合本仓库源码剖析其请求校验、令牌生成、令牌轮换Token Rotation与过期策略的完整实现链路。读完本文你将掌握 Refresh Token Grant 的端点调用方式、全部核心参数、CAS 中的配置项与服务级覆盖规则以及这套流程在源码中的真实执行路径。一、Refresh Token 授权流程概述OAuth 2.0 的 refresh token grant 类型用于在先前颁发的 access token 过期之后用对应的 refresh token 重新获取一个新的 access token。它避免了用户在 access token 失效后重新走一遍完整的登录授权流程是长会话long-lived session类应用的常见手段。在 CAS 的 OAuth/OpenID Connect 实现中该流程的契约定义如下端点Endpoint请求参数Parameters响应Response/oauth2.0/accessTokengrant_typerefresh_tokenclient_idIDclient_secretSECRETrefresh_tokenREFRESH_TOKEN新的 access token从请求参数可以看出该流程需要同时提交四种参数grant_type固定为refresh_token用于告诉 CAS 本次请求使用的是刷新令牌授权类型client_id客户端在 CAS 服务注册中心登记的标识符client_secret客户端的密钥用于客户端认证refresh_token先前由 CAS 颁发并保存下来的刷新令牌本体。这些参数名在源码中均有对应的常量定义见 OAuth20Constants.javaString BASE_OAUTH20_URL /oauth2.0; String GRANT_TYPE grant_type; String CLIENT_ID client_id; String CLIENT_SECRET client_secret; String REFRESH_TOKEN refresh_token; String ACCESS_TOKEN_URL accessToken; String TOKEN_URL token;注意BASE_OAUTH20_URL与ACCESS_TOKEN_URL拼接后正好构成文档表格中的/oauth2.0/accessToken端点地址。二、端点与请求处理入口Refresh Token Grant 的请求统一由Access Token 端点控制器OAuth20AccessTokenEndpointController接收处理其声明位于 OAuth20AccessTokenEndpointController.javaPostMapping(path { OAuth20Constants.BASE_OAUTH20_URL / OAuth20Constants.ACCESS_TOKEN_URL, OAuth20Constants.BASE_OAUTH20_URL / OAuth20Constants.TOKEN_URL}, produces MediaType.APPLICATION_JSON_VALUE) public ModelAndView handleRequest(final HttpServletRequest request, final HttpServletResponse response) throws Exception { val context new JEEContext(request, response); if (!verifyAccessTokenRequest(context)) { LOGGER.warn(Access token validation failed for request [{}], context.getFullRequestURL()); return OAuth20Utils.writeError(response, OAuth20Constants.INVALID_GRANT); } val tokenRequestContext examineAndExtractAccessTokenGrantRequest(request, response); logProtocolRequest(tokenRequestContext); val generatedTokenResult getConfigurationContext().getAccessTokenGenerator().generate(tokenRequestContext); return generateAccessTokenResponse(tokenRequestContext, generatedTokenResult); }该控制器同时映射了/oauth2.0/accessToken与/oauth2.0/token两个路径并且同时支持PostMapping与GetMapping即 POST 与 GET 两种 HTTP 方法均可发起令牌请求。整体处理链路可分为四步verifyAccessTokenRequest(...)交给注册的 grant type 校验器链验证请求合法性examineAndExtractAccessTokenGrantRequest(...)通过可审计的提取器extractor解析出AccessTokenRequestContextgetAccessTokenGenerator().generate(...)由令牌生成器实际产出新的 access token及可选的 refresh tokengenerateAccessTokenResponse(...)将结果编码为 JSON 响应返回给客户端。其中校验失败时CAS 会直接返回invalid_grant错误码这正是 refresh token 无效、过期或不属于该客户端时的典型错误响应。三、Refresh Token 请求的校验逻辑当 CAS 收到grant_typerefresh_token的请求后负责校验的是OAuth20RefreshTokenGrantTypeTokenRequestValidator其核心逻辑见 OAuth20RefreshTokenGrantTypeTokenRequestValidator.java校验过程按以下顺序执行参数完整性检查解析请求中的refresh_token参数与客户端标识若 refresh token 缺失或client_id为空直接返回校验失败刷新令牌存活性检查通过ticketRegistry.getTicket(token, OAuth20RefreshToken.class)从票据注册中心Ticket Registry中查找对应的 refresh token 票据若抛出InvalidTicketException即令牌不存在或已过期则校验失败并记录告警日志服务访问策略检查依据client_id定位注册服务OAuthRegisteredService并执行RegisteredServiceAccessStrategyEnforcer的服务访问策略审计未授权则抛出异常授权类型检查调用isGrantTypeSupportedBy(registeredService, grantType)确认该服务定义中明确允许refresh_token授权类型否则拒绝请求令牌归属检查比对 refresh token 绑定的clientId与本次请求的client_id二者不匹配时拒绝请求并记录警告日志。从源码可见CAS 对 Refresh Token Grant 的校验是相当严格的刷新令牌必须真实存在于票据注册中心、未过期、且必须属于发起请求的客户端。这一归属校验直接防止了刷新令牌被跨客户端盗用。四、请求提取器识别并解析 Refresh Token Grant在校验通过后请求会被交给授权类型请求提取器处理。CAS 会根据grant_type参数值在多个提取器中路由其中AccessTokenRefreshTokenGrantRequestExtractor专门负责refresh_token类型见 AccessTokenRefreshTokenGrantRequestExtractor.javaOverride public boolean supports(final WebContext context) { val grantType getConfigurationContext().getObject().getRequestParameterResolver() .resolveRequestParameter(context, OAuth20Constants.GRANT_TYPE).orElse(StringUtils.EMPTY); return OAuth20Utils.isGrantType(grantType, getGrantType()); } Override public OAuth20GrantTypes getGrantType() { return OAuth20GrantTypes.REFRESH_TOKEN; } Override protected String getOAuthParameterName() { return OAuth20Constants.REFRESH_TOKEN; } Override protected AccessTokenRequestContext extractInternal( final WebContext context, final AccessTokenRequestContext accessTokenRequestContext) { val registeredService getOAuthRegisteredServiceBy(context); if (registeredService null) { throw UnauthorizedServiceException.denied(Unable to locate service in registry); } val shouldRenewRefreshToken registeredService.isGenerateRefreshToken() registeredService.isRenewRefreshToken(); return super.extractInternal(context, accessTokenRequestContext .withGenerateRefreshToken(shouldRenewRefreshToken) .withExpireOldRefreshToken(shouldRenewRefreshToken)); }这段实现揭示了 Refresh Token Grant 的两个关键语义按grant_type路由supports(...)方法通过比较请求中的grant_type参数与OAuth20GrantTypes.REFRESH_TOKEN来决定该提取器是否接管本次请求令牌轮换Token RotationextractInternal(...)中读取注册服务的generateRefreshToken与renewRefreshToken两个开关只有当两者同时为true时CAS 才会在本次刷新过程中再生成一个新的 refresh token并让旧 refresh token 过期withGenerateRefreshToken与withExpireOldRefreshToken同时被置位。若服务未开启轮换则刷新后保留原 refresh token 继续使用。此外该提取器通过getRegisteredServiceIdentifierFromRequest(...)从请求中解析client_id/client_secret对并据此从服务注册中心定位注册服务找不到服务时直接抛出UnauthorizedServiceException。五、Refresh Token 的生成、追踪与轮换5.1 令牌生成新的 access token以及需要轮换时的新 refresh token由OAuth20DefaultTokenGenerator生成见 OAuth20DefaultTokenGenerator.javaval refreshToken FunctionUtils.doIf(tokenRequestContext.isGenerateRefreshToken(), Unchecked.supplier(() - generateRefreshToken(tokenRequestContext, accessToken.getId())), supplier - null).get(); return new AccessAndRefreshTokens(addedAccessToken, refreshToken);5.2 Access Token 追踪CAS 的 refresh token 会记录通过它签发过的所有 access token以便在刷新令牌被吊销时级联撤销同一授权下产生的访问令牌。该行为由配置项cas.authn.oauth.refreshToken.trackAccessTokens默认true控制相关代码见 OAuth20DefaultTokenGenerator.java#L287-L294protected void updateRefreshToken(final AccessTokenRequestContext tokenRequestContext, final Ticket accessToken) { val trackAccessTokens casProperties.getAuthn().getOauth().getRefreshToken().isTrackAccessTokens(); if (tokenRequestContext.isRefreshToken() !tokenRequestContext.getToken().isStateless() trackAccessTokens) { val refreshToken (OAuth20RefreshToken) tokenRequestContext.getToken(); LOGGER.trace(Tracking access token [{}] linked to refresh token [{}], accessToken.getId(), refreshToken.getId()); refreshToken.getAccessTokens().add(accessToken.getId()); ticketRegistry.updateTicket(refreshToken); } }在票据模型层面OAuth20DefaultRefreshToken持有一个accessTokens集合用于存放这些关联访问令牌的 ID见 OAuth20DefaultRefreshToken.java接口 OAuth20RefreshToken.java 对此也给出了明确的语义说明撤销 refresh token 时应当同时使基于同一授权发放的 access token 失效。5.3 旧令牌过期当服务开启令牌轮换后新令牌生成的同时会执行expireOldRefreshToken(...)将旧 refresh token 标记为过期并从注册中心删除见 OAuth20DefaultTokenGenerator.java#L406-L411private void expireOldRefreshToken(final AccessTokenRequestContext tokenRequestContext) throws Exception { val oldRefreshToken tokenRequestContext.getToken(); if (!oldRefreshToken.isStateless()) { LOGGER.debug(Expiring old refresh token [{}], oldRefreshToken); oldRefreshToken.markTicketExpired(); ticketRegistry.deleteTicket(oldRefreshToken); } }5.4 刷新令牌的票据前缀refresh token 在 CAS 票据体系中的前缀为RT见 OAuth20RefreshToken.javaOAuth20DefaultRefreshToken#getPrefix()返回该前缀见 OAuth20DefaultRefreshToken.java。在调试或排查票据注册中心中的数据时以RT开头的票据即为 OAuth 刷新令牌。六、Refresh Token 的过期策略与配置项6.1 全局过期策略CAS 为 refresh token 提供了全局默认的过期策略其构建逻辑在 OAuth20RefreshTokenExpirationPolicyBuilder.java 中private ExpirationPolicy toTicketExpirationPolicy() { val rtProps casProperties.getAuthn().getOauth().getRefreshToken(); val timeout Beans.newDuration(rtProps.getTimeToKillInSeconds()).toSeconds(); return buildExpirationPolicyFor(timeout); }其中timeToKillInSeconds表示超过该时长后 refresh token 即被视为过期的硬性超时hard timeout。6.2 服务级覆盖CAS 允许在单个注册服务OAuthRegisteredService级别覆盖全局过期策略。OAuthRegisteredService上提供了generateRefreshToken、renewRefreshToken、jwtRefreshToken与refreshTokenExpirationPolicy等字段见 OAuthRegisteredService.java。过期策略构建器会优先使用服务定义中配置的timeToKill见 OAuth20RefreshTokenExpirationPolicyBuilder.java#L46-L56Override public ExpirationPolicy buildTicketExpirationPolicyFor(final RegisteredServiceDefinition registeredService) { if (registeredService instanceof final OAuthRegisteredService service service.getRefreshTokenExpirationPolicy() ! null) { val policy service.getRefreshTokenExpirationPolicy(); val timeToKill policy.getTimeToKill(); if (StringUtils.isNotBlank(timeToKill)) { val timeToKillInSeconds Beans.newDuration(timeToKill).toSeconds(); return buildExpirationPolicyFor(timeToKillInSeconds); } } return toTicketExpirationPolicy(); }6.3 相关配置属性Refresh Token 的全部配置属性集中在 OAuthRefreshTokenProperties.java 中对应配置文件前缀为cas.authn.oauth.refresh-token.*配置属性类型默认值说明cas.authn.oauth.refresh-token.time-to-kill-in-secondsDurationDurationCapableP14D14 天超过该时长后 refresh token 视为过期的硬性超时cas.authn.oauth.refresh-token.storage-nameStringoauthRefreshTokensCacheCAS 在底层票据注册中心中保存 OAuth refresh token 所用的存储对象名称cas.authn.oauth.refresh-token.max-active-tokens-allowedlong0不限制单个应用最多可持有的活跃 refresh token 数量超过上限时请求被拒绝且不再签发 access tokencas.authn.oauth.refresh-token.create-as-jwtbooleanfalse是否将 access token 生成为 JWT 形式cas.authn.oauth.refresh-token.track-access-tokensbooleantrue是否记录由该 refresh token 签发的 access token仅用于历史与审计目的需要特别说明的是max-active-tokens-allowed表示应用最多能够获得的活跃 refresh token 数量一旦应用申请的令牌数超过该限制请求会被拒绝且 access token 不会签发这一点在 OAuthRefreshTokenProperties.java 的注释中有明确说明。七、Public Client 场景下的刷新令牌认证对于不定义 client secret 的公开客户端public client例如移动端应用CAS 提供了专门的OAuth20RefreshTokenAuthenticator见 OAuth20RefreshTokenAuthenticator.java。该类继承自OAuth20ClientIdClientSecretAuthenticator但仅在请求满足以下条件时介入认证请求中同时存在client_id与grant_type且grant_type为refresh_token请求中包含refresh_token参数注册服务不需要客户端认证!OAuth20Utils.doesServiceNeedAuthentication(registeredService)。在validateCredentials(...)中CAS 会从票据注册中心取回该刷新令牌并依次检查令牌是否存在、是否过期、refreshToken.getClientId()与请求的client_id是否一致。任一条件不满足都会抛出CredentialsException从而拒绝本次刷新见 OAuth20RefreshTokenAuthenticator.java#L73-L90。因此在构造 refresh token 请求时客户端认证方式取决于注册服务的配置服务需要认证如机密客户端在请求体中携带client_secret服务为公开客户端CAS 将依据client_id与刷新令牌本身完成认证无需提供client_secret。八、完整调用示例与实战要点8.1 标准刷新请求机密客户端假设客户端注册的服务允许refresh_token授权类型且已通过授权码流程获得过 refresh token则刷新 access token 的 HTTP 请求示例如下curl -k -X POST https://cas.example.org/cas/oauth2.0/accessToken \ -H Content-Type: application/x-www-form-urlencoded \ -d grant_typerefresh_token \ -d client_idmy-client-id \ -d client_secretmy-client-secret \ -d refresh_tokenRT-xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx成功响应将返回新的 access tokenJSON 形式典型字段包含access_token、expires_in若服务开启了轮换还会附带新的refresh_token。8.2 常见失败场景与排查方向结合前文源码可将 refresh token 请求失败归纳为以下几类原因失败表现可能原因排查方向invalid_grantrefresh token 不存在或已过期检查票据注册中心中RT前缀票据核对time-to-kill-in-seconds与服务的refreshTokenExpirationPolicyinvalid_grantclient_id与 refresh token 归属不一致确认刷新令牌由当前客户端签发检查 OAuth20RefreshTokenGrantTypeTokenRequestValidator.java 中的归属比对逻辑请求被拒绝服务定义未允许refresh_token授权类型在注册服务中显式启用refresh_tokengrant type请求被拒绝应用持有的活跃刷新令牌数超过max-active-tokens-allowed清理旧令牌或上调上限认证失败公开客户端未满足免认证条件检查服务是否需要客户端认证及client_id是否正确8.3 服务端配置示意在application.yml/cas.properties中可按需覆盖全局刷新令牌行为# 全局刷新令牌有效期默认 14 天 cas.authn.oauth.refresh-token.time-to-kill-in-secondsP14D # 是否追踪由刷新令牌签发的访问令牌 cas.authn.oauth.refresh-token.track-access-tokenstrue # 单个应用活跃刷新令牌上限 cas.authn.oauth.refresh-token.max-active-tokens-allowed100同时在服务注册定义JSON/YAML中可为特定应用开启刷新令牌签发与轮换{ class: org.apereo.cas.support.oauth.services.OAuthRegisteredService, clientId: my-client-id, clientSecret: my-client-secret, serviceId: https://app.example.org/.*, generateRefreshToken: true, renewRefreshToken: true, supportedGrantTypes: [refresh_token] }其中generateRefreshToken决定是否向该客户端签发 refresh tokenrenewRefreshToken决定刷新时是否执行令牌轮换签发新 refresh token 并使旧令牌过期supportedGrantTypes用于声明允许的授权类型。九、小结Refresh Token Grant 是 CAS OAuth/OpenID Connect 协议栈中用于维持长会话、避免频繁重新授权的关键授权类型。通过本文可以掌握协议契约向/oauth2.0/accessToken提交grant_typerefresh_token与client_id、client_secret、refresh_token四个参数即可换取新 access token实现链路请求依次经过 OAuth20AccessTokenEndpointController → OAuth20RefreshTokenGrantTypeTokenRequestValidator → AccessTokenRefreshTokenGrantRequestExtractor → OAuth20DefaultTokenGenerator安全语义刷新令牌必须存在、未过期且归属当前客户端支持令牌轮换与访问令牌追踪为吊销与审计提供支撑配置能力全局默认有效期 14 天可被服务级refreshTokenExpirationPolicy覆盖公开客户端无 secret场景有专门的认证器支持。在此基础上读者可以结合 CAS 文档目录中的其他 OAuth 协议流文档如授权码流、客户端凭据流构建完整的 OAuth 集成方案并依据 OAuthRefreshTokenProperties.java 与 OAuthRegisteredService.java 按需定制刷新令牌行为。赞分享后端认证鉴权单点登录【免费下载链接】casApereo CAS - Identity Single Sign On for all earthlings and beyond.项目地址https://gitcode.com/gh_mirrors/ca/cas点击查看免费下载相关推荐Apereo CAS OAuth 隐式授权流Implicit Flow / Token Response Type实战指南Apereo CAS OAuth 隐式授权流Implicit Flow / Token Response Type实战指南 本指南以 Apereo CAS后端认证鉴权单点登录Apereo CAS OAuth 2.0 Token Exchange 协议流程实战指南Impersonation 与 Delegation 的令牌交换Apereo CAS OAuth 2.0 Token Exchange 协议流程实战指南Impersonation 与 Delegation 的令牌交换 导读后端认证鉴权单点登录编程方式获取 DiceBear 头像风格全部可选参数OptionsDescriptor 完全指南编程方式获取 DiceBear 头像风格全部可选参数OptionsDescriptor 完全指南 每种 DiceBear 头像风格都因为其组件componeUI组件后端上一篇DIFT技术原理深度剖析U-Net架构改造与扩散时间步特征提取机制下一篇颠覆式小说资源管理工具Tomato-Novel-Downloader重构数字阅读体验创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表