webflux http basic 仅在特定路径上,而其他所有路径都使用 JWT

webflux http basic on specific path only while all others use JWT

我有一个 spring 启动应用程序,它对所有端点都使用 JWT。现在我想添加一个 /actuator 端点,它使用基本身份验证来启用普罗米修斯抓取指标。

@EnableWebFluxSecurity
@EnableReactiveMethodSecurity
class SecurityConfig(
  val userService: UserService
) {

  @Bean
  fun springSecurityFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain? {
    return http {
      csrf { disable() }
      formLogin { disable() }
      httpBasic { disable() }
      authorizeExchange {
        authorize(ServerWebExchangeMatchers.pathMatchers(HttpMethod.OPTIONS, "/**"), permitAll)

        // the following should not use JWT but basic auth
        authorize(ServerWebExchangeMatchers.pathMatchers("/actuator"), authenticated)

        authorize(anyExchange, authenticated)
      }
      oauth2ResourceServer {
        jwt {
          jwtAuthenticationConverter = customConverter()
        }
      }
    }
  }
}

在 MVC 堆栈中,我会使用这样的东西:

@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {
    @Configuration
    @Order(1)
    public static class ActuatorWebSecurityConfigurationAdapter extends WebSecurityConfigurerAdapter {
        @Value("${management.endpoints.web.base-path}")
        private String managementPath;

        @Value("${config.actuator.user.name}")
        private String actuatorUser;

        @Value("${config.actuator.user.password}")
        private String actuatorPassword;

        @Autowired
        public void configure(AuthenticationManagerBuilder auth) throws Exception {
            auth.inMemoryAuthentication()
                    .withUser(actuatorUser)
                    .password(passwordEncoder().encode(actuatorPassword))
                    .authorities("ROLE_ACTUATOR");
        }

        @Bean
        public PasswordEncoder passwordEncoder() {
            return new Argon2PasswordEncoder();
        }

        protected void configure(HttpSecurity http) throws Exception {
            http.antMatcher(managementPath + "/**")
                    .cors().and()
                    .csrf().disable()
                    .authorizeRequests()
                    .anyRequest()
                    .hasRole("ACTUATOR")
                    .and()
                    .httpBasic();
        }
    }

    @Configuration
    @Order(2)
    public static class ApiWebSecurityConfigurationAdapter extends WebSecurityConfigurerAdapter {
        @Override
        protected void configure(HttpSecurity http) throws Exception {
             http
                    .cors().and()
                    .csrf().disable()
                    .authenticationProvider(...)
                    .authorizeRequests()
                    // ...
        }
    }
}

这如何转化为 webflux?

可以添加多个 bean,但需要使用 securityMatcher:

@EnableWebFluxSecurity
@EnableReactiveMethodSecurity
class SecurityConfig(
  val userService: UserService
) {
  @Value("${config.actuator.user.name}")
  private val actuatorUser: String? = null

  @Value("${config.actuator.user.password}")
  private val actuatorPassword: String? = null

  @Bean
  @Order(1)
  fun springSecurityFilterChainActuator(http: ServerHttpSecurity): SecurityWebFilterChain? {
    val encoder: PasswordEncoder = PasswordEncoderFactories.createDelegatingPasswordEncoder()
    return http {
      securityMatcher(ServerWebExchangeMatchers.pathMatchers("/actuator/**"))
      csrf { disable() }
      formLogin { disable() }
      httpBasic {
        authenticationManager = UserDetailsRepositoryReactiveAuthenticationManager(
          MapReactiveUserDetailsService(
            User
              .withUsername(actuatorUser)
              .password(encoder.encode(actuatorPassword))
              .roles("ACTUATOR")
              .build()
          )
        )
      }
      authorizeExchange {
        authorize(anyExchange, hasRole("ACTUATOR"))
      }
    }
  }

  @Bean
  @Order(2)
  fun springSecurityFilterChainApi(http: ServerHttpSecurity): SecurityWebFilterChain? {
    return http {
      securityMatcher(ServerWebExchangeMatchers.pathMatchers("/**"))
      csrf { disable() }
      formLogin { disable() }
      httpBasic { disable() }
      authorizeExchange {
        authorize(ServerWebExchangeMatchers.pathMatchers(HttpMethod.OPTIONS, "/**"), permitAll)
        authorize(anyExchange, authenticated)
      }
      oauth2ResourceServer {
        jwt {
          jwtAuthenticationConverter = customConverter()
        }
      }
    }
  }
}