SEC-1093: Namespace support for jee element.

Adds a J2eePreAuthenticatedProcessingFilter to the stack, using a SimpleAttributes2GrantedAuthoritiesMapper to process the role attributes defined in the "mappable-roles" attribute. Provider uses a PreAuthenticatedGrantedAuthoritiesUserDetailsService by default.
This commit is contained in:
Luke Taylor 2010-07-05 19:16:46 +01:00
parent 565ef7383d
commit 443ac0487a
7 changed files with 148 additions and 12 deletions

View File

@ -46,6 +46,7 @@ public abstract class Elements {
public static final String CUSTOM_FILTER = "custom-filter";
public static final String REQUEST_CACHE = "request-cache";
public static final String X509 = "x509";
public static final String JEE = "jee";
public static final String FILTER_SECURITY_METADATA_SOURCE = "filter-security-metadata-source";
public static final String METHOD_SECURITY_METADATA_SOURCE = "method-security-metadata-source";
@Deprecated

View File

@ -25,16 +25,22 @@ import org.springframework.security.authentication.AnonymousAuthenticationProvid
import org.springframework.security.authentication.RememberMeAuthenticationProvider;
import org.springframework.security.config.BeanIds;
import org.springframework.security.config.Elements;
import org.springframework.security.core.authority.mapping.SimpleAttributes2GrantedAuthoritiesMapper;
import org.springframework.security.core.authority.mapping.SimpleMappableAttributesRetriever;
import org.springframework.security.web.access.AccessDeniedHandlerImpl;
import org.springframework.security.web.access.ExceptionTranslationFilter;
import org.springframework.security.web.authentication.AnonymousAuthenticationFilter;
import org.springframework.security.web.authentication.Http403ForbiddenEntryPoint;
import org.springframework.security.web.authentication.preauth.PreAuthenticatedAuthenticationProvider;
import org.springframework.security.web.authentication.preauth.PreAuthenticatedGrantedAuthoritiesUserDetailsService;
import org.springframework.security.web.authentication.preauth.j2ee.J2eeBasedPreAuthenticatedWebAuthenticationDetailsSource;
import org.springframework.security.web.authentication.preauth.j2ee.J2eePreAuthenticatedProcessingFilter;
import org.springframework.security.web.authentication.preauth.x509.SubjectDnX509PrincipalExtractor;
import org.springframework.security.web.authentication.preauth.x509.X509AuthenticationFilter;
import org.springframework.security.web.authentication.ui.DefaultLoginPageGeneratingFilter;
import org.springframework.security.web.authentication.www.BasicAuthenticationEntryPoint;
import org.springframework.security.web.authentication.www.BasicAuthenticationFilter;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;
import org.springframework.util.xml.DomUtils;
import org.w3c.dom.Element;
@ -88,9 +94,13 @@ final class AuthenticationConfigBuilder {
private String formFilterId = null;
private String openIDFilterId = null;
private BeanDefinition x509Filter;
private BeanDefinition x509EntryPoint;
private BeanReference x509ProviderRef;
private String x509ProviderId;
private BeanDefinition jeeFilter;
private BeanReference jeeProviderRef;
private RootBeanDefinition preAuthEntryPoint;
private String jeeProviderId;
private BeanDefinition logoutFilter;
private BeanDefinition loginPageGenerationFilter;
private BeanDefinition etf;
@ -119,6 +129,7 @@ final class AuthenticationConfigBuilder {
createFormLoginFilter(sessionStrategy, authenticationManager);
createOpenIDLoginFilter(sessionStrategy, authenticationManager);
createX509Filter(authenticationManager);
createJeeFilter(authenticationManager);
createLogoutFilter();
createLoginPageFilterIfNeeded();
createUserDetailsServiceFactory();
@ -318,7 +329,6 @@ final class AuthenticationConfigBuilder {
void createX509Filter(BeanReference authManager) {
Element x509Elt = DomUtils.getChildElementByTagName(httpElt, Elements.X509);
RootBeanDefinition filter = null;
RootBeanDefinition entryPoint = null;
if (x509Elt != null) {
BeanDefinitionBuilder filterBuilder = BeanDefinitionBuilder.rootBeanDefinition(X509AuthenticationFilter.class);
@ -334,14 +344,12 @@ final class AuthenticationConfigBuilder {
filterBuilder.addPropertyValue("principalExtractor", extractor.getBeanDefinition());
}
filter = (RootBeanDefinition) filterBuilder.getBeanDefinition();
entryPoint = new RootBeanDefinition(Http403ForbiddenEntryPoint.class);
entryPoint.setSource(pc.extractSource(x509Elt));
createPrauthEntryPoint(x509Elt);
createX509Provider();
}
x509Filter = filter;
x509EntryPoint = entryPoint;
}
private void createX509Provider() {
@ -359,6 +367,67 @@ final class AuthenticationConfigBuilder {
x509ProviderRef = new RuntimeBeanReference(x509ProviderId);
}
private void createPrauthEntryPoint(Element source) {
if (preAuthEntryPoint == null) {
preAuthEntryPoint = new RootBeanDefinition(Http403ForbiddenEntryPoint.class);
preAuthEntryPoint.setSource(pc.extractSource(source));
}
}
void createJeeFilter(BeanReference authManager) {
final String ATT_MAPPABLE_ROLES = "mappable-roles";
Element jeeElt = DomUtils.getChildElementByTagName(httpElt, Elements.JEE);
RootBeanDefinition filter = null;
if (jeeElt != null) {
BeanDefinitionBuilder filterBuilder = BeanDefinitionBuilder.rootBeanDefinition(J2eePreAuthenticatedProcessingFilter.class);
filterBuilder.getRawBeanDefinition().setSource(pc.extractSource(jeeElt));
filterBuilder.addPropertyValue("authenticationManager", authManager);
BeanDefinitionBuilder adsBldr = BeanDefinitionBuilder.rootBeanDefinition(J2eeBasedPreAuthenticatedWebAuthenticationDetailsSource.class);
adsBldr.addPropertyValue("userRoles2GrantedAuthoritiesMapper", new RootBeanDefinition(SimpleAttributes2GrantedAuthoritiesMapper.class));
String roles = jeeElt.getAttribute(ATT_MAPPABLE_ROLES);
Assert.state(StringUtils.hasText(roles));
BeanDefinitionBuilder rolesBuilder = BeanDefinitionBuilder.rootBeanDefinition(StringUtils.class);
rolesBuilder.addConstructorArgValue(roles);
rolesBuilder.setFactoryMethod("commaDelimitedListToSet");
RootBeanDefinition mappableRolesRetriever = new RootBeanDefinition(SimpleMappableAttributesRetriever.class);
mappableRolesRetriever.getPropertyValues().addPropertyValue("mappableAttributes", rolesBuilder.getBeanDefinition());
adsBldr.addPropertyValue("mappableRolesRetriever", mappableRolesRetriever);
filterBuilder.addPropertyValue("authenticationDetailsSource", adsBldr.getBeanDefinition());
filter = (RootBeanDefinition) filterBuilder.getBeanDefinition();
createPrauthEntryPoint(jeeElt);
createJeeProvider();
}
jeeFilter = filter;
}
private void createJeeProvider() {
Element jeeElt = DomUtils.getChildElementByTagName(httpElt, Elements.JEE);
BeanDefinition provider = new RootBeanDefinition(PreAuthenticatedAuthenticationProvider.class);
RootBeanDefinition uds;
if (StringUtils.hasText(jeeElt.getAttribute(ATT_USER_SERVICE_REF))) {
uds = new RootBeanDefinition();
uds.setFactoryBeanName(BeanIds.USER_DETAILS_SERVICE_FACTORY);
uds.setFactoryMethodName("authenticationUserDetailsService");
uds.getConstructorArgumentValues().addGenericArgumentValue(jeeElt.getAttribute(ATT_USER_SERVICE_REF));
} else {
uds = new RootBeanDefinition(PreAuthenticatedGrantedAuthoritiesUserDetailsService.class);
}
provider.getPropertyValues().addPropertyValue("preAuthenticatedUserDetailsService", uds);
jeeProviderId = pc.getReaderContext().registerWithGeneratedName(provider);
jeeProviderRef = new RuntimeBeanReference(jeeProviderId);
}
void createLoginPageFilterIfNeeded() {
boolean needLoginPage = formFilter != null || openIDFilter != null;
String formLoginPage = getLoginFormUrl(formEntryPoint);
@ -521,9 +590,9 @@ final class AuthenticationConfigBuilder {
return openIDEntryPoint;
}
// If X.509 has been enabled, use the preauth entry point.
if (DomUtils.getChildElementByTagName(httpElt, Elements.X509) != null) {
return x509EntryPoint;
// If X.509 or JEE have been enabled, use the preauth entry point.
if (preAuthEntryPoint != null) {
return preAuthEntryPoint;
}
pc.getReaderContext().error("No AuthenticationEntryPoint could be established. Please " +
@ -581,6 +650,10 @@ final class AuthenticationConfigBuilder {
filters.add(new OrderDecorator(x509Filter, X509_FILTER));
}
if (jeeFilter != null) {
filters.add(new OrderDecorator(jeeFilter, PRE_AUTH_FILTER));
}
if (formFilter != null) {
filters.add(new OrderDecorator(formFilter, FORM_LOGIN_FILTER));
}
@ -621,6 +694,10 @@ final class AuthenticationConfigBuilder {
providers.add(x509ProviderRef);
}
if (jeeProviderRef != null) {
providers.add(jeeProviderRef);
}
return providers;
}

View File

@ -58,7 +58,7 @@ public class UserDetailsServiceFactoryBean implements ApplicationContextAware {
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.");
" Please use a specific Id reference.");
}
return (AuthenticationUserDetailsService) beans.values().toArray()[0];
}

View File

@ -256,7 +256,7 @@ protect-pointcut.attlist &=
http =
## Container element for HTTP security configuration. Multiple elements can now be defined, each with a specific pattern to which the enclosed security configuration applies. A pattern can also be configured to bypass Spring Security's filters completely by setting the "secured" attribute to "false".
element http {http.attlist, (intercept-url* & access-denied-handler? & form-login? & openid-login? & x509? & http-basic? & logout? & session-management & remember-me? & anonymous? & port-mappings & custom-filter* & request-cache?) }
element http {http.attlist, (intercept-url* & access-denied-handler? & form-login? & openid-login? & x509? & jee? & http-basic? & logout? & session-management & remember-me? & anonymous? & port-mappings & custom-filter* & request-cache?) }
http.attlist &=
## The request URL pattern which will be mapped to the filter chain created by this <http> element. If omitted, the filter chain will match all requests.
attribute pattern {xsd:token}?
@ -566,6 +566,16 @@ 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?
jee =
## Adds a J2eePreAuthenticatedProcessingFilter to the filter chain to provide integration with container authentication.
element jee {jee.attlist}
jee.attlist &=
## A comma-separate list of roles to look for in the incoming HttpServletRequest.
attribute mappable-roles {xsd:token}
jee.attlist &=
## Explicitly specifies which user-service should be used to load user data for container authenticated clients. If ommitted, the set of mappable-roles will be used to construct the authorities for the user.
user-service-ref?
authentication-manager =
## Registers the AuthenticationManager instance and allows its list of AuthenticationProviders to be defined. Also allows you to define an alias to allow you to reference the AuthenticationManager in your own beans.
element authentication-manager {authman.attlist & authentication-provider* & ldap-authentication-provider*}

View File

@ -641,6 +641,7 @@
</xs:annotation><xs:complexType>
<xs:attributeGroup ref="security:x509.attlist"/>
</xs:complexType></xs:element>
<xs:element ref="security:jee"/>
<xs:element name="http-basic"><xs:annotation>
<xs:documentation>Adds support for basic authentication</xs:documentation>
</xs:annotation><xs:complexType>
@ -1212,6 +1213,23 @@
</xs:annotation>
</xs:attribute>
</xs:attributeGroup>
<xs:element name="jee"><xs:annotation>
<xs:documentation>Adds a J2eePreAuthenticatedProcessingFilter to the filter chain to provide integration with container authentication.</xs:documentation>
</xs:annotation><xs:complexType>
<xs:attributeGroup ref="security:jee.attlist"/>
</xs:complexType></xs:element>
<xs:attributeGroup name="jee.attlist">
<xs:attribute name="mappable-roles" use="required" type="xs:token">
<xs:annotation>
<xs:documentation>A comma-separate list of roles to look for in the incoming HttpServletRequest.</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute name="user-service-ref" type="xs:token">
<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-manager"><xs:annotation>
<xs:documentation>Registers the AuthenticationManager instance and allows its list of AuthenticationProviders to be defined. Also allows you to define an alias to allow you to reference the AuthenticationManager in your own beans.</xs:documentation>
</xs:annotation><xs:complexType>

View File

@ -1,5 +1,6 @@
package org.springframework.security.config.http;
import java.security.Principal
import java.util.Collection;
import java.util.Map;
import java.util.Iterator;
@ -17,6 +18,8 @@ import org.springframework.beans.factory.parsing.BeanDefinitionParsingException;
import org.springframework.context.support.AbstractXmlApplicationContext
import org.springframework.security.config.BeanIds;
import org.springframework.security.config.util.InMemoryXmlApplicationContext;
import org.springframework.security.core.authority.AuthorityUtils;
import org.springframework.security.core.context.SecurityContext
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.util.FieldUtils;
import org.springframework.security.access.AccessDeniedException
@ -471,6 +474,34 @@ class MiscHttpConfigTests extends AbstractHttpConfigTests {
expect:
getFilter(UsernamePasswordAuthenticationFilter).authenticationManager.eraseCredentialsAfterAuthentication == false
}
def jeeFilterExtractsExpectedRoles() {
xml.http() {
jee('mappable-roles': 'admin,user,a,b,c')
}
createAppContext()
FilterChainProxy fcp = appContext.getBean(BeanIds.FILTER_CHAIN_PROXY)
Principal p = Mock(Principal)
p.getName() >> 'joe'
when:
MockHttpServletRequest request = new MockHttpServletRequest("GET","/something")
request.setUserPrincipal(p)
request.addUserRole('admin')
request.addUserRole('user')
request.addUserRole('c')
request.addUserRole('notmapped')
fcp.doFilter(request, new MockHttpServletResponse(), new MockFilterChain())
SecurityContext ctx = request.getSession().getAttribute(HttpSessionSecurityContextRepository.SPRING_SECURITY_CONTEXT_KEY);
Set<String> roles = AuthorityUtils.authorityListToSet(ctx.getAuthentication().getAuthorities());
then:
roles.size() == 3
roles.contains 'ROLE_admin'
roles.contains 'ROLE_user'
roles.contains 'ROLE_c'
}
}
class MockEntryPoint extends LoginUrlAuthenticationEntryPoint {

View File

@ -24,8 +24,7 @@ public class SimpleMappableAttributesRetriever implements MappableAttributesRetr
return mappableAttributes;
}
@SuppressWarnings("unchecked")
public void setMappableAttributes(Set aMappableRoles) {
public void setMappableAttributes(Set<String> aMappableRoles) {
mappableAttributes = new HashSet<String>();
mappableAttributes.addAll(aMappableRoles);
mappableAttributes = Collections.unmodifiableSet(mappableAttributes);