1
0
mirror of synced 2026-05-22 21:33:16 +00:00

Refactoring of UserDetailsService injection (for X509, OpenID and RememberMeServices) to use a factory bean rather than a post-processor.

This commit is contained in:
Luke Taylor
2010-04-15 01:51:29 +01:00
parent 74896f217b
commit d3d9c5db59
8 changed files with 180 additions and 226 deletions
@@ -19,6 +19,7 @@ public abstract class BeanIds {
public static final String CONTEXT_SOURCE_SETTING_POST_PROCESSOR = PREFIX + "contextSettingPostProcessor";
public static final String USER_DETAILS_SERVICE = PREFIX + "userDetailsService";
public static final String USER_DETAILS_SERVICE_FACTORY = PREFIX + "userDetailsServiceFactory";
public static final String METHOD_ACCESS_MANAGER = PREFIX + "defaultMethodAccessManager";
@@ -22,8 +22,8 @@ import org.springframework.beans.factory.support.RootBeanDefinition;
import org.springframework.beans.factory.xml.ParserContext;
import org.springframework.security.authentication.AnonymousAuthenticationProvider;
import org.springframework.security.authentication.RememberMeAuthenticationProvider;
import org.springframework.security.config.BeanIds;
import org.springframework.security.config.Elements;
import org.springframework.security.core.userdetails.UserDetailsByNameServiceWrapper;
import org.springframework.security.web.access.AccessDeniedHandlerImpl;
import org.springframework.security.web.access.ExceptionTranslationFilter;
import org.springframework.security.web.authentication.AnonymousAuthenticationFilter;
@@ -119,7 +119,7 @@ final class AuthenticationConfigBuilder {
createX509Filter(authenticationManager);
createLogoutFilter();
createLoginPageFilterIfNeeded();
createUserServiceInjector();
createUserDetailsServiceFactory();
createExceptionTranslationFilter();
}
@@ -237,11 +237,12 @@ final class AuthenticationConfigBuilder {
BeanDefinitionBuilder openIDProviderBuilder =
BeanDefinitionBuilder.rootBeanDefinition(OPEN_ID_AUTHENTICATION_PROVIDER_CLASS);
String userService = openIDLoginElt.getAttribute(ATT_USER_SERVICE_REF);
RootBeanDefinition uds = new RootBeanDefinition();
uds.setFactoryBeanName(BeanIds.USER_DETAILS_SERVICE_FACTORY);
uds.setFactoryMethodName("authenticationUserDetailsService");
uds.getConstructorArgumentValues().addGenericArgumentValue(openIDLoginElt.getAttribute(ATT_USER_SERVICE_REF));
if (StringUtils.hasText(userService)) {
openIDProviderBuilder.addPropertyReference("userDetailsService", userService);
}
openIDProviderBuilder.addPropertyValue("authenticationUserDetailsService", uds);
BeanDefinition openIDProvider = openIDProviderBuilder.getBeanDefinition();
openIDProviderId = pc.getReaderContext().registerWithGeneratedName(openIDProvider);
@@ -321,14 +322,12 @@ final class AuthenticationConfigBuilder {
Element x509Elt = DomUtils.getChildElementByTagName(httpElt, Elements.X509);
BeanDefinition provider = new RootBeanDefinition(PreAuthenticatedAuthenticationProvider.class);
String userServiceRef = x509Elt.getAttribute(ATT_USER_SERVICE_REF);
RootBeanDefinition uds = new RootBeanDefinition();
uds.setFactoryBeanName(BeanIds.USER_DETAILS_SERVICE_FACTORY);
uds.setFactoryMethodName("authenticationUserDetailsService");
uds.getConstructorArgumentValues().addGenericArgumentValue(x509Elt.getAttribute(ATT_USER_SERVICE_REF));
if (StringUtils.hasText(userServiceRef)) {
RootBeanDefinition preAuthUserService = new RootBeanDefinition(UserDetailsByNameServiceWrapper.class);
preAuthUserService.setSource(pc.extractSource(x509Elt));
preAuthUserService.getPropertyValues().addPropertyValue("userDetailsService", new RuntimeBeanReference(userServiceRef));
provider.getPropertyValues().addPropertyValue("preAuthenticatedUserDetailsService", preAuthUserService);
}
provider.getPropertyValues().addPropertyValue("preAuthenticatedUserDetailsService", uds);
x509ProviderId = pc.getReaderContext().registerWithGeneratedName(provider);
x509ProviderRef = new RuntimeBeanReference(x509ProviderId);
@@ -527,14 +526,14 @@ final class AuthenticationConfigBuilder {
return (String) pv.getValue();
}
void createUserServiceInjector() {
BeanDefinitionBuilder userServiceInjector =
BeanDefinitionBuilder.rootBeanDefinition(UserDetailsServiceInjectionBeanPostProcessor.class);
userServiceInjector.addConstructorArgValue(x509ProviderId);
userServiceInjector.addConstructorArgValue(rememberMeServicesId);
userServiceInjector.addConstructorArgValue(openIDProviderId);
userServiceInjector.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
pc.getReaderContext().registerWithGeneratedName(userServiceInjector.getBeanDefinition());
private void createUserDetailsServiceFactory() {
if (pc.getRegistry().containsBeanDefinition(BeanIds.USER_DETAILS_SERVICE_FACTORY)) {
// Multiple <http> case
return;
}
RootBeanDefinition bean = new RootBeanDefinition(UserDetailsServiceFactoryBean.class);
bean.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
pc.getReaderContext().getRegistry().registerBeanDefinition(BeanIds.USER_DETAILS_SERVICE_FACTORY, bean);
}
List<OrderDecorator> getFilters() {
@@ -10,6 +10,7 @@ import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.support.RootBeanDefinition;
import org.springframework.beans.factory.xml.BeanDefinitionParser;
import org.springframework.beans.factory.xml.ParserContext;
import org.springframework.security.config.BeanIds;
import org.springframework.security.web.authentication.rememberme.JdbcTokenRepositoryImpl;
import org.springframework.security.web.authentication.rememberme.PersistentTokenBasedRememberMeServices;
import org.springframework.security.web.authentication.rememberme.RememberMeAuthenticationFilter;
@@ -87,9 +88,12 @@ class RememberMeBeanDefinitionParser implements BeanDefinitionParser {
}
if (services != null) {
if (userServiceSet) {
services.getPropertyValues().addPropertyValue("userDetailsService", new RuntimeBeanReference(userServiceRef));
}
RootBeanDefinition uds = new RootBeanDefinition();
uds.setFactoryBeanName(BeanIds.USER_DETAILS_SERVICE_FACTORY);
uds.setFactoryMethodName("cachingUserDetailsService");
uds.getConstructorArgumentValues().addGenericArgumentValue(userServiceRef);
services.getPropertyValues().addPropertyValue("userDetailsService", uds);
if ("true".equals(element.getAttribute(ATT_SECURE_COOKIE))) {
services.getPropertyValues().addPropertyValue("useSecureCookie", true);
@@ -0,0 +1,132 @@
package org.springframework.security.config.http;
import java.util.Map;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.HierarchicalBeanFactory;
import org.springframework.beans.factory.ListableBeanFactory;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.ApplicationContextException;
import org.springframework.security.config.authentication.AbstractUserDetailsServiceBeanDefinitionParser;
import org.springframework.security.config.authentication.CachingUserDetailsService;
import org.springframework.security.core.userdetails.AuthenticationUserDetailsService;
import org.springframework.security.core.userdetails.UserDetailsByNameServiceWrapper;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.util.StringUtils;
/**
* Bean used to lookup a named UserDetailsService or AuthenticationUserDetailsService.
*
* @author Luke Taylor
* @since 3.1
*/
public class UserDetailsServiceFactoryBean implements ApplicationContextAware {
private ApplicationContext beanFactory;
UserDetailsService userDetailsService(String id) {
if (!StringUtils.hasText(id)) {
return getUserDetailsService();
}
return (UserDetailsService) beanFactory.getBean(id);
}
UserDetailsService cachingUserDetailsService(String id) {
if (!StringUtils.hasText(id)) {
return getUserDetailsService();
}
// Overwrite with the caching version if available
String cachingId = id + AbstractUserDetailsServiceBeanDefinitionParser.CACHING_SUFFIX;
if (beanFactory.containsBeanDefinition(cachingId)) {
return (UserDetailsService) beanFactory.getBean(cachingId);
}
return (UserDetailsService) beanFactory.getBean(id);
}
@SuppressWarnings("unchecked")
AuthenticationUserDetailsService authenticationUserDetailsService(String name) {
UserDetailsService uds;
if (!StringUtils.hasText(name)) {
Map<String,?> beans = getBeansOfType(AuthenticationUserDetailsService.class);
if (!beans.isEmpty()) {
if (beans.size() > 1) {
throw new ApplicationContextException("More than one AuthenticationUserDetailsService registered." +
"Please use a specific Id reference in <openid-login/> element.");
}
return (AuthenticationUserDetailsService) beans.values().toArray()[0];
}
uds = getUserDetailsService();
} else {
Object bean = beanFactory.getBean(name);
if (bean instanceof AuthenticationUserDetailsService) {
return (AuthenticationUserDetailsService)bean;
} else if (bean instanceof UserDetailsService) {
uds = cachingUserDetailsService(name);
if (uds == null) {
uds = (UserDetailsService)bean;
}
} else {
throw new ApplicationContextException("Bean '" + name + "' must be a UserDetailsService or an" +
" AuthenticationUserDetailsService");
}
}
return new UserDetailsByNameServiceWrapper(uds);
}
/**
* Obtains a user details service for use in RememberMeServices etc. Will return a caching version
* if available so should not be used for beans which need to separate the two.
*/
private UserDetailsService getUserDetailsService() {
Map<String,?> beans = getBeansOfType(CachingUserDetailsService.class);
if (beans.size() == 0) {
beans = getBeansOfType(UserDetailsService.class);
}
if (beans.size() == 0) {
throw new ApplicationContextException("No UserDetailsService registered.");
} else if (beans.size() > 1) {
throw new ApplicationContextException("More than one UserDetailsService registered. Please " +
"use a specific Id reference in <remember-me/> <openid-login/> or <x509 /> elements.");
}
return (UserDetailsService) beans.values().toArray()[0];
}
public void setApplicationContext(ApplicationContext beanFactory) throws BeansException {
this.beanFactory = beanFactory;
}
private Map<String,?> getBeansOfType(Class<?> type) {
Map<String,?> beans = beanFactory.getBeansOfType(type);
// Check ancestor bean factories if they exist and the current one has none of the required type
BeanFactory parent = beanFactory.getParentBeanFactory();
while (parent != null && beans.size() == 0) {
if (parent instanceof ListableBeanFactory) {
beans = ((ListableBeanFactory)parent).getBeansOfType(type);
}
if (parent instanceof HierarchicalBeanFactory) {
parent = ((HierarchicalBeanFactory)parent).getParentBeanFactory();
} else {
break;
}
}
return beans;
}
}
@@ -1,187 +0,0 @@
package org.springframework.security.config.http;
import java.util.Map;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.BeanWrapperImpl;
import org.springframework.beans.BeansException;
import org.springframework.beans.PropertyValue;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.BeanFactoryAware;
import org.springframework.beans.factory.HierarchicalBeanFactory;
import org.springframework.beans.factory.ListableBeanFactory;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.beans.factory.config.RuntimeBeanReference;
import org.springframework.beans.factory.support.RootBeanDefinition;
import org.springframework.context.ApplicationContextException;
import org.springframework.security.config.authentication.AbstractUserDetailsServiceBeanDefinitionParser;
import org.springframework.security.config.authentication.CachingUserDetailsService;
import org.springframework.security.core.userdetails.UserDetailsByNameServiceWrapper;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.web.authentication.preauth.PreAuthenticatedAuthenticationProvider;
import org.springframework.security.web.authentication.rememberme.AbstractRememberMeServices;
import org.springframework.util.Assert;
/**
* Registered by {@link HttpSecurityBeanDefinitionParser} to inject a UserDetailsService into
* the X509Provider, RememberMeServices and OpenIDAuthenticationProvider instances created by
* the namespace.
*
* @author Luke Taylor
* @since 2.0.2
*/
public class UserDetailsServiceInjectionBeanPostProcessor implements BeanPostProcessor, BeanFactoryAware {
private final Log logger = LogFactory.getLog(getClass());
private ConfigurableListableBeanFactory beanFactory;
private final String x509ProviderId;
private final String rememberMeServicesId;
private final String openIDProviderId;
public UserDetailsServiceInjectionBeanPostProcessor(String x509ProviderId, String rememberMeServicesId,
String openIDProviderId) {
this.x509ProviderId = x509ProviderId;
this.rememberMeServicesId = rememberMeServicesId;
this.openIDProviderId = openIDProviderId;
}
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
if(beanName == null) {
return bean;
}
if (beanName.equals(x509ProviderId)) {
injectUserDetailsServiceIntoX509Provider((PreAuthenticatedAuthenticationProvider) bean);
} else if (beanName.equals(rememberMeServicesId)) {
injectUserDetailsServiceIntoRememberMeServices(bean);
} else if (beanName.equals(openIDProviderId)) {
injectUserDetailsServiceIntoOpenIDProvider(bean);
}
return bean;
}
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
return bean;
}
private void injectUserDetailsServiceIntoRememberMeServices(Object rms) {
if (!(rms instanceof AbstractRememberMeServices)) {
logger.info("RememberMeServices is not an instance of AbstractRememberMeServices. UserDetailsService will" +
" not be automatically injected.");
return;
}
AbstractRememberMeServices services = (AbstractRememberMeServices) rms;
BeanDefinition beanDefinition = beanFactory.getBeanDefinition(rememberMeServicesId);
PropertyValue pv = beanDefinition.getPropertyValues().getPropertyValue("userDetailsService");
if (pv == null) {
// If it doesn't already have a UserDetailsService set, then set it.
services.setUserDetailsService(getUserDetailsService());
} else {
// If already set, then attempt to locate a caching version of the injected UserDetailsService
UserDetailsService cachingUserService = getCachingUserService(pv.getValue());
if (cachingUserService != null) {
services.setUserDetailsService(cachingUserService);
}
}
}
private void injectUserDetailsServiceIntoX509Provider(PreAuthenticatedAuthenticationProvider provider) {
BeanDefinition beanDefinition = beanFactory.getBeanDefinition(x509ProviderId);
PropertyValue pv = beanDefinition.getPropertyValues().getPropertyValue("preAuthenticatedUserDetailsService");
UserDetailsByNameServiceWrapper wrapper = new UserDetailsByNameServiceWrapper();
if (pv == null) {
wrapper.setUserDetailsService(getUserDetailsService());
provider.setPreAuthenticatedUserDetailsService(wrapper);
} else {
RootBeanDefinition preAuthUserService = (RootBeanDefinition) pv.getValue();
Object userService =
preAuthUserService.getPropertyValues().getPropertyValue("userDetailsService").getValue();
UserDetailsService cachingUserService = getCachingUserService(userService);
if (cachingUserService != null) {
wrapper.setUserDetailsService(cachingUserService);
provider.setPreAuthenticatedUserDetailsService(wrapper);
}
}
}
private void injectUserDetailsServiceIntoOpenIDProvider(Object bean) {
BeanDefinition beanDefinition = beanFactory.getBeanDefinition(openIDProviderId);
PropertyValue pv = beanDefinition.getPropertyValues().getPropertyValue("userDetailsService");
if (pv == null) {
BeanWrapperImpl beanWrapper = new BeanWrapperImpl(bean);
beanWrapper.setPropertyValue("userDetailsService", getUserDetailsService());
}
}
/**
* Obtains a user details service for use in RememberMeServices etc. Will return a caching version
* if available so should not be used for beans which need to separate the two.
*/
UserDetailsService getUserDetailsService() {
Map<String,?> beans = getBeansOfType(CachingUserDetailsService.class);
if (beans.size() == 0) {
beans = getBeansOfType(UserDetailsService.class);
}
if (beans.size() == 0) {
throw new ApplicationContextException("No UserDetailsService registered.");
} else if (beans.size() > 1) {
throw new ApplicationContextException("More than one UserDetailsService registered. Please " +
"use a specific Id reference in <remember-me/> <openid-login/> or <x509 /> elements.");
}
return (UserDetailsService) beans.values().toArray()[0];
}
private UserDetailsService getCachingUserService(Object userServiceRef) {
Assert.isInstanceOf(RuntimeBeanReference.class, userServiceRef,
"userDetailsService property value must be a RuntimeBeanReference");
String id = ((RuntimeBeanReference)userServiceRef).getBeanName();
// Overwrite with the caching version if available
String cachingId = id + AbstractUserDetailsServiceBeanDefinitionParser.CACHING_SUFFIX;
if (beanFactory.containsBeanDefinition(cachingId)) {
return (UserDetailsService) beanFactory.getBean(cachingId);
}
return null;
}
private Map<String,?> getBeansOfType(Class<?> type) {
Map<String,?> beans = beanFactory.getBeansOfType(type);
// Check ancestor bean factories if they exist and the current one has none of the required type
BeanFactory parent = beanFactory.getParentBeanFactory();
while (parent != null && beans.size() == 0) {
if (parent instanceof ListableBeanFactory) {
beans = ((ListableBeanFactory)parent).getBeansOfType(type);
}
if (parent instanceof HierarchicalBeanFactory) {
parent = ((HierarchicalBeanFactory)parent).getParentBeanFactory();
} else {
break;
}
}
return beans;
}
public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
this.beanFactory = (ConfigurableListableBeanFactory) beanFactory;
}
}