Authentication is required to obtain an access token (anonymous not allowed) 不一定是 Token 端点拒绝了请求,也可能在旧客户端本地的 AccessTokenProviderChain 中已经抛出。应先定位异常栈,再核对授权类型、资源详情和 Provider。

本文讨论历史 Spring Security OAuth,不是当前 Spring Security 客户端配置指南。

出错位置在客户端 ProviderChain

2019 年使用 OAuth2RestTemplate 接授权码流程时,我手工建立了包含 AuthorizationCodeAccessTokenProvider 的 ProviderChain,随后出现匿名认证异常。改为直接设置相应 Provider 后,当时的调用恢复。

为了看清触发条件,可参考官方仓库 2.5.2.RELEASE 的 AccessTokenProviderChain:当当前认证对象是 AnonymousAuthenticationToken,而资源不是 client-only 时,会抛出该错误。这个后期版本用于解释分支,不能冒充当年工程的精确依赖。

因此“客户端没有自己的登录页”并非充分根因:用户可能在授权服务器登录,本地安全上下文也可能是空或匿名对象,两者不同。

Provider 和资源详情必须匹配

历史配置的核心片段如下,details 必须是与授权码流程匹配的资源配置:

@Bean
public OAuth2RestTemplate oauth2RestTemplate(
        OAuth2ClientContext context,
        OAuth2ProtectedResourceDetails details) {
    OAuth2RestTemplate template =
            new OAuth2RestTemplate(details, context);
    template.setAccessTokenProvider(
            new AuthorizationCodeAccessTokenProvider());
    return template;
}

这是旧 API 的连接点,不是完整登录配置。授权码资源不能交给面向 password grant 的 Provider,否则还可能发生资源详情类转换异常。直接替换 Provider 也会改变链中的处理行为,不能把它当作通用“取消登录”方案;不要同时关闭 state 校验来绕过授权流程问题。

Postman 能换到 Token,客户端仍需接通回调

前一天的接入记录是:授权端点可返回 code,Postman 也能换 Token,但应用还没有接好上下文。旧模板提供两种使用入口:

OAuth2AccessToken token = template.getAccessToken();
ResponseEntity<String> response =
        template.getForEntity(resourceUrl, String.class);

这些是依赖既有 Bean 的调用片段。授权请求、可信回调、state、授权码换令牌、会话上下文和资源访问仍必须连起来;令牌“保存在内存中”不意味着全站用户共享,也不意味着客户端本地登录状态已经建立。调用路径可结合 Tonr/Sparklr 示例阅读 理解。

缺资源详情 Bean,不能为了启动改变身份

同年 12 月还遇到 OAuth2ProtectedResourceDetails Bean 缺失,条件评估提到:

security.oauth2.client.grant-type=client_credentials
did not find property 'grant-type'

当时加上属性后能够启动,但条件评估的一条未匹配不是所有配置失败的唯一证明。应检查具体 Boot/OAuth 自动配置、候选 Bean 和注入目标,而不是为了触发 Bean 创建就把授权码改成 client_credentials。

后者是客户端代表自身访问的流程,不是用户授权码流程的等价替代。RFC 6749 §4.4 对其使用条件有明确区分。启动通过之后,还要验证授权、回调和身份隔离;旧项目结束生命周期公告 也说明这些类只适合用于维护和阅读历史系统。