SEC-645: Reimplementation of X509 provider and namespace implementation.

This commit is contained in:
Luke Taylor 2008-01-27 22:45:44 +00:00
parent 9af7ab68bf
commit acf5601714
12 changed files with 237 additions and 73 deletions

View File

@ -2,6 +2,8 @@ package org.springframework.security.config;
/**
* Contains all the default Bean IDs created by the namespace support in Spring Security 2.
* <p>
* These are mainly intended for internal use.
*
* @author Ben Alex
* @version $Id$
@ -44,4 +46,7 @@ public abstract class BeanIds {
public static final String EMBEDDED_APACHE_DS = "_apacheDirectoryServerContainer";
public static final String CONTEXT_SOURCE = "_securityContextSource";
public static final String PORT_MAPPER = "_portMapper";
public static final String X509_FILTER = "_x509ProcessingFilter";
public static final String X509_AUTH_PROVIDER = "_x509AuthenitcationProvider";
public static final String PRE_AUTH_ENTRY_POINT = "_preAuthenticatedProcessingFilterEntryPoint";
}

View File

@ -31,4 +31,5 @@ abstract class Elements {
public static final String PORT_MAPPING = "port-mapping";
public static final String CUSTOM_FILTER = "custom-filter";
public static final String CUSTOM_AUTH_RPOVIDER = "custom-authentication-provider";
public static final String X509 = "x509";
}

View File

@ -256,6 +256,11 @@ public class HttpSecurityBeanDefinitionParser implements BeanDefinitionParser {
new BasicAuthenticationBeanDefinitionParser(realm).parse(basicAuthElt, parserContext);
}
Element x509Elt = DomUtils.getChildElementByTagName(element, Elements.X509);
if (x509Elt != null) {
new X509BeanDefinitionParser().parse(x509Elt, parserContext);
}
registry.registerBeanDefinition(BeanIds.FILTER_CHAIN_PROXY, filterChainProxy);
registry.registerBeanDefinition(BeanIds.HTTP_SESSION_CONTEXT_INTEGRATION_FILTER, httpScif);
registry.registerBeanDefinition(BeanIds.EXCEPTION_TRANSLATION_FILTER, exceptionTranslationFilterBuilder.getBeanDefinition());

View File

@ -11,6 +11,7 @@ import javax.servlet.Filter;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.BeansException;
import org.springframework.beans.PropertyValue;
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.config.BeanFactoryPostProcessor;
@ -24,6 +25,7 @@ import org.springframework.security.ui.AuthenticationEntryPoint;
import org.springframework.security.ui.basicauth.BasicProcessingFilter;
import org.springframework.security.ui.rememberme.RememberMeServices;
import org.springframework.security.util.FilterChainProxy;
import org.springframework.security.providers.preauth.UserDetailsByNameServiceWrapper;
import org.springframework.util.Assert;
/**
@ -33,12 +35,14 @@ import org.springframework.util.Assert;
* @author Luke Taylor
* @author Ben Alex
* @version $Id$
* @since 2.0
*/
public class HttpSecurityConfigPostProcessor implements BeanFactoryPostProcessor, Ordered {
private Log logger = LogFactory.getLog(getClass());
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
injectUserDetailsServiceIntoRememberMeServices(beanFactory);
injectUserDetailsServiceIntoX509Provider(beanFactory);
injectAuthenticationEntryPointIntoExceptionTranslationFilter(beanFactory);
@ -50,8 +54,28 @@ public class HttpSecurityConfigPostProcessor implements BeanFactoryPostProcessor
private void injectUserDetailsServiceIntoRememberMeServices(ConfigurableListableBeanFactory beanFactory) {
try {
BeanDefinition rememberMeServices = beanFactory.getBeanDefinition(BeanIds.REMEMBER_ME_SERVICES);
rememberMeServices.getPropertyValues().addPropertyValue("userDetailsService",
PropertyValue pv = rememberMeServices.getPropertyValues().getPropertyValue("userDetailsService");
if (pv == null) {
rememberMeServices.getPropertyValues().addPropertyValue("userDetailsService",
ConfigUtils.getUserDetailsService(beanFactory));
}
} catch (NoSuchBeanDefinitionException e) {
// ignore
}
}
private void injectUserDetailsServiceIntoX509Provider(ConfigurableListableBeanFactory beanFactory) {
try {
BeanDefinition x509AuthProvider = beanFactory.getBeanDefinition(BeanIds.X509_AUTH_PROVIDER);
PropertyValue pv = x509AuthProvider.getPropertyValues().getPropertyValue("preAuthenticatedUserDetailsService");
if (pv == null) {
UserDetailsByNameServiceWrapper preAuthUserService = new UserDetailsByNameServiceWrapper();
preAuthUserService.setUserDetailsService(ConfigUtils.getUserDetailsService(beanFactory));
x509AuthProvider.getPropertyValues().addPropertyValue("preAuthenticatedUserDetailsService",
preAuthUserService);
}
} catch (NoSuchBeanDefinitionException e) {
// ignore
}

View File

@ -0,0 +1,64 @@
package org.springframework.security.config;
import org.springframework.security.ui.preauth.PreAuthenticatedProcessingFilterEntryPoint;
import org.springframework.security.ui.preauth.x509.X509PreAuthenticatedProcessingFilter;
import org.springframework.security.ui.preauth.x509.SubjectDnX509PrincipalExtractor;
import org.springframework.security.providers.preauth.PreAuthenticatedAuthenticationProvider;
import org.springframework.security.providers.preauth.UserDetailsByNameServiceWrapper;
import org.springframework.beans.factory.xml.BeanDefinitionParser;
import org.springframework.beans.factory.xml.ParserContext;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.config.RuntimeBeanReference;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.support.RootBeanDefinition;
import org.springframework.util.StringUtils;
import org.w3c.dom.Element;
/**
* Parses x509 element in namespace, registering an {@link X509PreAuthenticatedProcessingFilter} instance and a
* {@link PreAuthenticatedProcessingFilterEntryPoint}.
*
* @author Luke Taylor
* @version $Id$
* @since 2.0
*/
public class X509BeanDefinitionParser implements BeanDefinitionParser {
public static final String ATT_REGEX = "subject-principal-regex";
public static final String ATT_USER_SERVICE_REF = "user-service-ref";
public BeanDefinition parse(Element element, ParserContext parserContext) {
BeanDefinitionBuilder filterBuilder = BeanDefinitionBuilder.rootBeanDefinition(X509PreAuthenticatedProcessingFilter.class);
RootBeanDefinition entryPoint = new RootBeanDefinition(PreAuthenticatedProcessingFilterEntryPoint.class);
String regex = element.getAttribute(ATT_REGEX);
if (StringUtils.hasText(regex)) {
SubjectDnX509PrincipalExtractor extractor = new SubjectDnX509PrincipalExtractor();
extractor.setSubjectDnRegex(regex);
filterBuilder.addPropertyValue("principalExtractor", extractor);
}
BeanDefinition provider = new RootBeanDefinition(PreAuthenticatedAuthenticationProvider.class);
ConfigUtils.getRegisteredProviders(parserContext).add(provider);
parserContext.getRegistry().registerBeanDefinition(BeanIds.X509_AUTH_PROVIDER, provider);
String userServiceRef = element.getAttribute(ATT_USER_SERVICE_REF);
if (StringUtils.hasText(userServiceRef)) {
RuntimeBeanReference userService = new RuntimeBeanReference(userServiceRef);
BeanDefinition preAuthUserService = new RootBeanDefinition(UserDetailsByNameServiceWrapper.class);
preAuthUserService.getPropertyValues().addPropertyValue("userDetailsService", userService);
provider.getPropertyValues().addPropertyValue("preAuthenticatedUserDetailsService", preAuthUserService);
}
parserContext.getRegistry().registerBeanDefinition(BeanIds.PRE_AUTH_ENTRY_POINT, entryPoint);
filterBuilder.addPropertyValue("authenticationManager", new RuntimeBeanReference(BeanIds.AUTHENTICATION_MANAGER));
parserContext.getRegistry().registerBeanDefinition(BeanIds.X509_FILTER, filterBuilder.getBeanDefinition());
return null;
}
}

View File

@ -24,6 +24,7 @@ import org.springframework.util.Assert;
* a UsernameNotFoundException.
*
* @author Ruud Senden
* @version $Id$
* @since 2.0
*/
public class PreAuthenticatedAuthenticationProvider implements AuthenticationProvider, InitializingBean, Ordered {

View File

@ -62,11 +62,11 @@ public abstract class FilterChainOrder {
}
/** Allows filters to be used by name in the XSD file without explicit reference to Java constants */
public static Integer getOrder(String filterName) {
public static int getOrder(String filterName) {
Integer order = (Integer) filterNameToOrder.get(filterName);
Assert.notNull(order, "Unable to match filter name " + filterName);
return order;
return order.intValue();
}
}

View File

@ -31,6 +31,10 @@ public class X509PreAuthenticatedProcessingFilter extends AbstractPreAuthenticat
X509Certificate[] certs = (X509Certificate[]) request.getAttribute("javax.servlet.request.X509Certificate");
if (certs != null && certs.length > 0) {
if (logger.isDebugEnabled()) {
logger.debug("X.509 client authentication certificate:" + certs[0]);
}
return certs[0];
}

View File

@ -27,9 +27,13 @@ id =
## A bean identifier, used for referring to the bean elsewhere in the context.
attribute id {xsd:ID}
ref =
## Defines a reference to a Spring bean id.
## Defines a reference to a Spring bean Id.
attribute ref {xsd:string}
user-service-ref =
## A reference to a user-service (or UserDetailsService bean) Id
attribute user-service-ref {xsd:string}
password-encoder =
## element which defines a password encoding strategy. Used by an authentication provider to convert submitted passwords to hashed versions, for example.
element password-encoder {ref | (hash? & base64? & salt-source*)}
@ -88,7 +92,7 @@ protect.attlist &=
annotation-driven =
## Activates security annotation scanning. All beans registered in the Spring application context will be scanned for Spring Security annotations. Where found, the beans will automatically be proxied and security authorization applied to the methods accordingly. Please ensure you have the spring-security-tiger-XXX.jar on your classpath.
## Activates security annotation scanning. All beans registered in the Spring application context will be scanned for Spring Security annotations. Where found, the beans will automatically be proxied and security authorization applied to the methods accordingly. Please ensure you have the spring-security-tiger-XXX.jar on your classpath.
element annotation-driven {annotation-driven.attlist}
annotation-driven.attlist &=
## Specifies that JSR-250 style attributes are to be used (for example "RolesAllowed" instead of "Secured"). This will require the javax.annotation.security classes on the classpath. Defaults to false.
@ -99,7 +103,7 @@ annotation-driven.attlist &=
http =
## Container element for HTTP security configuration
element http {http.attlist, (intercept-url+ & form-login? & http-basic? & logout? & concurrent-session-control? & remember-me? & anonymous? & port-mappings) }
element http {http.attlist, (intercept-url+ & form-login? & x509? & http-basic? & logout? & concurrent-session-control? & remember-me? & anonymous? & port-mappings) }
http.attlist &=
## Automatically registers a login form, BASIC authentication, anonymous authentication, logout services, remember-me and servlet-api-integration. If set to "true", all of these capabilities are added (although you can still customize the configuration of each by providing the respective element). If unspecified, defaults to "false".
attribute auto-config {"true" | "false" }?
@ -207,48 +211,18 @@ remember-me.attlist &=
(attribute key {xsd:string} | (attribute token-repository-ref {xsd:string} | attribute data-source-ref {xsd:string}))
anonymous =
## Adds support for automatically granting all anonymous web requests a particular principal identity and a corresponding granted authority.
## Adds support for automatically granting all anonymous web requests a particular principal identity and a corresponding granted authority.
element anonymous {anonymous.attlist}
anonymous.attlist &=
## The key used between the provider and filter. This generally does not need to be set. If unset, it will default to "doesNotMatter".
## The key used between the provider and filter. This generally does not need to be set. If unset, it will default to "doesNotMatter".
attribute key {xsd:string}?
anonymous.attlist &=
## The username that should be assigned to the anonymous request. This allows the principal to be identified, which may be important for logging and auditing. if unset, defaults to "anonymousUser".
## The username that should be assigned to the anonymous request. This allows the principal to be identified, which may be important for logging and auditing. if unset, defaults to "anonymousUser".
attribute username {xsd:string}?
anonymous.attlist &=
## The granted authority that should be assigned to the anonymous request. Commonly this is used to assign the anonymous request particular roles, which can subsequently be used in authorization decisions. If unset, defaults to "ROLE_ANONYMOUS".
## The granted authority that should be assigned to the anonymous request. Commonly this is used to assign the anonymous request particular roles, which can subsequently be used in authorization decisions. If unset, defaults to "ROLE_ANONYMOUS".
attribute granted-authority {xsd:string}?
authentication-provider =
## Indicates that the contained user-service should be used as an authentication source.
element authentication-provider {ap.attlist & (user-service | jdbc-user-service) & password-encoder}
ap.attlist &=
## Specifies a reference to a separately configured UserDetailsService from which to obtain authentication data.
attribute user-service-ref {xsd:string}?
custom-authentication-provider =
element custom-authentication-provider {cap.attlist}
cap.attlist &= empty
user-service =
## Creates an in-memory UserDetailsService from a properties file or a list of "user" child elements.
element user-service {id? & (properties-file | (user*))}
properties-file =
attribute properties {xsd:string}?
user =
## Represents a user in the application.
element user {user.attlist, empty}
user.attlist &=
## The username assigned to the user.
attribute name {xsd:string}
user.attlist &=
## The password assigned to the user. This may be hashed if the corresponding authentication provider supports hashing (remember to set the "hash" attribute of the "user-service" element).
attribute password {xsd:string}
user.attlist &=
## One of more authorities granted to the user. Separate authorities with a comma (but no space). For example, "ROLE_USER,ROLE_ADMINISTRATOR"
attribute authorities {xsd:string}
port-mappings =
## Defines the list of mappings between http and https ports for use in redirects
element port-mappings {port-mappings.attlist, port-mapping+}
@ -262,11 +236,52 @@ http-port = attribute http {xsd:integer}
https-port = attribute https {xsd:integer}
x509 =
## Adds support for X.509 client authentication.
element x509 {x509.attlist}
x509.attlist &=
## The regular expression used to obtain the username from the certificate's subject. Defaults to matching on the common name using the pattern "CN=(.*?),".
attribute subject-principal-regex {xsd:string}?
x509.attlist &=
## Explicitly specifies which user-service should be used to load user data for X.509 authenticated clients. If ommitted, the default user-service will be used.
user-service-ref?
authentication-provider =
## Indicates that the contained user-service should be used as an authentication source.
element authentication-provider {ap.attlist & (user-service | jdbc-user-service) & password-encoder}
ap.attlist &=
## Specifies a reference to a separately configured UserDetailsService from which to obtain authentication data.
user-service-ref?
custom-authentication-provider =
element custom-authentication-provider {cap.attlist}
cap.attlist &= empty
user-service =
## Creates an in-memory UserDetailsService from a properties file or a list of "user" child elements.
element user-service {id? & (properties-file | (user*))}
properties-file =
attribute properties {xsd:string}?
user =
## Represents a user in the application.
element user {user.attlist, empty}
user.attlist &=
## The username assigned to the user.
attribute name {xsd:string}
user.attlist &=
## The password assigned to the user. This may be hashed if the corresponding authentication provider supports hashing (remember to set the "hash" attribute of the "user-service" element).
attribute password {xsd:string}
user.attlist &=
## One of more authorities granted to the user. Separate authorities with a comma (but no space). For example, "ROLE_USER,ROLE_ADMINISTRATOR"
attribute authorities {xsd:string}
jdbc-user-service =
## Causes creation of a JDBC-based UserDetailsService.
## Causes creation of a JDBC-based UserDetailsService.
element jdbc-user-service {id? & jdbc-user-service.attlist}
jdbc-user-service.attlist &=
## The bean ID of the DataSource which provides the required tables.
## The bean ID of the DataSource which provides the required tables.
attribute data-source-ref {xsd:string}

View File

@ -66,7 +66,14 @@
<xs:attributeGroup name="ref">
<xs:attribute name="ref" use="required" type="xs:string">
<xs:annotation>
<xs:documentation>Defines a reference to a Spring bean id.</xs:documentation>
<xs:documentation>Defines a reference to a Spring bean Id.</xs:documentation>
</xs:annotation>
</xs:attribute>
</xs:attributeGroup>
<xs:attributeGroup name="user-service-ref">
<xs:attribute name="user-service-ref" use="required" type="xs:string">
<xs:annotation>
<xs:documentation>A reference to a user-service (or UserDetailsService bean) Id</xs:documentation>
</xs:annotation>
</xs:attribute>
</xs:attributeGroup>
@ -80,7 +87,7 @@
</xs:sequence>
<xs:attribute name="ref" type="xs:string">
<xs:annotation>
<xs:documentation>Defines a reference to a Spring bean id.</xs:documentation>
<xs:documentation>Defines a reference to a Spring bean Id.</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute name="hash">
@ -258,6 +265,7 @@
<xs:choice minOccurs="0" maxOccurs="unbounded">
<xs:element ref="security:intercept-url"/>
<xs:element ref="security:form-login"/>
<xs:element ref="security:x509"/>
<xs:element ref="security:http-basic"/>
<xs:element ref="security:logout"/>
<xs:element ref="security:concurrent-session-control"/>
@ -539,6 +547,48 @@
</xs:annotation>
</xs:attribute>
</xs:attributeGroup>
<xs:element name="port-mappings">
<xs:annotation>
<xs:documentation>Defines the list of mappings between http and https ports for use in redirects</xs:documentation>
</xs:annotation>
<xs:complexType>
<xs:sequence>
<xs:element maxOccurs="unbounded" ref="security:port-mapping"/>
</xs:sequence>
</xs:complexType>
</xs:element>
<xs:element name="port-mapping">
<xs:complexType>
<xs:attributeGroup ref="security:http-port"/>
<xs:attributeGroup ref="security:https-port"/>
</xs:complexType>
</xs:element>
<xs:attributeGroup name="http-port">
<xs:attribute name="http" use="required" type="xs:integer"/>
</xs:attributeGroup>
<xs:attributeGroup name="https-port">
<xs:attribute name="https" use="required" type="xs:integer"/>
</xs:attributeGroup>
<xs:element name="x509">
<xs:annotation>
<xs:documentation>Adds support for X.509 client authentication.</xs:documentation>
</xs:annotation>
<xs:complexType>
<xs:attributeGroup ref="security:x509.attlist"/>
</xs:complexType>
</xs:element>
<xs:attributeGroup name="x509.attlist">
<xs:attribute name="subject-regex-match" type="xs:string">
<xs:annotation>
<xs:documentation>The regular expression used to obtain the username from the certificate's subject. Defaults to matching on the common name using the pattern "CN=(.*?),".</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute name="user-service-ref" type="xs:string">
<xs:annotation>
<xs:documentation>A reference to a user-service (or UserDetailsService bean) Id</xs:documentation>
</xs:annotation>
</xs:attribute>
</xs:attributeGroup>
<xs:element name="authentication-provider">
<xs:annotation>
<xs:documentation>Indicates that the contained user-service should be used as an authentication source. </xs:documentation>
@ -557,7 +607,7 @@
<xs:attributeGroup name="ap.attlist">
<xs:attribute name="user-service-ref" type="xs:string">
<xs:annotation>
<xs:documentation>Specifies a reference to a separately configured UserDetailsService from which to obtain authentication data. </xs:documentation>
<xs:documentation>A reference to a user-service (or UserDetailsService bean) Id</xs:documentation>
</xs:annotation>
</xs:attribute>
</xs:attributeGroup>
@ -608,28 +658,6 @@
</xs:annotation>
</xs:attribute>
</xs:attributeGroup>
<xs:element name="port-mappings">
<xs:annotation>
<xs:documentation>Defines the list of mappings between http and https ports for use in redirects</xs:documentation>
</xs:annotation>
<xs:complexType>
<xs:sequence>
<xs:element maxOccurs="unbounded" ref="security:port-mapping"/>
</xs:sequence>
</xs:complexType>
</xs:element>
<xs:element name="port-mapping">
<xs:complexType>
<xs:attributeGroup ref="security:http-port"/>
<xs:attributeGroup ref="security:https-port"/>
</xs:complexType>
</xs:element>
<xs:attributeGroup name="http-port">
<xs:attribute name="http" use="required" type="xs:integer"/>
</xs:attributeGroup>
<xs:attributeGroup name="https-port">
<xs:attribute name="https" use="required" type="xs:integer"/>
</xs:attributeGroup>
<xs:element name="jdbc-user-service">
<xs:annotation>
<xs:documentation>Causes creation of a JDBC-based UserDetailsService.</xs:documentation>

View File

@ -6,6 +6,7 @@ import org.springframework.security.intercept.web.FilterInvocationDefinitionSour
import org.springframework.security.intercept.web.FilterInvocation;
import org.springframework.security.securechannel.ChannelProcessingFilter;
import org.springframework.security.ui.ExceptionTranslationFilter;
import org.springframework.security.ui.preauth.x509.X509PreAuthenticatedProcessingFilter;
import org.springframework.security.ui.basicauth.BasicProcessingFilter;
import org.springframework.security.ui.logout.LogoutFilter;
import org.springframework.security.ui.rememberme.RememberMeProcessingFilter;
@ -222,6 +223,17 @@ public class HttpSecurityBeanDefinitionParserTests {
assertTrue(rememberMeServices instanceof PersistentTokenBasedRememberMeServices);
}
@Test
public void x509SupportAddsFilterAtExpectedPosition() throws Exception {
setContext(
"<http auto-config='true'>" +
" <x509 />" +
"</http>" + AUTH_PROVIDER_XML);
List filters = getFilterChainProxy().getFilters("/someurl");
assertTrue(filters.get(2) instanceof X509PreAuthenticatedProcessingFilter);
}
private void setContext(String context) {
appContext = new InMemoryXmlApplicationContext(context);
}

View File

@ -22,8 +22,11 @@
<intercept-url pattern="/post.html" access="ROLE_TELLER" />
-->
<intercept-url pattern="/**" access="IS_AUTHENTICATED_ANONYMOUSLY" />
<!-- All of this is unnecessary if auto-config="true" -->
<!--
Uncomment to enable X509 client authentication support
<x509 />
-->
<!-- All of this is unnecessary if auto-config="true" -->
<form-login />
<anonymous />
<http-basic />
@ -33,6 +36,8 @@
<concurrent-session-control max-sessions="1" exception-if-maximum-exceeded="true"/>
</http>
<!--
Uncomment to add X509 support as an external filter definition (an alternative to the <x509 /> element).
<beans:bean id="x509Filter" class="org.springframework.security.ui.preauth.x509.X509PreAuthenticatedProcessingFilter" autowire="byType">
<custom-filter after="X509_FILTER"/>
@ -44,7 +49,7 @@
<beans:bean class="org.springframework.security.providers.preauth.UserDetailsByNameServiceWrapper" autowire="byType"/>
</beans:property>
</beans:bean>
-->
<!--
Usernames/Passwords are
rod/koala