Spring Boot中OAuth2与Basic Auth共存时401问题的解决
在单个Spring Boot应用中实现Basic认证与OAuth2资源共存的解决方案
问题背景
我有一个精简的Spring Boot Web应用,需求如下:
- 路径
/resource通过OAuth2/OIDC不透明令牌(opaque token)introspection机制保护,令牌校验后为匹配的客户端ID赋予oauth2角色 - 路径
/helloworld采用HTTP Basic认证保护,认证成功后映射到demo角色
目前/resource可以通过正确的Bearer令牌正常访问,但使用配置的用户名密码(均为demo)访问/helloworld时,始终返回401未授权,与未携带认证信息的请求结果一致。需要解决两种认证方式在同一应用中共存并正常工作的问题。
现有代码
DemoApplication.java
@SpringBootApplication @RestController public class DemoApplication { @PreAuthorize("hasRole('demo')") @GetMapping("/helloworld") public String hello() { return "Hello World!"; } @PreAuthorize("hasRole('oauth2')") @GetMapping("/resource") public String resource() { return "Protected resource"; } public static void main(String... args) { SpringApplication.run(DemoApplication.class, args); } }
DemoSecurityConfiguration.java
public class DemoSecurityConfiguration { @Configuration @Order(10) @EnableWebSecurity public static class HelloWorldBasicSecurityConfigurerAdapter extends WebSecurityConfigurerAdapter { @Override protected void configure(HttpSecurity http) throws Exception { http.antMatcher("/helloworld") .httpBasic() .and() .authorizeRequests().antMatchers("/helloworld").authenticated() ; } } @Configuration @Order(0) @EnableWebSecurity public static class ResourceSecurityConfigurerAdapter extends WebSecurityConfigurerAdapter { @Autowired private Environment environment; @Override protected void configure(HttpSecurity http) throws Exception { http.antMatcher("/resource") .oauth2ResourceServer(oauth2 -> oauth2.opaqueToken( opaqueToken -> opaqueToken.introspector(new DemoAuthoritiesOpaqueTokenIntrospector()))) .authorizeRequests().antMatchers("/resource").authenticated() ; } private class DemoAuthoritiesOpaqueTokenIntrospector implements OpaqueTokenIntrospector { private final OpaqueTokenIntrospector delegate; private final String demoClientId; public DemoAuthoritiesOpaqueTokenIntrospector() { String introSpectionUri = environment .getProperty("spring.security.oauth2.resourceserver.opaque-token.introspection-uri"); String clientId = environment .getProperty("spring.security.oauth2.resourceserver.opaque-token.client-id"); String clientSecret = environment .getProperty("spring.security.oauth2.resourceserver.opaque-token.client-secret"); demoClientId = environment.getProperty("demo.security.oauth2.credentials-grant.client-id"); delegate = new NimbusOpaqueTokenIntrospector(introSpectionUri, clientId, clientSecret); } public OAuth2AuthenticatedPrincipal introspect(String token) { OAuth2AuthenticatedPrincipal principal = this.delegate.introspect(token); return new DefaultOAuth2AuthenticatedPrincipal(principal.getName(), principal.getAttributes(), extractAuthorities(principal)); } private Collection<GrantedAuthority> extractAuthorities(OAuth2AuthenticatedPrincipal principal) { String userId = principal.getAttribute("client_id"); if (demoClientId.equals(userId)) { return Collections.singleton(new SimpleGrantedAuthority("ROLE_oauth2")); } return Collections.emptySet(); } } } }
application.yaml
spring: security: basic: enabled: true user: name: demo password: demo roles: demo oauth2: resourceserver: opaque-token: introspection-uri: "https://...oauth2" client-id: "abba" client-secret: "secret" demo: security: oauth2: credentials-grant: client-id: "rundmc
build.gradle
plugins { id 'java' id 'org.springframework.boot' version '2.7.6' id 'io.spring.dependency-management' version '1.0.15.RELEASE' } group = 'com.example' version = '0.0.1-SNAPSHOT' sourceCompatibility = '17' repositories { mavenCentral() } dependencies { implementation 'org.springframework.boot:spring-boot-starter' implementation 'org.springframework.boot:spring-boot-starter-web' implementation 'org.springframework.boot:spring-boot-starter-security' implementation("org.springframework.security:spring-security-oauth2-resource-server") implementation("org.springframework.security:spring-security-oauth2-jose") implementation("org.springframework.security:spring-security-oauth2-client") }
问题分析
- Basic认证用户未被正确加载:自定义的
HelloWorldBasicSecurityConfigurerAdapter没有配置用户认证来源,导致Spring Boot自动配置的内存用户(spring.security.user下的配置)未生效,系统无法识别提供的用户名密码。 - application.yaml语法错误:
demo.security.oauth2.credentials-grant.client-id的引号未闭合,会导致配置加载失败,可能影响OAuth2相关逻辑。 - 废弃配置冗余:
spring.security.basic.enabled在Spring Boot 2.x版本中已被弃用,无需配置。
解决方案
1. 修正Basic认证配置类
在HelloWorldBasicSecurityConfigurerAdapter中添加用户认证配置,显式加载Spring Boot配置的内存用户:
@Configuration @Order(10) @EnableWebSecurity public static class HelloWorldBasicSecurityConfigurerAdapter extends WebSecurityConfigurerAdapter { @Value("${spring.security.user.name}") private String username; @Value("${spring.security.user.password}") private String password; @Value("${spring.security.user.roles}") private String role; @Override protected void configure(AuthenticationManagerBuilder auth) throws Exception { auth.inMemoryAuthentication() .withUser(username) .password("{noop}" + password) // {noop}表示不加密,生产环境需使用BCrypt等密码编码器 .roles(role); } @Override protected void configure(HttpSecurity http) throws Exception { http.antMatcher("/helloworld") .httpBasic() .and() .authorizeRequests() .antMatchers("/helloworld").hasRole(role); // 与@PreAuthorize的角色校验对应 } }
2. 修正application.yaml
- 闭合
demo.client-id的引号 - 移除废弃的
spring.security.basic.enabled配置
spring: security: user: name: demo password: demo roles: demo oauth2: resourceserver: opaque-token: introspection-uri: "https://...oauth2" client-id: "abba" client-secret: "secret" demo: security: oauth2: credentials-grant: client-id: "rundmc"
3. 确认配置优先级
两个配置类的@Order顺序正确:ResourceSecurityConfigurerAdapter的@Order(0)优先级更高,先处理/resource路径;HelloWorldBasicSecurityConfigurerAdapter的@Order(10)处理/helloworld路径,二者互不干扰。
测试验证
- 启动应用后,测试Basic认证:
curl -u demo:demo http://localhost:8080/helloworld
应返回Hello World!
- 测试OAuth2不透明令牌认证:
curl -H "Authorization: Bearer <有效令牌>" http://localhost:8080/resource
应返回Protected resource
内容的提问来源于stack exchange,提问作者user17203350
相关产品推荐
相关产品推荐

