旧版 Spring Security OAuth 刷新 Token 时出现 UserDetailsService is required.,需要区分两个连接点:容器里是否有真正的用户查询服务,以及授权服务器是否使用了它。只写 @Autowired 并不会自动生成接口实现。
从刷新错误追到用户服务
2019 年的项目首次授权能拿到令牌,刷新时却提示:
UserDetailsService is required.
按照配置增加 endpoints.userDetailsService(...) 后,又发现用户服务本身无法注入。因此处理顺序应是先提供 Bean,再接入端点,并检查堆栈到底由哪个组件要求该服务。
UserDetailsService 应读取真实用户数据
下面是替换固定密码示例后的结构。代码依赖应用自行提供 AccountLookup 实现;它应返回存储中的密码哈希、权限和账号状态,而不是现场生成一个永远存在的用户。
interface AccountLookup {
UserDetails findByUsername(String username);
}
@Service("customUserDetailsService")
class CustomUserDetailsService implements UserDetailsService {
private final AccountLookup accounts;
CustomUserDetailsService(AccountLookup accounts) {
this.accounts = accounts;
}
@Override
public UserDetails loadUserByUsername(String username) {
UserDetails user = accounts.findByUsername(username);
if (user == null) {
throw new UsernameNotFoundException("用户不存在");
}
return user;
}
}
片段使用 Spring 的 Service 及 Spring Security 的 UserDetails、UserDetailsService、UsernameNotFoundException 类型,数据访问实现仍属于项目。不要返回 null,不要给所有用户名统一密码或 admin 权限;密码哈希应来自用户存储,验证与编码规则也应保持一致。
明确把正确 Bean 交给旧授权端点
在旧 AuthorizationServerConfigurerAdapter 配置类中,可明确限定注入对象并连接:
@Autowired
@Qualifier("customUserDetailsService")
private UserDetailsService userDetailsService;
@Override
public void configure(
AuthorizationServerEndpointsConfigurer endpoints) {
endpoints.userDetailsService(userDetailsService);
}
@Qualifier 解决的是多个候选 Bean 时选哪一个,不会替代实现本身。如果项目已显式配置 TokenServices 或 AuthenticationManager,还要确认端点配置是否实际用于那条刷新链,不能只看方法写在源码中。
刷新时是否重新加载用户,由实现决定
官方旧仓库 AuthorizationServerEndpointsConfigurer 中有用户服务接入点;DefaultTokenServices 在具备相应认证管理器、且不是 client-only 认证时才走用户重新认证分支。不能泛称所有 OAuth 刷新都必须读一次数据库。
应在隔离环境测试有效用户、已删除或禁用用户,以及错误客户端凭据,核对账号状态是否按设计生效。客户端认证问题另见 invalid_client 排查。这些历史 API 已结束生命周期,不能直接用作新授权服务模板。