This would allow users to easily apply global changes to Spring Security (apply to multiple Security FilterChains). It also allows separating their configurations if they would like. This is convenient for internal frameworks

Comment From: jzheaux

Somewhat related, as far as a demonstration of one of the benefits I see of doing this: https://github.com/spring-projects/spring-security/issues/13057

Comment From: franticticktick

Hi @rwinch, can I take this to work?

Comment From: rwinch

Thanks for volunteering @franticticktick! I've assigned it to you

Comment From: franticticktick

Hey @rwinch @jzheaux . I did some research and have some questions. Let's say we want to define several customizers as beans:

    @Bean
    Customizer<RememberMeConfigurer> rememberMeCustomizer() {
        return rememberMeConfigurer -> {
            //...  
        };
    }

    @Bean
    Customizer<JeeConfigurer> jeeCustomizer() {
        return jeeConfigurer -> {
          //...  
        };
    }

We can get beans through this trick:

String[] beanNames = applicationContext.getBeanNamesForType(Customizer.class);
for (String bean: beanNames) {
   Customizer<?> customizer = applicationContext.getBean(bean, Customizer.class);
}

But in order to apply each customizer to HttpSecurity, we at least need to know the type of the customizer, that is, somehow extract the type of the generic. So we could do something like this:

if(customizer.getConfigurerType() instanceof JeeConfigurer) {
        httpSecurity.jee((Customizer<JeeConfigurer<HttpSecurity>>) customizer);
}

Due to type erasure, we cannot get the generic type in the parameter of the customize method. I don't see any easy way to solve this problem, but maybe I'm missing something. Maybe there is some other way? Sorry for bad English :)

Comment From: kse-music

@franticticktick Is this what you want?

       ConfigurableListableBeanFactory beanFactory = applicationContext.getBeanFactory();
        String[] beanNames = applicationContext.getBeanNamesForType(Customizer.class);
        for (String bean: beanNames) {
            Customizer<?> customizer = applicationContext.getBean(bean, Customizer.class);
            BeanDefinition bd = beanFactory.getMergedBeanDefinition(bean);
            Class<?> resolveClass = bd.getResolvableType().as(Customizer.class).getGeneric().resolve();
            if(resolveClass == RememberMeConfigurer.class) {
                http.rememberMe((Customizer<RememberMeConfigurer<HttpSecurity>>) customizer);
            } else if(resolveClass == JeeConfigurer.class) {
                http.jee((Customizer<JeeConfigurer<HttpSecurity>>) customizer);
            }
        }

Comment From: franticticktick

Hey @kse-music , thanks for the note. I considered this solution, but it’s not possible to simply access ConfigurableListableBeanFactory. There is a mistake in your code: to get a ConfigurableListableBeanFactory you need to do the following trick:

GenericApplicationContext ctx = (GenericApplicationContext) applicationContext;
ConfigurableListableBeanFactory beanFactory  = ctx.getBeanFactory();

How appropriate is such a strong type casting? This is a good question and it confuses me.

Comment From: kse-music

@franticticktick If so, maybe applying Customizer beans in add configuration is a small change

AbstractConfiguredSecurityBuilder

       @SuppressWarnings("unchecked")
    private <C extends SecurityConfigurer<O, B>> void add(C configurer) {
        Assert.notNull(configurer, "configurer cannot be null");
        Class<? extends SecurityConfigurer<O, B>> clazz = (Class<? extends SecurityConfigurer<O, B>>) configurer
            .getClass();
        applyCustomizerBean(configurer, clazz);
        synchronized (this.configurers) {
            if (this.buildState.isConfigured()) {
                throw new IllegalStateException("Cannot apply " + configurer + " to already built object");
            }
            List<SecurityConfigurer<O, B>> configs = null;
            if (this.allowConfigurersOfSameType) {
                configs = this.configurers.get(clazz);
            }
            configs = (configs != null) ? configs : new ArrayList<>(1);
            configs.add(configurer);
            this.configurers.put(clazz, configs);
            if (this.buildState.isInitializing()) {
                this.configurersAddedInInitializing.add(configurer);
            }
        }
    }

        @SuppressWarnings("unchecked")
    private <C extends SecurityConfigurer<O, B>> void applyCustomizerBean(C configurer, Class<?> clazz) {
        ApplicationContext context = getSharedObject(ApplicationContext.class);
        if (context == null) {
            return;
        }
        ResolvableType resolvableType = ResolvableType.forClassWithGenerics(Customizer.class, clazz);
        Customizer<C> customizer = (Customizer<C>) context.getBeanProvider(resolvableType).getIfUnique();
        if (customizer == null) {
            return;
        }
        customizer.customize(configurer);
    }