Spring Boot OAuth2应用同时支持资源服务与Web授权码流的配置问题
解决方案:分离Web安全与资源服务器的过滤器链
这个问题在你当前使用的Spring Boot 1.5.x + Spring Security OAuth 2.0(4.x)版本里完全可以解决,不用升级!核心问题是资源服务器过滤器和Web安全过滤器的顺序冲突:默认情况下@EnableResourceServer的过滤器优先级(order=3)比WebSecurityConfigurerAdapter的默认优先级(order=100)高,导致所有请求都先经过资源服务器处理,Web路径的请求也被判定为需要Bearer Token,从而返回unauthorized,而不是触发授权码流。
我们的解决思路是:
- 给Web安全配置设置更高的优先级(比资源服务器低的数字,比如order=2),让Web内容路径的请求先走授权码流的认证链;
- 让资源服务器只拦截API路径,且强制无会话,确保Bearer Token调用正常。
步骤1:配置Web安全(授权码流+会话支持)
创建专门的Web安全配置类,指定优先级为2,覆盖Web内容的认证逻辑,同时保留授权码流的跳转能力:
@Configuration @Order(2) // 优先级高于资源服务器的order=3 public class WebSecurityConfig extends WebSecurityConfigurerAdapter { @Override protected void configure(HttpSecurity http) throws Exception { http // 定义需要认证的Web内容路径 .authorizeRequests() .antMatchers("/landing", "/swagger-ui/**", "/swagger-resources/**").authenticated() .and() // 配置OAuth2授权码流(对接PingFederate) .oauth2Login() .authorizationEndpoint() .baseUri("/oauth2/authorize") // 自定义授权端点路径 .and() .redirectionEndpoint() .baseUri("/oauth2/callback/*") // 回调路径,需在PingFederate配置中同步 .and() // 浏览器访问时按需创建会话 .sessionManagement() .sessionCreationPolicy(SessionCreationPolicy.IF_REQUIRED); } // 配置PingFederate的客户端信息 @Bean public ClientRegistrationRepository clientRegistrationRepository() { return new InMemoryClientRegistrationRepository(pingFederateClient()); } private ClientRegistration pingFederateClient() { return ClientRegistration.withRegistrationId("pingfederate") .clientId("your-client-id") // 替换为你的PingFederate客户端ID .clientSecret("your-client-secret") // 替换为你的客户端密钥 .authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE) .redirectUriTemplate("{baseUrl}/oauth2/callback/{registrationId}") .scope("openid", "profile", "api") // 按需配置PingFederate要求的权限范围 .authorizationUri("https://your-pingfederate-domain/oauth/authorize") // PingFederate授权端点 .tokenUri("https://your-pingfederate-domain/oauth/token") // PingFederate令牌端点 .userInfoUri("https://your-pingfederate-domain/oauth/userinfo") // 用户信息端点(可选) .userNameAttributeName(IdTokenClaimNames.SUB) .clientName("PingFederate") .build(); } }
步骤2:配置资源服务器(Bearer Token+无会话)
创建资源服务器配置类,只拦截API路径,且强制无会话:
@Configuration @EnableResourceServer public class ResourceServerConfig extends ResourceServerConfigurerAdapter { @Override public void configure(HttpSecurity http) throws Exception { http // 只拦截API路径 .requestMatchers() .antMatchers("/api/**") .and() // API请求必须认证 .authorizeRequests() .anyRequest().authenticated() .and() // API调用不创建会话,避免重定向 .sessionManagement() .sessionCreationPolicy(SessionCreationPolicy.STATELESS); } // 配置资源服务器的资源ID和令牌验证逻辑 @Override public void configure(ResourceServerSecurityConfigurer resources) throws Exception { resources.resourceId("your-api-resource-id") // 替换为PingFederate中配置的资源ID .tokenStore(tokenStore()); } // 根据PingFederate的令牌类型选择TokenStore: // 如果是JWT令牌,用JwtTokenStore;如果是引用令牌,用RemoteTokenServices @Bean public TokenStore tokenStore() { // 示例:JWT令牌配置 return new JwtTokenStore(jwtAccessTokenConverter()); } @Bean public JwtAccessTokenConverter jwtAccessTokenConverter() { JwtAccessTokenConverter converter = new JwtAccessTokenConverter(); // 替换为PingFederate的公钥,用于验证JWT签名 converter.setVerifierKey("-----BEGIN PUBLIC KEY-----\n你的公钥内容\n-----END PUBLIC KEY-----"); return converter; } // 如果是引用令牌,替换为RemoteTokenServices配置: /* @Bean public RemoteTokenServices remoteTokenServices() { RemoteTokenServices services = new RemoteTokenServices(); services.setCheckTokenEndpointUrl("https://your-pingfederate-domain/oauth/check_token"); services.setClientId("your-client-id"); services.setClientSecret("your-client-secret"); return services; } */ }
关键注意事项
- 路径隔离:确保Web内容路径(如
/landing、Swagger相关)和API路径(如/api/**)没有重叠,避免过滤器链冲突。 - 令牌验证适配:根据PingFederate发放的令牌类型(JWT或引用令牌)选择对应的
TokenStore实现,配置错误会导致令牌验证失败。 - PingFederate配置同步:确保PingFederate客户端的回调URL、权限范围等与代码中的配置一致,否则授权码流会失败。
测试验证
- 浏览器访问Web内容:访问
http://localhost:8080/landing,会自动跳转到PingFederate登录页面,登录后建立会话,可正常访问Web内容和Swagger文档。 - curl调用API:执行
curl -H "Authorization: Bearer your-access-token" http://localhost:8080/api/your-endpoint,无需会话,直接返回API响应。
内容的提问来源于stack exchange,提问作者Bernd
相关产品推荐
相关产品推荐

