额外使用 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 奇怪行为

如何使用 oAuth2 对 SPA 用户进行身份验证?

firebase 函数缺少 oauth2 凭据

向 Google 进行身份验证时,从 Spring OAuth2 授权服务器发出 JWT 令牌

使用 Spring Boot OAuth2 针对 Google 进行身份验证时如何将 google 用户电子邮件发送到白名单用户

Spring Cloud OAuth2:身份验证后再次重定向到登录页面,就像使用表单登录时未经身份验证一样