刷新请求里已经填了 grant_type=refresh_token 和刷新令牌,仍返回 invalid_client,应先检查客户端认证,而不是反复更换 refresh token。

这是 2019 年旧版 Spring Security OAuth 项目的经历:我把 client ID 和 secret 直接放入表单后收到 Bad client credentials,改用 Postman 的 Basic Auth 后,请求成功。这个结果只证明当时服务端接受了那种认证方式,不代表所有 OAuth 客户端都必须有 secret。

Postman 中,认证与表单分两处填写

对于已注册、且服务端允许 HTTP Basic 认证的保密客户端,可按以下方式组织受控测试:

  1. 请求旧项目的 Token 端点 /oauth/token,使用 POST;非本机测试应使用 HTTPS。
  2. Authorization 选择 Basic Auth,用户名填客户端 ID,密码填客户端 secret,而不是最终用户的账号密码。
  3. Body 选择 x-www-form-urlencoded,填写以下两项。
  4. 不要又在 Body 或继承的认证设置中重复添加另一套客户端认证。
grant_type=refresh_token
refresh_token=<仅在受控工具中填写,勿公开>

secret 和刷新令牌不要加入 URL、截图、共享集合或普通日志。检查 Postman 是否继承了集合认证、预请求脚本或旧的手工 Authorization 头,避免界面上改了,实际发出的请求却没有改变。

为什么表单里填 secret 仍可能失败

RFC 6749 §2.3.1 定义客户端密码认证,服务端对请求体凭据的支持并不是任意实现都默认开启;同一个请求不能同时使用多种客户端认证方式。

旧框架也有 allowFormAuthenticationForClients() 这样的配置入口,可见 历史授权服务器安全 API。不应为了让某个工具测试方便就临时放宽服务端策略,先按客户端注册的方法发送请求。

Basic 的编码不是加密,仍需 TLS 保护。浏览器或移动端等无法保守 secret 的公开客户端,也不应被要求内置一个所谓“保密”密码;应采用相应注册类型支持的安全流程。

把 invalid_client 与 invalid_grant 分开

客户端凭据、认证方法或客户端注册有问题,优先看 invalid_client;刷新令牌无效、过期、撤销或不属于该客户端,则是另一组排查方向。具体错误响应由实现决定,不只按 HTTP 状态猜测。

先让正确与错误客户端凭据得到预期的不同结果,再验证刷新令牌的有效与失效分支。若认证已通过却报用户服务缺失,继续查 UserDetailsService 接入,不要把几种故障当成同一个问题。