diff --git a/config/src/main/java/org/springframework/security/config/annotation/web/AbstractRequestMatcherConfigurer.java b/config/src/main/java/org/springframework/security/config/annotation/web/AbstractRequestMatcherRegistry.java similarity index 93% rename from config/src/main/java/org/springframework/security/config/annotation/web/AbstractRequestMatcherConfigurer.java rename to config/src/main/java/org/springframework/security/config/annotation/web/AbstractRequestMatcherRegistry.java index 1177ef8631..6b0deaf575 100644 --- a/config/src/main/java/org/springframework/security/config/annotation/web/AbstractRequestMatcherConfigurer.java +++ b/config/src/main/java/org/springframework/security/config/annotation/web/AbstractRequestMatcherRegistry.java @@ -20,9 +20,7 @@ import java.util.Arrays; import java.util.List; import org.springframework.http.HttpMethod; -import org.springframework.security.config.annotation.SecurityBuilder; -import org.springframework.security.config.annotation.SecurityConfigurerAdapter; -import org.springframework.security.config.annotation.web.configurers.AbstractRequestMatcherMappingConfigurer; +import org.springframework.security.config.annotation.web.configurers.AbstractConfigAttributeRequestMatcherRegistry; import org.springframework.security.web.util.matcher.AntPathRequestMatcher; import org.springframework.security.web.util.matcher.AnyRequestMatcher; import org.springframework.security.web.util.matcher.RegexRequestMatcher; @@ -33,14 +31,12 @@ import org.springframework.security.web.util.matcher.RequestMatcher; * {@link RequestMatcher} require a certain level of authorization. * * - * @param The Builder that is building Object O and is configured by this {@link AbstractRequestMatcherMappingConfigurer} * @param The object that is returned or Chained after creating the RequestMatcher - * @param The Object being built by Builder B * * @author Rob Winch * @since 3.2 */ -public abstract class AbstractRequestMatcherConfigurer,C,O> extends SecurityConfigurerAdapter { +public abstract class AbstractRequestMatcherRegistry { private static final RequestMatcher ANY_REQUEST = AnyRequestMatcher.INSTANCE; /** * Maps any request. @@ -109,7 +105,7 @@ public abstract class AbstractRequestMatcherConfigurer authorizeRequests() throws Exception { - return getOrApply(new ExpressionUrlAuthorizationConfigurer()); + public ExpressionUrlAuthorizationConfigurer.ExpressionInterceptUrlRegistry authorizeRequests() throws Exception { + return getOrApply(new ExpressionUrlAuthorizationConfigurer()).getRegistry(); } /** @@ -1020,8 +1020,8 @@ public final class HttpSecurity extends AbstractConfiguredSecurityBuilder requiresChannel() throws Exception { - return getOrApply(new ChannelSecurityConfigurer()); + public ChannelSecurityConfigurer.ChannelRequestMatcherRegistry requiresChannel() throws Exception { + return getOrApply(new ChannelSecurityConfigurer()).getRegistry(); } /** @@ -1312,7 +1312,7 @@ public final class HttpSecurity extends AbstractConfiguredSecurityBuilder { + public final class RequestMatcherConfigurer extends AbstractRequestMatcherRegistry { protected RequestMatcherConfigurer chainRequestMatchers(List requestMatchers) { requestMatcher(new OrRequestMatcher(requestMatchers)); diff --git a/config/src/main/java/org/springframework/security/config/annotation/web/builders/WebSecurity.java b/config/src/main/java/org/springframework/security/config/annotation/web/builders/WebSecurity.java index 6b9955d1dc..19d083dbdd 100644 --- a/config/src/main/java/org/springframework/security/config/annotation/web/builders/WebSecurity.java +++ b/config/src/main/java/org/springframework/security/config/annotation/web/builders/WebSecurity.java @@ -29,7 +29,7 @@ import org.springframework.context.ApplicationContextAware; import org.springframework.security.access.expression.SecurityExpressionHandler; import org.springframework.security.config.annotation.AbstractConfiguredSecurityBuilder; import org.springframework.security.config.annotation.SecurityBuilder; -import org.springframework.security.config.annotation.web.AbstractRequestMatcherConfigurer; +import org.springframework.security.config.annotation.web.AbstractRequestMatcherRegistry; import org.springframework.security.config.annotation.web.WebSecurityConfigurer; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; import org.springframework.security.config.annotation.web.configuration.WebSecurityConfiguration; @@ -313,7 +313,7 @@ public final class WebSecurity extends * @author Rob Winch * @since 3.2 */ - public final class IgnoredRequestConfigurer extends AbstractRequestMatcherConfigurer { + public final class IgnoredRequestConfigurer extends AbstractRequestMatcherRegistry { @Override protected IgnoredRequestConfigurer chainRequestMatchers(List requestMatchers) { @@ -324,7 +324,6 @@ public final class WebSecurity extends /** * Returns the {@link WebSecurity} to be returned for chaining. */ - @Override public WebSecurity and() { return WebSecurity.this; } diff --git a/config/src/main/java/org/springframework/security/config/annotation/web/configurers/AbstractRequestMatcherMappingConfigurer.java b/config/src/main/java/org/springframework/security/config/annotation/web/configurers/AbstractConfigAttributeRequestMatcherRegistry.java similarity index 92% rename from config/src/main/java/org/springframework/security/config/annotation/web/configurers/AbstractRequestMatcherMappingConfigurer.java rename to config/src/main/java/org/springframework/security/config/annotation/web/configurers/AbstractConfigAttributeRequestMatcherRegistry.java index 2e567ffffa..dba0458553 100644 --- a/config/src/main/java/org/springframework/security/config/annotation/web/configurers/AbstractRequestMatcherMappingConfigurer.java +++ b/config/src/main/java/org/springframework/security/config/annotation/web/configurers/AbstractConfigAttributeRequestMatcherRegistry.java @@ -21,8 +21,7 @@ import java.util.LinkedHashMap; import java.util.List; import org.springframework.security.access.ConfigAttribute; -import org.springframework.security.config.annotation.SecurityBuilder; -import org.springframework.security.config.annotation.web.AbstractRequestMatcherConfigurer; +import org.springframework.security.config.annotation.web.AbstractRequestMatcherRegistry; import org.springframework.security.web.util.matcher.RequestMatcher; /** @@ -32,15 +31,13 @@ import org.springframework.security.web.util.matcher.RequestMatcher; * @author Rob Winch * @since 3.2 * - * @param The Builder that is building Object O and is configured by this {@link AbstractRequestMatcherMappingConfigurer} * @param The object that is returned or Chained after creating the RequestMatcher - * @param The Object being built by Builder B * * @see ChannelSecurityConfigurer * @see UrlAuthorizationConfigurer * @see ExpressionUrlAuthorizationConfigurer */ -public abstract class AbstractRequestMatcherMappingConfigurer,C,O> extends AbstractRequestMatcherConfigurer { +public abstract class AbstractConfigAttributeRequestMatcherRegistry extends AbstractRequestMatcherRegistry { private List urlMappings = new ArrayList(); private List unmappedMatchers; diff --git a/config/src/main/java/org/springframework/security/config/annotation/web/configurers/AbstractInterceptUrlConfigurer.java b/config/src/main/java/org/springframework/security/config/annotation/web/configurers/AbstractInterceptUrlConfigurer.java index 72de2bd786..77c9027f1b 100644 --- a/config/src/main/java/org/springframework/security/config/annotation/web/configurers/AbstractInterceptUrlConfigurer.java +++ b/config/src/main/java/org/springframework/security/config/annotation/web/configurers/AbstractInterceptUrlConfigurer.java @@ -23,7 +23,6 @@ import org.springframework.security.access.vote.AffirmativeBased; import org.springframework.security.authentication.AuthenticationManager; import org.springframework.security.config.annotation.SecurityConfigurer; import org.springframework.security.config.annotation.web.HttpSecurityBuilder; -import org.springframework.security.web.DefaultSecurityFilterChain; import org.springframework.security.web.access.intercept.FilterInvocationSecurityMetadataSource; import org.springframework.security.web.access.intercept.FilterSecurityInterceptor; @@ -53,49 +52,21 @@ import org.springframework.security.web.access.intercept.FilterSecurityIntercept *
  • {@link org.springframework.security.config.annotation.web.builders.HttpSecurity#getAuthenticationManager()}
  • * * + * + * @param the AbstractInterceptUrlConfigurer * @param the type of {@link HttpSecurityBuilder} that is being configured - * @param the type of object that is changed - * @param the type of object that is changed for the {@link AbstractRequestMatcherMappingConfigurer} * * @author Rob Winch * @since 3.2 * @see ExpressionUrlAuthorizationConfigurer * @see UrlAuthorizationConfigurer */ -abstract class AbstractInterceptUrlConfigurer,C,R> extends - AbstractRequestMatcherMappingConfigurer implements - SecurityConfigurer { +abstract class AbstractInterceptUrlConfigurer, H extends HttpSecurityBuilder> extends + AbstractHttpConfigurer{ private Boolean filterSecurityInterceptorOncePerRequest; private AccessDecisionManager accessDecisionManager; - /** - * Allows setting the {@link AccessDecisionManager}. If none is provided, a default {@l AccessDecisionManager} is - * created. - * - * @param accessDecisionManager the {@link AccessDecisionManager} to use - * @return the {@link AbstractInterceptUrlConfigurer} for further customization - */ - public C accessDecisionManager( - AccessDecisionManager accessDecisionManager) { - this.accessDecisionManager = accessDecisionManager; - return getSelf(); - } - - /** - * Allows setting if the {@link FilterSecurityInterceptor} should be only applied once per request (i.e. if the - * filter intercepts on a forward, should it be applied again). - * - * @param filterSecurityInterceptorOncePerRequest if the {@link FilterSecurityInterceptor} should be only applied - * once per request - * @return the {@link AbstractInterceptUrlConfigurer} for further customization - */ - public C filterSecurityInterceptorOncePerRequest( - boolean filterSecurityInterceptorOncePerRequest) { - this.filterSecurityInterceptorOncePerRequest = filterSecurityInterceptorOncePerRequest; - return getSelf(); - } - @Override public void configure(H http) throws Exception { FilterInvocationSecurityMetadataSource metadataSource = createMetadataSource(http); @@ -134,6 +105,47 @@ abstract class AbstractInterceptUrlConfigurer,C @SuppressWarnings("rawtypes") abstract List getDecisionVoters(H http); + abstract class AbstractInterceptUrlRegistry,T> extends AbstractConfigAttributeRequestMatcherRegistry { + + /** + * Allows setting the {@link AccessDecisionManager}. If none is provided, a default {@l AccessDecisionManager} is + * created. + * + * @param accessDecisionManager the {@link AccessDecisionManager} to use + * @return the {@link AbstractInterceptUrlConfigurer} for further customization + */ + public R accessDecisionManager( + AccessDecisionManager accessDecisionManager) { + AbstractInterceptUrlConfigurer.this.accessDecisionManager = accessDecisionManager; + return getSelf(); + } + + /** + * Allows setting if the {@link FilterSecurityInterceptor} should be only applied once per request (i.e. if the + * filter intercepts on a forward, should it be applied again). + * + * @param filterSecurityInterceptorOncePerRequest if the {@link FilterSecurityInterceptor} should be only applied + * once per request + * @return the {@link AbstractInterceptUrlConfigurer} for further customization + */ + public R filterSecurityInterceptorOncePerRequest( + boolean filterSecurityInterceptorOncePerRequest) { + AbstractInterceptUrlConfigurer.this.filterSecurityInterceptorOncePerRequest = filterSecurityInterceptorOncePerRequest; + return getSelf(); + } + + /** + * Returns a reference to the current object with a single suppression of + * the type + * + * @return a reference to the current object + */ + @SuppressWarnings("unchecked") + private R getSelf() { + return (R) this; + } + } + /** * Creates the default {@code AccessDecisionManager} * @return the default {@code AccessDecisionManager} @@ -175,15 +187,4 @@ abstract class AbstractInterceptUrlConfigurer,C securityInterceptor.afterPropertiesSet(); return securityInterceptor; } - - /** - * Returns a reference to the current object with a single suppression of - * the type - * - * @return a reference to the current object - */ - @SuppressWarnings("unchecked") - private C getSelf() { - return (C) this; - } } \ No newline at end of file diff --git a/config/src/main/java/org/springframework/security/config/annotation/web/configurers/ChannelSecurityConfigurer.java b/config/src/main/java/org/springframework/security/config/annotation/web/configurers/ChannelSecurityConfigurer.java index 80bce2adbb..691bd02674 100644 --- a/config/src/main/java/org/springframework/security/config/annotation/web/configurers/ChannelSecurityConfigurer.java +++ b/config/src/main/java/org/springframework/security/config/annotation/web/configurers/ChannelSecurityConfigurer.java @@ -23,9 +23,10 @@ import java.util.List; import org.springframework.security.access.ConfigAttribute; import org.springframework.security.access.SecurityConfig; import org.springframework.security.config.annotation.ObjectPostProcessor; +import org.springframework.security.config.annotation.SecurityBuilder; +import org.springframework.security.config.annotation.SecurityConfigurer; import org.springframework.security.config.annotation.web.HttpSecurityBuilder; import org.springframework.security.config.annotation.web.builders.HttpSecurity; -import org.springframework.security.web.DefaultSecurityFilterChain; import org.springframework.security.web.PortMapper; import org.springframework.security.web.access.channel.ChannelDecisionManagerImpl; import org.springframework.security.web.access.channel.ChannelProcessingFilter; @@ -72,11 +73,13 @@ import org.springframework.security.web.util.matcher.RequestMatcher; * @since 3.2 */ public final class ChannelSecurityConfigurer> extends - AbstractRequestMatcherMappingConfigurer.RequiresChannelUrl,DefaultSecurityFilterChain> { + AbstractHttpConfigurer, H> { private ChannelProcessingFilter channelFilter = new ChannelProcessingFilter(); private LinkedHashMap> requestMap = new LinkedHashMap>(); private List channelProcessors; + private final ChannelRequestMatcherRegistry REGISTRY = new ChannelRequestMatcherRegistry(); + /** * Creates a new instance * @see HttpSecurity#requiresChannel() @@ -84,6 +87,10 @@ public final class ChannelSecurityConfigurer> e public ChannelSecurityConfigurer() { } + public ChannelRequestMatcherRegistry getRegistry() { + return REGISTRY; + } + @Override public void configure(H http) throws Exception { ChannelDecisionManagerImpl channelDecisionManager = new ChannelDecisionManagerImpl(); @@ -100,27 +107,6 @@ public final class ChannelSecurityConfigurer> e http.addFilter(channelFilter); } - /** - * Adds an {@link ObjectPostProcessor} for this class. - * - * @param objectPostProcessor - * @return the {@link ChannelSecurityConfigurer} for further customizations - */ - public ChannelSecurityConfigurer withObjectPostProcessor(ObjectPostProcessor objectPostProcessor) { - addObjectPostProcessor(objectPostProcessor); - return this; - } - - /** - * Sets the {@link ChannelProcessor} instances to use in {@link ChannelDecisionManagerImpl} - * @param channelProcessors - * @return - */ - public ChannelSecurityConfigurer channelProcessors(List channelProcessors) { - this.channelProcessors = channelProcessors; - return this; - } - private List getChannelProcessors(H http) { if(channelProcessors != null) { return channelProcessors; @@ -145,17 +131,53 @@ public final class ChannelSecurityConfigurer> e } - private ChannelSecurityConfigurer addAttribute(String attribute, List matchers) { + private ChannelRequestMatcherRegistry addAttribute(String attribute, List matchers) { for(RequestMatcher matcher : matchers) { Collection attrs = Arrays.asList(new SecurityConfig(attribute)); requestMap.put(matcher, attrs); } - return this; + return REGISTRY; } - @Override - protected RequiresChannelUrl chainRequestMatchersInternal(List requestMatchers) { - return new RequiresChannelUrl(requestMatchers); + public final class ChannelRequestMatcherRegistry extends AbstractConfigAttributeRequestMatcherRegistry { + + @Override + protected RequiresChannelUrl chainRequestMatchersInternal(List requestMatchers) { + return new RequiresChannelUrl(requestMatchers); + } + + /** + * Adds an {@link ObjectPostProcessor} for this class. + * + * @param objectPostProcessor + * @return the {@link ChannelSecurityConfigurer} for further customizations + */ + public ChannelRequestMatcherRegistry withObjectPostProcessor(ObjectPostProcessor objectPostProcessor) { + addObjectPostProcessor(objectPostProcessor); + return this; + } + + /** + * Sets the {@link ChannelProcessor} instances to use in {@link ChannelDecisionManagerImpl} + * @param channelProcessors + * @return + */ + public ChannelRequestMatcherRegistry channelProcessors(List channelProcessors) { + ChannelSecurityConfigurer.this.channelProcessors = channelProcessors; + return this; + } + + /** + * Return the {@link SecurityBuilder} when done using the + * {@link SecurityConfigurer}. This is useful for method chaining. + * + * @return + */ + public H and() { + return ChannelSecurityConfigurer.this.and(); + } + + private ChannelRequestMatcherRegistry() {} } public final class RequiresChannelUrl { @@ -165,15 +187,15 @@ public final class ChannelSecurityConfigurer> e this.requestMatchers = requestMatchers; } - public ChannelSecurityConfigurer requiresSecure() { + public ChannelRequestMatcherRegistry requiresSecure() { return requires("REQUIRES_SECURE_CHANNEL"); } - public ChannelSecurityConfigurer requiresInsecure() { + public ChannelRequestMatcherRegistry requiresInsecure() { return requires("REQUIRES_INSECURE_CHANNEL"); } - public ChannelSecurityConfigurer requires(String attribute) { + public ChannelRequestMatcherRegistry requires(String attribute) { return addAttribute(attribute, requestMatchers); } } diff --git a/config/src/main/java/org/springframework/security/config/annotation/web/configurers/ExpressionUrlAuthorizationConfigurer.java b/config/src/main/java/org/springframework/security/config/annotation/web/configurers/ExpressionUrlAuthorizationConfigurer.java index c435c2f34a..57f4779794 100644 --- a/config/src/main/java/org/springframework/security/config/annotation/web/configurers/ExpressionUrlAuthorizationConfigurer.java +++ b/config/src/main/java/org/springframework/security/config/annotation/web/configurers/ExpressionUrlAuthorizationConfigurer.java @@ -67,7 +67,7 @@ import org.springframework.util.StringUtils; * @since 3.2 * @see {@link org.springframework.security.config.annotation.web.builders.HttpSecurity#authorizeRequests()} */ -public final class ExpressionUrlAuthorizationConfigurer> extends AbstractInterceptUrlConfigurer,ExpressionUrlAuthorizationConfigurer.AuthorizedUrl> { +public final class ExpressionUrlAuthorizationConfigurer> extends AbstractInterceptUrlConfigurer,H> { static final String permitAll = "permitAll"; private static final String denyAll = "denyAll"; private static final String anonymous = "anonymous"; @@ -75,6 +75,8 @@ public final class ExpressionUrlAuthorizationConfigurer expressionHandler; /** @@ -84,31 +86,59 @@ public final class ExpressionUrlAuthorizationConfigurer expressionHandler(SecurityExpressionHandler expressionHandler) { - this.expressionHandler = expressionHandler; - return this; + public ExpressionInterceptUrlRegistry getRegistry() { + return REGISTRY; + } + + public class ExpressionInterceptUrlRegistry extends ExpressionUrlAuthorizationConfigurer.AbstractInterceptUrlRegistry { + + @Override + protected final AuthorizedUrl chainRequestMatchersInternal(List requestMatchers) { + return new AuthorizedUrl(requestMatchers); + } + + + /** + * Allows customization of the {@link SecurityExpressionHandler} to be used. The default is {@link DefaultWebSecurityExpressionHandler} + * + * @param expressionHandler the {@link SecurityExpressionHandler} to be used + * @return the {@link ExpressionUrlAuthorizationConfigurer} for further customization. + */ + public ExpressionInterceptUrlRegistry expressionHandler(SecurityExpressionHandler expressionHandler) { + ExpressionUrlAuthorizationConfigurer.this.expressionHandler = expressionHandler; + return this; + } + + /** + * Adds an {@link ObjectPostProcessor} for this class. + * + * @param objectPostProcessor + * @return the {@link ExpressionUrlAuthorizationConfigurer} for further customizations + */ + public ExpressionInterceptUrlRegistry withObjectPostProcessor(ObjectPostProcessor objectPostProcessor) { + addObjectPostProcessor(objectPostProcessor); + return this; + } + + public H and() { + return ExpressionUrlAuthorizationConfigurer.this.and(); + } + } + /** - * Adds an {@link ObjectPostProcessor} for this class. + * Allows registering multiple {@link RequestMatcher} instances to a collection of {@link ConfigAttribute} instances * - * @param objectPostProcessor - * @return the {@link ExpressionUrlAuthorizationConfigurer} for further customizations + * @param requestMatchers the {@link RequestMatcher} instances to register to the {@link ConfigAttribute} instances + * @param configAttributes the {@link ConfigAttribute} to be mapped by the {@link RequestMatcher} instances + * @return the {@link ExpressionUrlAuthorizationConfigurer} for further customization. */ - public ExpressionUrlAuthorizationConfigurer withObjectPostProcessor(ObjectPostProcessor objectPostProcessor) { - addObjectPostProcessor(objectPostProcessor); - return this; - } - - @Override - protected final AuthorizedUrl chainRequestMatchersInternal(List requestMatchers) { - return new AuthorizedUrl(requestMatchers); + private ExpressionUrlAuthorizationConfigurer interceptUrl(Iterable requestMatchers, Collection configAttributes) { + for(RequestMatcher requestMatcher : requestMatchers) { + REGISTRY.addMapping(new AbstractConfigAttributeRequestMatcherRegistry.UrlMapping(requestMatcher, configAttributes)); + } + return null; } @Override @@ -123,27 +153,13 @@ public final class ExpressionUrlAuthorizationConfigurer> requestMap = createRequestMap(); + LinkedHashMap> requestMap = REGISTRY.createRequestMap(); if(requestMap.isEmpty()) { throw new IllegalStateException("At least one mapping is required (i.e. authorizeRequests().anyRequest.authenticated())"); } return new ExpressionBasedFilterInvocationSecurityMetadataSource(requestMap, getExpressionHandler(http)); } - /** - * Allows registering multiple {@link RequestMatcher} instances to a collection of {@link ConfigAttribute} instances - * - * @param requestMatchers the {@link RequestMatcher} instances to register to the {@link ConfigAttribute} instances - * @param configAttributes the {@link ConfigAttribute} to be mapped by the {@link RequestMatcher} instances - * @return the {@link ExpressionUrlAuthorizationConfigurer} for further customization. - */ - private ExpressionUrlAuthorizationConfigurer interceptUrl(Iterable requestMatchers, Collection configAttributes) { - for(RequestMatcher requestMatcher : requestMatchers) { - addMapping(new UrlMapping(requestMatcher, configAttributes)); - } - return this; - } - private SecurityExpressionHandler getExpressionHandler(H http) { if(expressionHandler == null) { DefaultWebSecurityExpressionHandler defaultHandler = new DefaultWebSecurityExpressionHandler(); @@ -216,7 +232,7 @@ public final class ExpressionUrlAuthorizationConfigurer hasRole(String role) { + public ExpressionInterceptUrlRegistry hasRole(String role) { return access(ExpressionUrlAuthorizationConfigurer.hasRole(role)); } @@ -232,7 +248,7 @@ public final class ExpressionUrlAuthorizationConfigurer hasAnyRole(String... roles) { + public ExpressionInterceptUrlRegistry hasAnyRole(String... roles) { return access(ExpressionUrlAuthorizationConfigurer.hasAnyRole(roles)); } @@ -242,7 +258,7 @@ public final class ExpressionUrlAuthorizationConfigurer hasAuthority(String authority) { + public ExpressionInterceptUrlRegistry hasAuthority(String authority) { return access(ExpressionUrlAuthorizationConfigurer.hasAuthority(authority)); } @@ -253,7 +269,7 @@ public final class ExpressionUrlAuthorizationConfigurer hasAnyAuthority(String... authorities) { + public ExpressionInterceptUrlRegistry hasAnyAuthority(String... authorities) { return access(ExpressionUrlAuthorizationConfigurer.hasAnyAuthority(authorities)); } @@ -264,7 +280,7 @@ public final class ExpressionUrlAuthorizationConfigurer hasIpAddress(String ipaddressExpression) { + public ExpressionInterceptUrlRegistry hasIpAddress(String ipaddressExpression) { return access(ExpressionUrlAuthorizationConfigurer.hasIpAddress(ipaddressExpression)); } @@ -273,7 +289,7 @@ public final class ExpressionUrlAuthorizationConfigurer permitAll() { + public ExpressionInterceptUrlRegistry permitAll() { return access(permitAll); } @@ -282,7 +298,7 @@ public final class ExpressionUrlAuthorizationConfigurer anonymous() { + public ExpressionInterceptUrlRegistry anonymous() { return access(anonymous); } @@ -292,7 +308,7 @@ public final class ExpressionUrlAuthorizationConfigurer rememberMe() { + public ExpressionInterceptUrlRegistry rememberMe() { return access(rememberMe); } @@ -301,7 +317,7 @@ public final class ExpressionUrlAuthorizationConfigurer denyAll() { + public ExpressionInterceptUrlRegistry denyAll() { return access(denyAll); } @@ -310,7 +326,7 @@ public final class ExpressionUrlAuthorizationConfigurer authenticated() { + public ExpressionInterceptUrlRegistry authenticated() { return access(authenticated); } @@ -320,7 +336,7 @@ public final class ExpressionUrlAuthorizationConfigurer fullyAuthenticated() { + public ExpressionInterceptUrlRegistry fullyAuthenticated() { return access(fullyAuthenticated); } @@ -330,12 +346,12 @@ public final class ExpressionUrlAuthorizationConfigurer access(String attribute) { + public ExpressionInterceptUrlRegistry access(String attribute) { if(not) { attribute = "!" + attribute; } interceptUrl(requestMatchers, SecurityConfig.createList(attribute)); - return ExpressionUrlAuthorizationConfigurer.this; + return ExpressionUrlAuthorizationConfigurer.this.REGISTRY; } } } \ No newline at end of file diff --git a/config/src/main/java/org/springframework/security/config/annotation/web/configurers/PermitAllSupport.java b/config/src/main/java/org/springframework/security/config/annotation/web/configurers/PermitAllSupport.java index a01b5cdae7..4f95e43f1e 100644 --- a/config/src/main/java/org/springframework/security/config/annotation/web/configurers/PermitAllSupport.java +++ b/config/src/main/java/org/springframework/security/config/annotation/web/configurers/PermitAllSupport.java @@ -20,7 +20,7 @@ import javax.servlet.http.HttpServletRequest; import org.springframework.security.access.SecurityConfig; import org.springframework.security.config.annotation.web.HttpSecurityBuilder; -import org.springframework.security.config.annotation.web.configurers.AbstractRequestMatcherMappingConfigurer.UrlMapping; +import org.springframework.security.config.annotation.web.configurers.AbstractConfigAttributeRequestMatcherRegistry.UrlMapping; import org.springframework.security.web.util.matcher.RequestMatcher; @@ -49,7 +49,7 @@ final class PermitAllSupport { for(RequestMatcher matcher : requestMatchers) { if(matcher != null) { - configurer.addMapping(0, new UrlMapping(matcher, SecurityConfig.createList(ExpressionUrlAuthorizationConfigurer.permitAll))); + configurer.getRegistry().addMapping(0, new UrlMapping(matcher, SecurityConfig.createList(ExpressionUrlAuthorizationConfigurer.permitAll))); } } } diff --git a/config/src/main/java/org/springframework/security/config/annotation/web/configurers/UrlAuthorizationConfigurer.java b/config/src/main/java/org/springframework/security/config/annotation/web/configurers/UrlAuthorizationConfigurer.java index c91840b629..9155a9aa10 100644 --- a/config/src/main/java/org/springframework/security/config/annotation/web/configurers/UrlAuthorizationConfigurer.java +++ b/config/src/main/java/org/springframework/security/config/annotation/web/configurers/UrlAuthorizationConfigurer.java @@ -34,22 +34,44 @@ import org.springframework.util.Assert; /** - * Adds URL based authorization using {@link DefaultFilterInvocationSecurityMetadataSource}. At least one - * {@link org.springframework.web.bind.annotation.RequestMapping} needs to be mapped to {@link ConfigAttribute}'s for - * this {@link SecurityContextConfigurer} to have meaning. - *

    Security Filters

    + * Adds URL based authorization using + * {@link DefaultFilterInvocationSecurityMetadataSource}. At least one + * {@link org.springframework.web.bind.annotation.RequestMapping} needs to be + * mapped to {@link ConfigAttribute}'s for this + * {@link SecurityContextConfigurer} to have meaning.

    Security Filters

    + * + *

    + * Usage includes applying the {@link UrlAuthorizationConfigurer} and then + * modifying the StandardInterceptUrlRegistry. For example: + *

    + * + *
    + * protected void configure(HttpSecurity http) throws Exception {
    + *     http
    + *         .apply(new UrlAuthorizationConfigurer()).getRegistry()
    + *             .antMatchers("/users**","/sessions/**").hasRole("USER")
    + *             .antMatchers("/signup").hasRole("ANONYMOUS")
    + *             .anyRequest().hasRole("USER")
    + * }
    + * 
    * * The following Filters are populated * *
      - *
    • {@link org.springframework.security.web.access.intercept.FilterSecurityInterceptor}
    • + *
    • + * {@link org.springframework.security.web.access.intercept.FilterSecurityInterceptor} + *
    • *
    * *

    Shared Objects Created

    * - * The following shared objects are populated to allow other {@link org.springframework.security.config.annotation.SecurityConfigurer}'s to customize: + * The following shared objects are populated to allow other + * {@link org.springframework.security.config.annotation.SecurityConfigurer}'s + * to customize: *
      - *
    • {@link org.springframework.security.web.access.intercept.FilterSecurityInterceptor}
    • + *
    • + * {@link org.springframework.security.web.access.intercept.FilterSecurityInterceptor} + *
    • *
    * *

    Shared Objects Used

    @@ -57,17 +79,32 @@ import org.springframework.util.Assert; * The following shared objects are used: * *
      - *
    • {@link org.springframework.security.config.annotation.web.builders.HttpSecurity#getAuthenticationManager()}
    • + *
    • + * {@link org.springframework.security.config.annotation.web.builders.HttpSecurity#getAuthenticationManager()} + *
    • *
    * - * @param the type of {@link HttpSecurityBuilder} that is being configured - * @param the type of object that is being chained + * @param + * the type of {@link HttpSecurityBuilder} that is being configured + * @param + * the type of object that is being chained * * @author Rob Winch * @since 3.2 * @see ExpressionUrlAuthorizationConfigurer */ -public final class UrlAuthorizationConfigurer, C> extends AbstractInterceptUrlConfigurer.AuthorizedUrl> { +public final class UrlAuthorizationConfigurer> extends AbstractInterceptUrlConfigurer,H> { + private final StandardInterceptUrlRegistry REGISTRY = new StandardInterceptUrlRegistry(); + + /** + * The StandardInterceptUrlRegistry is what users will interact with after + * applying the {@link UrlAuthorizationConfigurer}. + * + * @return + */ + public StandardInterceptUrlRegistry getRegistry() { + return REGISTRY; + } /** * Adds an {@link ObjectPostProcessor} for this class. @@ -75,11 +112,35 @@ public final class UrlAuthorizationConfigurer, * @param objectPostProcessor * @return the {@link UrlAuthorizationConfigurer} for further customizations */ - public UrlAuthorizationConfigurer withObjectPostProcessor(ObjectPostProcessor objectPostProcessor) { + public UrlAuthorizationConfigurer withObjectPostProcessor(ObjectPostProcessor objectPostProcessor) { addObjectPostProcessor(objectPostProcessor); return this; } + public class StandardInterceptUrlRegistry extends ExpressionUrlAuthorizationConfigurer.AbstractInterceptUrlRegistry { + + @Override + protected final AuthorizedUrl chainRequestMatchersInternal(List requestMatchers) { + return new AuthorizedUrl(requestMatchers); + } + + /** + * Adds an {@link ObjectPostProcessor} for this class. + * + * @param objectPostProcessor + * @return the {@link ExpressionUrlAuthorizationConfigurer} for further customizations + */ + public StandardInterceptUrlRegistry withObjectPostProcessor(ObjectPostProcessor objectPostProcessor) { + addObjectPostProcessor(objectPostProcessor); + return this; + } + + public H and() { + return UrlAuthorizationConfigurer.this.and(); + } + + } + /** * Creates the default {@link AccessDecisionVoter} instances used if an * {@link AccessDecisionManager} was not specified using @@ -104,15 +165,7 @@ public final class UrlAuthorizationConfigurer, */ @Override FilterInvocationSecurityMetadataSource createMetadataSource(H http) { - return new DefaultFilterInvocationSecurityMetadataSource(createRequestMap()); - } - - /** - * Chains the {@link RequestMatcher} creation to the {@link AuthorizedUrl} class. - */ - @Override - protected AuthorizedUrl chainRequestMatchersInternal(List requestMatchers) { - return new AuthorizedUrl(requestMatchers); + return new DefaultFilterInvocationSecurityMetadataSource(REGISTRY.createRequestMap()); } /** @@ -121,11 +174,11 @@ public final class UrlAuthorizationConfigurer, * @param configAttributes the {@link ConfigAttribute} instances that should be mapped by the {@link RequestMatcher} instances * @return the {@link UrlAuthorizationConfigurer} for further customizations */ - private UrlAuthorizationConfigurer addMapping(Iterable requestMatchers, Collection configAttributes) { + private StandardInterceptUrlRegistry addMapping(Iterable requestMatchers, Collection configAttributes) { for(RequestMatcher requestMatcher : requestMatchers) { - addMapping(new UrlMapping(requestMatcher, configAttributes)); + REGISTRY.addMapping(new AbstractConfigAttributeRequestMatcherRegistry.UrlMapping(requestMatcher, configAttributes)); } - return this; + return REGISTRY; } /** @@ -198,7 +251,7 @@ public final class UrlAuthorizationConfigurer, * with ROLE_ * the {@link UrlAuthorizationConfigurer} for further customization */ - public UrlAuthorizationConfigurer hasRole(String role) { + public StandardInterceptUrlRegistry hasRole(String role) { return access(UrlAuthorizationConfigurer.hasRole(role)); } @@ -211,7 +264,7 @@ public final class UrlAuthorizationConfigurer, * it is automatically prepended already. * @return the {@link UrlAuthorizationConfigurer} for further customization */ - public UrlAuthorizationConfigurer hasAnyRole(String... roles) { + public StandardInterceptUrlRegistry hasAnyRole(String... roles) { return access(UrlAuthorizationConfigurer.hasAnyRole(roles)); } @@ -222,7 +275,7 @@ public final class UrlAuthorizationConfigurer, * the authority that should be required * @return the {@link UrlAuthorizationConfigurer} for further customization */ - public UrlAuthorizationConfigurer hasAuthority(String authority) { + public StandardInterceptUrlRegistry hasAuthority(String authority) { return access(authority); } @@ -231,7 +284,7 @@ public final class UrlAuthorizationConfigurer, * @param authorities the authorities that the user should have at least one of (i.e. ROLE_USER, ROLE_ADMIN, etc). * @return the {@link UrlAuthorizationConfigurer} for further customization */ - public UrlAuthorizationConfigurer hasAnyAuthority(String... authorities) { + public StandardInterceptUrlRegistry hasAnyAuthority(String... authorities) { return access(UrlAuthorizationConfigurer.hasAnyAuthority(authorities)); } @@ -239,7 +292,7 @@ public final class UrlAuthorizationConfigurer, * Specifies that an anonymous user is allowed access * @return the {@link UrlAuthorizationConfigurer} for further customization */ - public UrlAuthorizationConfigurer anonymous() { + public StandardInterceptUrlRegistry anonymous() { return hasRole("ROLE_ANONYMOUS"); } @@ -248,9 +301,9 @@ public final class UrlAuthorizationConfigurer, * @param attributes the {@link ConfigAttribute}'s that restrict access to a URL * @return the {@link UrlAuthorizationConfigurer} for further customization */ - public UrlAuthorizationConfigurer access(String... attributes) { + public StandardInterceptUrlRegistry access(String... attributes) { addMapping(requestMatchers, SecurityConfig.createList(attributes)); - return UrlAuthorizationConfigurer.this; + return UrlAuthorizationConfigurer.this.REGISTRY; } } } \ No newline at end of file diff --git a/config/src/test/groovy/org/springframework/security/config/annotation/web/RequestMatchersTests.groovy b/config/src/test/groovy/org/springframework/security/config/annotation/web/RequestMatchersTests.groovy index 2431a8c400..17f7af8c86 100644 --- a/config/src/test/groovy/org/springframework/security/config/annotation/web/RequestMatchersTests.groovy +++ b/config/src/test/groovy/org/springframework/security/config/annotation/web/RequestMatchersTests.groovy @@ -15,7 +15,7 @@ */ package org.springframework.security.config.annotation.web; -import static org.springframework.security.config.annotation.web.AbstractRequestMatcherConfigurer.RequestMatchers.* +import static org.springframework.security.config.annotation.web.AbstractRequestMatcherRegistry.RequestMatchers.* import org.springframework.http.HttpMethod; import org.springframework.security.web.util.matcher.AntPathRequestMatcher; diff --git a/config/src/test/groovy/org/springframework/security/config/annotation/web/builders/NamespaceHttpTests.groovy b/config/src/test/groovy/org/springframework/security/config/annotation/web/builders/NamespaceHttpTests.groovy index de858e8ea9..1669ee90fc 100644 --- a/config/src/test/groovy/org/springframework/security/config/annotation/web/builders/NamespaceHttpTests.groovy +++ b/config/src/test/groovy/org/springframework/security/config/annotation/web/builders/NamespaceHttpTests.groovy @@ -507,7 +507,7 @@ public class NamespaceHttpTests extends BaseSpringSpec { static class DisableUseExpressionsConfig extends BaseWebConfig { protected void configure(HttpSecurity http) throws Exception { http - .apply(new UrlAuthorizationConfigurer()) + .apply(new UrlAuthorizationConfigurer()).getRegistry() .antMatchers("/users**","/sessions/**").hasRole("USER") .antMatchers("/signup").hasRole("ANONYMOUS") .anyRequest().hasRole("USER") diff --git a/config/src/test/groovy/org/springframework/security/config/annotation/web/configurers/AbstractRequestMatcherMappingConfigurerTests.groovy b/config/src/test/groovy/org/springframework/security/config/annotation/web/configurers/AbstractConfigAttributeRequestMatcherRegistryTests.groovy similarity index 76% rename from config/src/test/groovy/org/springframework/security/config/annotation/web/configurers/AbstractRequestMatcherMappingConfigurerTests.groovy rename to config/src/test/groovy/org/springframework/security/config/annotation/web/configurers/AbstractConfigAttributeRequestMatcherRegistryTests.groovy index b841546a9e..d680a705df 100644 --- a/config/src/test/groovy/org/springframework/security/config/annotation/web/configurers/AbstractRequestMatcherMappingConfigurerTests.groovy +++ b/config/src/test/groovy/org/springframework/security/config/annotation/web/configurers/AbstractConfigAttributeRequestMatcherRegistryTests.groovy @@ -15,25 +15,20 @@ */ package org.springframework.security.config.annotation.web.configurers; -import java.util.List; +import org.springframework.http.HttpMethod +import org.springframework.security.access.AccessDecisionVoter +import org.springframework.security.config.annotation.web.AbstractRequestMatcherRegistry +import org.springframework.security.web.util.matcher.AntPathRequestMatcher +import org.springframework.security.web.util.matcher.RegexRequestMatcher +import org.springframework.security.web.util.matcher.RequestMatcher -import org.springframework.http.HttpMethod; -import org.springframework.security.access.AccessDecisionVoter; -import org.springframework.security.config.annotation.web.builders.HttpSecurity; -import org.springframework.security.config.annotation.web.configurers.AbstractRequestMatcherMappingConfigurer; -import org.springframework.security.web.DefaultSecurityFilterChain; -import org.springframework.security.web.access.intercept.FilterInvocationSecurityMetadataSource; -import org.springframework.security.web.util.matcher.AntPathRequestMatcher; -import org.springframework.security.web.util.matcher.RegexRequestMatcher; -import org.springframework.security.web.util.matcher.RequestMatcher; - -import spock.lang.Specification; +import spock.lang.Specification /** * @author Rob Winch * */ -class AbstractRequestMatcherMappingConfigurerTests extends Specification { +class AbstractConfigAttributeRequestMatcherRegistryTests extends Specification { ConcreteAbstractRequestMatcherMappingConfigurer registry = new ConcreteAbstractRequestMatcherMappingConfigurer() def "regexMatchers(GET,'/a.*') uses RegexRequestMatcher"() { @@ -64,7 +59,7 @@ class AbstractRequestMatcherMappingConfigurerTests extends Specification { matchers.collect {it.class } == [AntPathRequestMatcher] } - static class ConcreteAbstractRequestMatcherMappingConfigurer extends AbstractRequestMatcherMappingConfigurer,DefaultSecurityFilterChain> { + static class ConcreteAbstractRequestMatcherMappingConfigurer extends AbstractConfigAttributeRequestMatcherRegistry> { List decisionVoters() { return null; } diff --git a/config/src/test/groovy/org/springframework/security/config/annotation/web/configurers/ExpressionUrlAuthorizationConfigurerConfigs.java b/config/src/test/groovy/org/springframework/security/config/annotation/web/configurers/ExpressionUrlAuthorizationConfigurerConfigs.java new file mode 100644 index 0000000000..4e280207c4 --- /dev/null +++ b/config/src/test/groovy/org/springframework/security/config/annotation/web/configurers/ExpressionUrlAuthorizationConfigurerConfigs.java @@ -0,0 +1,63 @@ +/* + * Copyright 2002-2013 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.security.config.annotation.web.configurers; + + +import java.util.Arrays; + +import org.springframework.context.annotation.Configuration; +import org.springframework.security.access.AccessDecisionVoter; +import org.springframework.security.access.expression.SecurityExpressionHandler; +import org.springframework.security.access.vote.AffirmativeBased; +import org.springframework.security.config.annotation.web.builders.HttpSecurity; +import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; +import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; +import org.springframework.security.web.FilterInvocation; +import org.springframework.security.web.access.expression.DefaultWebSecurityExpressionHandler; +import org.springframework.security.web.access.expression.WebExpressionVoter; + +/** + * + * @author Rob Winch + * + */ +public class ExpressionUrlAuthorizationConfigurerConfigs { + + /** + * Ensure that All additional properties properly compile and chain properly + */ + @EnableWebSecurity + @Configuration + static class AllPropertiesWorkConfig extends WebSecurityConfigurerAdapter { + + @SuppressWarnings("rawtypes") + @Override + protected void configure(HttpSecurity http) throws Exception { + SecurityExpressionHandler handler = new DefaultWebSecurityExpressionHandler(); + WebExpressionVoter expressionVoter = new WebExpressionVoter(); + AffirmativeBased adm = new AffirmativeBased(Arrays.asList(expressionVoter)); + http + .authorizeRequests() + .expressionHandler(handler) + .accessDecisionManager(adm) + .filterSecurityInterceptorOncePerRequest(true) + .antMatchers("/a","/b").hasRole("ADMIN") + .anyRequest().permitAll() + .and() + .formLogin(); + } + } +} diff --git a/config/src/test/groovy/org/springframework/security/config/annotation/web/configurers/ExpressionUrlAuthorizationsTests.groovy b/config/src/test/groovy/org/springframework/security/config/annotation/web/configurers/ExpressionUrlAuthorizationsTests.groovy index 4ee9225951..2d63b85328 100644 --- a/config/src/test/groovy/org/springframework/security/config/annotation/web/configurers/ExpressionUrlAuthorizationsTests.groovy +++ b/config/src/test/groovy/org/springframework/security/config/annotation/web/configurers/ExpressionUrlAuthorizationsTests.groovy @@ -15,6 +15,8 @@ */ package org.springframework.security.config.annotation.web.configurers; +import static org.springframework.security.config.annotation.web.configurers.ExpressionUrlAuthorizationConfigurerConfigs.* + import javax.servlet.http.HttpServletResponse import org.springframework.beans.factory.BeanCreationException @@ -453,4 +455,11 @@ public class ExpressionUrlAuthorizationConfigurerTests extends BaseSpringSpec { .inMemoryAuthentication() } } + + def "All Properties are accessible and chain properly"() { + when: + loadConfig(AllPropertiesWorkConfig) + then: + noExceptionThrown() + } } diff --git a/config/src/test/groovy/org/springframework/security/config/annotation/web/configurers/UrlAuthorizationsTests.groovy b/config/src/test/groovy/org/springframework/security/config/annotation/web/configurers/UrlAuthorizationsTests.groovy index ebbc0fa836..643a558fec 100644 --- a/config/src/test/groovy/org/springframework/security/config/annotation/web/configurers/UrlAuthorizationsTests.groovy +++ b/config/src/test/groovy/org/springframework/security/config/annotation/web/configurers/UrlAuthorizationsTests.groovy @@ -72,7 +72,8 @@ public class UrlAuthorizationsTests extends BaseSpringSpec { static class NoSpecificAccessDecessionManagerConfig extends WebSecurityConfigurerAdapter { protected void configure(HttpSecurity http) throws Exception { http - .apply(new UrlAuthorizationConfigurer()) + .apply(new UrlAuthorizationConfigurer()).getRegistry() + .antMatchers("/a").hasRole("ADMIN") .anyRequest().hasRole("USER") } }