额外使用 OAuth2 进行身份验证时缺少 DaoAuthenticationProvider
Posted
技术标签:
【中文标题】额外使用 OAuth2 进行身份验证时缺少 DaoAuthenticationProvider【英文标题】:DaoAuthenticationProvider missing when additionally using OAuth2 for Authentication 【发布时间】:2021-08-13 19:01:06 【问题描述】:我检测到一种令我感到困惑的行为,这可能是故意的,但我无法确定是否如此。
当我将基本身份验证与特定的 oauth2 配置一起使用时,不会创建 DaoAuthenticationprovider
。如果application.yaml
内部没有此特定配置,一切正常。是否包含在内,我得到一个导致 401 的内部异常。我在下面描述了这两种情况。
我的问题是:如果这是正确的行为,我如何在不提供自定义 AuthenticationProvider 等的情况下运行默认的基本身份验证?我自己并没有尝试实现这一点,因为我不知道如何轻松实现它,而且,如果可能的话,我宁愿使用默认行为。
基本身份验证在以下情况下不起作用
curl --location --request GET 'http://localhost:8080/actuator/info' --header 'Authorization: Basic dXNlcjpwYXNzd29yZA=='
回应是
"timestamp":"2021-05-25T13:25:30.198+00:00","status":401,"error":"Unauthorized","message":"","path":"/actuator/info"
在 Spring 中我得到一个 ProviderNotFoundException:
/actuator/info at position 5 of 11 in additional filter chain; firing Filter: 'BasicAuthenticationFilter'
2021-05-25 15:23:32.727 DEBUG 4216 --- [nio-8080-exec-2] o.s.s.w.a.www.BasicAuthenticationFilter : Basic Authentication Authorization header found for user 'user'
2021-05-25 15:23:32.730 DEBUG 4216 --- [nio-8080-exec-2] o.s.s.w.a.www.BasicAuthenticationFilter : Authentication request for failed!
org.springframework.security.authentication.ProviderNotFoundException: No AuthenticationProvider found for org.springframework.security.authentication.UsernamePasswordAuthenticationToken
at org.springframework.security.authentication.ProviderManager.authenticate(ProviderManager.java:251) ~[spring-security-core-5.3.4.RELEASE.jar:5.3.4.RELEASE]
调试时我发现DaoAuthenticationProvider provider = new DaoAuthenticationProvider();
在配置InitializeUserDetailsBeanManagerConfigurer.java
时不 被调用。实际上,似乎并没有真正调用 InitializeUserDetailsBeanManagerConfigurer 进行配置,而是在这里登陆GlobalAuthenticationConfigurerAdapter.java
。
让一切顺利
我只需要从application.yaml
中删除以下几行:
oauth2:
resourceserver:
jwt:
issuer-uri: https://someissuer
jwk-set-uri: https://somejwkuri
然后我从同一个卷曲中得到 200 分,这个在春天里:
/actuator/info at position 5 of 11 in additional filter chain; firing Filter: 'BasicAuthenticationFilter'
2021-05-25 15:51:21.845 DEBUG 16540 --- [nio-8080-exec-1] o.s.s.w.a.www.BasicAuthenticationFilter : Basic Authentication Authorization header found for user 'user'
2021-05-25 15:51:21.845 DEBUG 16540 --- [nio-8080-exec-1] o.s.s.authentication.ProviderManager : Authentication attempt using org.springframework.security.authentication.dao.DaoAuthenticationProvider
2021-05-25 15:51:22.036 DEBUG 16540 --- [nio-8080-exec-1] o.s.s.w.a.www.BasicAuthenticationFilter : Authentication success: org.springframework.security.authentication.UsernamePasswordAuthenticationToken@ed8ad585: Principal: org.springframework.security.core.userdetails.User@36ebcb: Username: user; Password: [PROTECTED]; Enabled: true; AccountNonExpired: true; credentialsNonExpired: true; AccountNonLocked: true; Granted Authorities: ROLE_ENDPOINT_ADMIN; Credentials: [PROTECTED]; Authenticated: true; Details: org.springframework.security.web.authentication.WebAuthenticationDetails@b364: RemoteIpAddress: 0:0:0:0:0:0:0:1; SessionId: null; Granted Authorities: ROLE_ENDPOINT_ADMIN
代码
application.yaml
还有flyway、hikari、camunda等其他配置,但都是独立的,不影响我观察到的行为。
spring:
security:
oauth2:
resourceserver:
jwt:
issuer-uri: https://someissuer
jwk-set-uri: https://somejwkuri
user:
name: user
password: password
roles: ENDPOINT_ADMIN
server:
port: 8080
management:
endpoints:
web:
exposure:
include: health,info,metrics,prometheus,loggers
cors:
allowed-origins: "*"
allowed-methods: GET,POST
security:
enabled: false
logging:
level:
org:
springframework: DEBUG
pattern:
console:%dyyyy-MM-dd HH:mm:ss %-5level %logger36 - %msg%n
WebSecurityConfigurerAdapter 扩展类
它是一个内部类,因为我使用了多个过滤器链,但我在测试期间排除了其他过滤器链 - 没有影响
public class SecurityConfig
@Order(1)
@Configuration
public static class BasicAuthSecurityConfig extends WebSecurityConfigurerAdapter
// info is excluded on purpose
private final String[] ENDPOINTS = "health", "metrics", "prometheus", /* "info" */ ;
@Override
protected void configure(HttpSecurity http) throws Exception
http.csrf().disable().requestMatcher(EndpointRequest.toAnyEndpoint()).authorizeRequests()
.requestMatchers(EndpointRequest.to(ENDPOINTS)).permitAll()
.requestMatchers(EndpointRequest.toAnyEndpoint()).hasRole("ENDPOINT_ADMIN").and()
.httpBasic();
// other commented out inner classes for oauth and general handling
build.gradle
非常短的版本,但它应该说明这是构建的。
plugins
//Spring Boot
id 'org.springframework.boot' version '2.3.3.RELEASE'
dependencies
compile 'org.springframework.boot:spring-boot-starter-security'
compile 'org.springframework.boot:spring-boot-starter-actuator'
compile 'org.springframework.boot:spring-boot-starter-amqp'
compile 'org.springframework.boot:spring-boot-starter-data-jpa'
compile 'org.springframework.boot:spring-boot-starter-data-rest'
compile 'org.springframework.boot:spring-boot-starter-web'
compile 'org.springframework.cloud:spring-cloud-starter-kubernetes-config'
compile 'org.springframework.cloud:spring-cloud-starter-openfeign'
compile 'org.springframework.security:spring-security-oauth2-resource-server'
compile 'org.springframework.security:spring-security-oauth2-jose'
compile 'org.springframework.security:spring-security-config'
【问题讨论】:
【参考方案1】:快速解释
在您的 application.yml 中,您已经配置了 HTTP 基本身份验证和 OAuth2 资源服务器。 虽然拥有同时使用两者的应用程序是合理的,但 Spring Boot 将无法根据这些属性找出您需要的特定配置。 此外,Spring Boot 自动配置旨在用于直接的场景。对于更复杂的配置,例如这个配置,最好显式创建您需要的 bean。
深入讲解
这里是您当前配置发生的情况的概述。
由于设置了spring.security.oauth2.resourceserver.jwt.jwk-set-uri
,Spring Boot 了解您的应用程序是一个 OAuth2 资源服务器并生成一个JwtDecoder
bean。 (见OAuth2ResourceServerAutoConfiguration
)
如果您删除该属性并仅设置 spring.security.user.name
,那么 Spring Boot 会使用您提供的用户信息生成默认的 InMemoryUserDetailsManager
。 (见UserDetailsServiceAutoConfiguration
)
但是,当存在JwtDecoder
bean 时,Spring Boot 不会创建默认的InMemoryUserDetailsManager
。
对于背景,您可以在 Spring Boot backlog 中看到这个 issue。
现在,您的应用程序没有InMemoryUserDetailsManager
,但它仍然尝试使用 HTTP 基本身份验证进行身份验证,因为您已指定 http.httpBasic()
。这就是您看到错误消息的原因。
解决方案
与其在 application.yml 中指定用户凭据,不如创建一个UserDetailsService
bean。无论如何,您都希望这样做,因为 InMemoryUserDetailsManager
主要用于测试目的,不建议在生产中使用。
我将在示例中使用InMemoryUserDetailsManager
,只是为了说明等效配置。
@EnableWebSecurity
public class SecurityConfiguration
@Order(1)
@Configuration
public class HttpBasicAuthSecurityConfig extends WebSecurityConfigurerAdapter
@Override
protected void configure(HttpSecurity http) throws Exception
http
.antMatcher("/basic/**")
.authorizeRequests(authorize -> authorize
.anyRequest().authenticated()
)
.httpBasic(withDefaults());
// Explicitly create bean, instead of configuring user in application.yml
@Bean
public UserDetailsService userDetailsService()
UserDetails user = User.withDefaultPasswordEncoder()
.username("user")
.password("password")
.roles("USER")
.build();
return new InMemoryUserDetailsManager(user);
@Order(2)
@Configuration
public class ResourceServerConfig extends WebSecurityConfigurerAdapter
@Override
protected void configure(HttpSecurity http) throws Exception
http
.antMatcher("/oauth2/**")
.authorizeRequests(authorize -> authorize
.anyRequest().authenticated()
)
.oauth2ResourceServer(oauth2 -> oauth2
.jwt(withDefaults())
);
// Explicitly create bean, instead of configuring decoder in application.yml
@Bean
JwtDecoder jwtDecoder()
NimbusJwtDecoder jwtDecoder = NimbusJwtDecoder
.withJwkSetUri("https://somejwkuri")
.build();
jwtDecoder
.setJwtValidator(JwtValidators.createDefaultWithIssuer("https://someissuer"));
return jwtDecoder;
【讨论】:
以上是关于额外使用 OAuth2 进行身份验证时缺少 DaoAuthenticationProvider的主要内容,如果未能解决你的问题,请参考以下文章
使用 Twitch 进行身份验证时的 Spring-Boot OAuth2 奇怪行为
向 Google 进行身份验证时,从 Spring OAuth2 授权服务器发出 JWT 令牌
使用 Spring Boot OAuth2 针对 Google 进行身份验证时如何将 google 用户电子邮件发送到白名单用户