共计 3022 个字符,预计需要花费 8 分钟才能阅读完成。
背景与痛点
在现代微服务架构中,OAuth 2.0 已成为身份验证和授权的标准协议。当客户端使用过期的访问令牌(access_token)请求资源服务器时,就会遇到 401 invalid access token or token expired 错误。这种情况在分布式系统中尤为常见,会导致用户体验中断,尤其是当用户正在执行关键操作时。

- 典型场景 :用户长时间停留在页面后提交表单,但此时访问令牌已过期
- 连锁反应 :前端需要重新获取令牌,可能导致用户操作数据丢失
- 系统影响 :频繁的令牌失效会增加认证服务器压力,降低整体性能
技术方案对比
处理令牌过期主要有两种技术路线:
- 短期令牌 + 刷新令牌(Refresh Token)
- 优点:安全性高,可灵活控制会话生命周期
-
缺点:需要维护令牌状态,增加系统复杂度
-
JWT 自包含令牌
- 优点:无状态,减少服务端存储压力
- 缺点:难以主动失效令牌,存在安全风险
生产环境中推荐使用第一种方案,特别是在需要严格安全控制的场景。
核心实现
使用 Spring Security OAuth2Client
Spring Security 5.2+ 提供了完整的 OAuth2 客户端支持。以下是关键配置:
@Bean
public OAuth2AuthorizedClientManager authorizedClientManager(
ClientRegistrationRepository clientRegistrationRepository,
OAuth2AuthorizedClientRepository authorizedClientRepository) {
OAuth2AuthorizedClientProvider authorizedClientProvider =
OAuth2AuthorizedClientProviderBuilder.builder()
.authorizationCode()
.refreshToken() // 启用刷新令牌机制
.clientCredentials()
.build();
DefaultOAuth2AuthorizedClientManager authorizedClientManager =
new DefaultOAuth2AuthorizedClientManager(
clientRegistrationRepository,
authorizedClientRepository);
authorizedClientManager.setAuthorizedClientProvider(authorizedClientProvider);
return authorizedClientManager;
}
实现自动刷新拦截器
创建自定义的 ClientHttpRequestInterceptor 在请求失败时自动刷新令牌:
public class OAuth2RefreshTokenInterceptor implements ClientHttpRequestInterceptor {
private final OAuth2AuthorizedClientManager authorizedClientManager;
private final String clientRegistrationId;
@Override
public ClientHttpResponse intercept(HttpRequest request, byte[] body,
ClientHttpRequestExecution execution) throws IOException {
try {return execution.execute(request, body);
} catch (HttpClientErrorException e) {if (e.getStatusCode() == HttpStatus.UNAUTHORIZED) { // 401 错误
// 获取新的访问令牌
OAuth2AuthorizedClient authorizedClient = authorizedClientManager
.authorize(OAuth2AuthorizeRequest
.withClientRegistrationId(clientRegistrationId)
.principal("system")
.build());
// 更新请求头后重试
request.getHeaders().setBearerAuth(authorizedClient.getAccessToken().getTokenValue());
return execution.execute(request, body);
}
throw e;
}
}
}
处理并发场景
多个线程同时遇到令牌过期时可能引发 ” 令牌竞争 ” 问题。解决方案:
- 使用
synchronized块或ReentrantLock保护令牌刷新逻辑 - 在 Redis 中使用
SETNX命令实现分布式锁 - 设置合理的锁超时时间(建议 5-10 秒)
生产环境考量
令牌存储方案
- 内存存储 :适合单实例应用,简单但无法扩展
- Redis 集群 :分布式环境首选,注意设置合理的 TTL
速率限制策略
防止刷新令牌被暴力攻击:
- 认证服务器实现令牌端点的速率限制
- 客户端采用指数退避算法(Exponential Backoff)重试
- 监控异常刷新行为(如短时间内多次刷新)
避坑指南
- CSRF 保护导致刷新失败
- 问题:Spring Security 默认启用 CSRF 保护
-
解决:对 OAuth2 令牌端点禁用 CSRF
http.csrf().ignoringRequestMatchers(new AntPathRequestMatcher("/oauth2/token")); -
刷新令牌过期
- 问题:refresh_token 也有有效期
-
解决:捕获
InvalidTokenException强制用户重新登录 -
网络抖动导致刷新失败
- 问题:认证服务暂时不可用
- 解决:结合 Resilience4j 实现重试机制
RetryConfig config = RetryConfig.custom() .maxAttempts(3) .waitDuration(Duration.ofMillis(500)) .retryOnResult(response -> ((ClientHttpResponse)response) .getStatusCode().is5xxServerError()) .build();
延伸思考
在微服务架构设计中,需要考虑:
- 当采用无状态 JWT 时,如何实现即时吊销?
- 分布式会话(如 Spring Session)与 OAuth2 令牌如何选择?
- 服务网格(Service Mesh)环境下令牌传播的最佳实践
建议根据实际业务的安全要求和扩展性需求做出权衡。对于金融等高安全场景,推荐使用短期令牌 + 刷新令牌方案;对于内部低风险系统,可以考虑无状态 JWT 方案以简化架构。
总结
处理 401 invalid access token or token expired 错误的核心在于:
- 合理设置令牌有效期(通常 access_token 1 小时,refresh_token 7 天)
- 实现可靠的自动刷新机制
- 处理好边界情况(网络错误、并发请求等)
- 监控令牌生命周期和刷新频率
通过本文介绍的 Spring Security 方案,开发者可以构建健壮的令牌管理机制,显著提升系统可用性和用户体验。实际项目中还需结合监控告警系统,及时发现和处理认证异常。
