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);
}