14 changed files with 4 additions and 3886 deletions
@ -1,196 +0,0 @@ |
|||||||
/* |
|
||||||
* Copyright 2002-2025 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 |
|
||||||
* |
|
||||||
* https://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.List; |
|
||||||
|
|
||||||
import org.springframework.security.access.AccessDecisionManager; |
|
||||||
import org.springframework.security.access.AccessDecisionVoter; |
|
||||||
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.access.intercept.FilterInvocationSecurityMetadataSource; |
|
||||||
import org.springframework.security.web.access.intercept.FilterSecurityInterceptor; |
|
||||||
|
|
||||||
/** |
|
||||||
* A base class for configuring the {@link FilterSecurityInterceptor}. |
|
||||||
* |
|
||||||
* <h2>Security Filters</h2> |
|
||||||
* |
|
||||||
* The following Filters are populated |
|
||||||
* |
|
||||||
* <ul> |
|
||||||
* <li>{@link FilterSecurityInterceptor}</li> |
|
||||||
* </ul> |
|
||||||
* |
|
||||||
* <h2>Shared Objects Created</h2> |
|
||||||
* |
|
||||||
* The following shared objects are populated to allow other {@link SecurityConfigurer}'s |
|
||||||
* to customize: |
|
||||||
* <ul> |
|
||||||
* <li>{@link FilterSecurityInterceptor}</li> |
|
||||||
* </ul> |
|
||||||
* |
|
||||||
* <h2>Shared Objects Used</h2> |
|
||||||
* |
|
||||||
* The following shared objects are used: |
|
||||||
* |
|
||||||
* <ul> |
|
||||||
* <li>{@link AuthenticationManager}</li> |
|
||||||
* </ul> |
|
||||||
* |
|
||||||
* @param <C> the AbstractInterceptUrlConfigurer |
|
||||||
* @param <H> the type of {@link HttpSecurityBuilder} that is being configured |
|
||||||
* @author Rob Winch |
|
||||||
* @since 3.2 |
|
||||||
* @see ExpressionUrlAuthorizationConfigurer |
|
||||||
* @see UrlAuthorizationConfigurer |
|
||||||
* @deprecated Use {@link AuthorizeHttpRequestsConfigurer} instead |
|
||||||
*/ |
|
||||||
@Deprecated |
|
||||||
public abstract class AbstractInterceptUrlConfigurer<C extends AbstractInterceptUrlConfigurer<C, H>, H extends HttpSecurityBuilder<H>> |
|
||||||
extends AbstractHttpConfigurer<C, H> { |
|
||||||
|
|
||||||
private Boolean filterSecurityInterceptorOncePerRequest; |
|
||||||
|
|
||||||
private AccessDecisionManager accessDecisionManager; |
|
||||||
|
|
||||||
AbstractInterceptUrlConfigurer() { |
|
||||||
} |
|
||||||
|
|
||||||
@Override |
|
||||||
public void configure(H http) throws Exception { |
|
||||||
FilterInvocationSecurityMetadataSource metadataSource = createMetadataSource(http); |
|
||||||
if (metadataSource == null) { |
|
||||||
return; |
|
||||||
} |
|
||||||
FilterSecurityInterceptor securityInterceptor = createFilterSecurityInterceptor(http, metadataSource, |
|
||||||
http.getSharedObject(AuthenticationManager.class)); |
|
||||||
if (this.filterSecurityInterceptorOncePerRequest != null) { |
|
||||||
securityInterceptor.setObserveOncePerRequest(this.filterSecurityInterceptorOncePerRequest); |
|
||||||
} |
|
||||||
securityInterceptor = postProcess(securityInterceptor); |
|
||||||
http.addFilter(securityInterceptor); |
|
||||||
http.setSharedObject(FilterSecurityInterceptor.class, securityInterceptor); |
|
||||||
} |
|
||||||
|
|
||||||
/** |
|
||||||
* Subclasses should implement this method to provide a |
|
||||||
* {@link FilterInvocationSecurityMetadataSource} for the |
|
||||||
* {@link FilterSecurityInterceptor}. |
|
||||||
* @param http the builder to use |
|
||||||
* @return the {@link FilterInvocationSecurityMetadataSource} to set on the |
|
||||||
* {@link FilterSecurityInterceptor}. Cannot be null. |
|
||||||
*/ |
|
||||||
abstract FilterInvocationSecurityMetadataSource createMetadataSource(H http); |
|
||||||
|
|
||||||
/** |
|
||||||
* Subclasses should implement this method to provide the {@link AccessDecisionVoter} |
|
||||||
* instances used to create the default {@link AccessDecisionManager} |
|
||||||
* @param http the builder to use |
|
||||||
* @return the {@link AccessDecisionVoter} instances used to create the default |
|
||||||
* {@link AccessDecisionManager} |
|
||||||
*/ |
|
||||||
abstract List<AccessDecisionVoter<?>> getDecisionVoters(H http); |
|
||||||
|
|
||||||
/** |
|
||||||
* Creates the default {@code AccessDecisionManager} |
|
||||||
* @return the default {@code AccessDecisionManager} |
|
||||||
*/ |
|
||||||
private AccessDecisionManager createDefaultAccessDecisionManager(H http) { |
|
||||||
AffirmativeBased result = new AffirmativeBased(getDecisionVoters(http)); |
|
||||||
return postProcess(result); |
|
||||||
} |
|
||||||
|
|
||||||
/** |
|
||||||
* If currently null, creates a default {@link AccessDecisionManager} using |
|
||||||
* {@link #createDefaultAccessDecisionManager(HttpSecurityBuilder)}. Otherwise returns |
|
||||||
* the {@link AccessDecisionManager}. |
|
||||||
* @param http the builder to use |
|
||||||
* @return the {@link AccessDecisionManager} to use |
|
||||||
*/ |
|
||||||
private AccessDecisionManager getAccessDecisionManager(H http) { |
|
||||||
if (this.accessDecisionManager == null) { |
|
||||||
this.accessDecisionManager = createDefaultAccessDecisionManager(http); |
|
||||||
} |
|
||||||
return this.accessDecisionManager; |
|
||||||
} |
|
||||||
|
|
||||||
/** |
|
||||||
* Creates the {@link FilterSecurityInterceptor} |
|
||||||
* @param http the builder to use |
|
||||||
* @param metadataSource the {@link FilterInvocationSecurityMetadataSource} to use |
|
||||||
* @param authenticationManager the {@link AuthenticationManager} to use |
|
||||||
* @return the {@link FilterSecurityInterceptor} |
|
||||||
* @throws Exception |
|
||||||
*/ |
|
||||||
private FilterSecurityInterceptor createFilterSecurityInterceptor(H http, |
|
||||||
FilterInvocationSecurityMetadataSource metadataSource, AuthenticationManager authenticationManager) |
|
||||||
throws Exception { |
|
||||||
FilterSecurityInterceptor securityInterceptor = new FilterSecurityInterceptor(); |
|
||||||
securityInterceptor.setSecurityMetadataSource(metadataSource); |
|
||||||
securityInterceptor.setAccessDecisionManager(getAccessDecisionManager(http)); |
|
||||||
securityInterceptor.setAuthenticationManager(authenticationManager); |
|
||||||
securityInterceptor.setSecurityContextHolderStrategy(getSecurityContextHolderStrategy()); |
|
||||||
securityInterceptor.afterPropertiesSet(); |
|
||||||
return securityInterceptor; |
|
||||||
} |
|
||||||
|
|
||||||
@Deprecated |
|
||||||
public abstract class AbstractInterceptUrlRegistry<R extends AbstractInterceptUrlRegistry<R, T>, T> |
|
||||||
extends AbstractConfigAttributeRequestMatcherRegistry<T> { |
|
||||||
|
|
||||||
AbstractInterceptUrlRegistry() { |
|
||||||
} |
|
||||||
|
|
||||||
/** |
|
||||||
* Allows setting the {@link AccessDecisionManager}. If none is provided, a |
|
||||||
* default {@link 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; |
|
||||||
} |
|
||||||
|
|
||||||
} |
|
||||||
|
|
||||||
} |
|
||||||
@ -1,412 +0,0 @@ |
|||||||
/* |
|
||||||
* Copyright 2002-2025 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 |
|
||||||
* |
|
||||||
* https://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.ArrayList; |
|
||||||
import java.util.Collection; |
|
||||||
import java.util.LinkedHashMap; |
|
||||||
import java.util.List; |
|
||||||
|
|
||||||
import org.springframework.context.ApplicationContext; |
|
||||||
import org.springframework.security.access.AccessDecisionVoter; |
|
||||||
import org.springframework.security.access.ConfigAttribute; |
|
||||||
import org.springframework.security.access.PermissionEvaluator; |
|
||||||
import org.springframework.security.access.SecurityConfig; |
|
||||||
import org.springframework.security.access.expression.SecurityExpressionHandler; |
|
||||||
import org.springframework.security.access.hierarchicalroles.RoleHierarchy; |
|
||||||
import org.springframework.security.authentication.AuthenticationTrustResolver; |
|
||||||
import org.springframework.security.config.Customizer; |
|
||||||
import org.springframework.security.config.ObjectPostProcessor; |
|
||||||
import org.springframework.security.config.annotation.web.HttpSecurityBuilder; |
|
||||||
import org.springframework.security.config.annotation.web.builders.HttpSecurity; |
|
||||||
import org.springframework.security.config.core.GrantedAuthorityDefaults; |
|
||||||
import org.springframework.security.web.FilterInvocation; |
|
||||||
import org.springframework.security.web.access.expression.DefaultWebSecurityExpressionHandler; |
|
||||||
import org.springframework.security.web.access.expression.ExpressionBasedFilterInvocationSecurityMetadataSource; |
|
||||||
import org.springframework.security.web.access.expression.WebExpressionVoter; |
|
||||||
import org.springframework.security.web.util.matcher.RequestMatcher; |
|
||||||
import org.springframework.util.Assert; |
|
||||||
import org.springframework.util.StringUtils; |
|
||||||
|
|
||||||
/** |
|
||||||
* Adds URL based authorization based upon SpEL expressions to an application. 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. |
|
||||||
* <h2>Security Filters</h2> |
|
||||||
* |
|
||||||
* The following Filters are populated |
|
||||||
* |
|
||||||
* <ul> |
|
||||||
* <li>{@link org.springframework.security.web.access.intercept.FilterSecurityInterceptor} |
|
||||||
* </li> |
|
||||||
* </ul> |
|
||||||
* |
|
||||||
* <h2>Shared Objects Created</h2> |
|
||||||
* |
|
||||||
* The following shared objects are populated to allow other |
|
||||||
* {@link org.springframework.security.config.annotation.SecurityConfigurer}'s to |
|
||||||
* customize: |
|
||||||
* <ul> |
|
||||||
* <li>{@link org.springframework.security.web.access.intercept.FilterSecurityInterceptor} |
|
||||||
* </li> |
|
||||||
* </ul> |
|
||||||
* |
|
||||||
* <h2>Shared Objects Used</h2> |
|
||||||
* |
|
||||||
* <ul> |
|
||||||
* <li>{@link AuthenticationTrustResolver} is optionally used to populate the |
|
||||||
* {@link DefaultWebSecurityExpressionHandler}</li> |
|
||||||
* </ul> |
|
||||||
* |
|
||||||
* @param <H> the type of {@link HttpSecurityBuilder} that is being configured |
|
||||||
* @author Rob Winch |
|
||||||
* @author Yanming Zhou |
|
||||||
* @author Ngoc Nhan |
|
||||||
* @since 3.2 |
|
||||||
* @see org.springframework.security.config.annotation.web.builders.HttpSecurity#authorizeRequests(Customizer) |
|
||||||
* @deprecated Use {@link AuthorizeHttpRequestsConfigurer} instead |
|
||||||
*/ |
|
||||||
@Deprecated |
|
||||||
public final class ExpressionUrlAuthorizationConfigurer<H extends HttpSecurityBuilder<H>> |
|
||||||
extends AbstractInterceptUrlConfigurer<ExpressionUrlAuthorizationConfigurer<H>, H> { |
|
||||||
|
|
||||||
static final String permitAll = "permitAll"; |
|
||||||
|
|
||||||
private static final String denyAll = "denyAll"; |
|
||||||
|
|
||||||
private static final String anonymous = "anonymous"; |
|
||||||
|
|
||||||
private static final String authenticated = "authenticated"; |
|
||||||
|
|
||||||
private static final String fullyAuthenticated = "fullyAuthenticated"; |
|
||||||
|
|
||||||
private static final String rememberMe = "rememberMe"; |
|
||||||
|
|
||||||
private final String rolePrefix; |
|
||||||
|
|
||||||
private final ExpressionInterceptUrlRegistry REGISTRY; |
|
||||||
|
|
||||||
private SecurityExpressionHandler<FilterInvocation> expressionHandler; |
|
||||||
|
|
||||||
/** |
|
||||||
* Creates a new instance |
|
||||||
* @see HttpSecurity#authorizeRequests(Customizer) |
|
||||||
*/ |
|
||||||
public ExpressionUrlAuthorizationConfigurer(ApplicationContext context) { |
|
||||||
GrantedAuthorityDefaults grantedAuthorityDefaults = context.getBeanProvider(GrantedAuthorityDefaults.class) |
|
||||||
.getIfUnique(); |
|
||||||
if (grantedAuthorityDefaults != null) { |
|
||||||
this.rolePrefix = grantedAuthorityDefaults.getRolePrefix(); |
|
||||||
} |
|
||||||
else { |
|
||||||
this.rolePrefix = "ROLE_"; |
|
||||||
} |
|
||||||
this.REGISTRY = new ExpressionInterceptUrlRegistry(context); |
|
||||||
} |
|
||||||
|
|
||||||
public ExpressionInterceptUrlRegistry getRegistry() { |
|
||||||
return this.REGISTRY; |
|
||||||
} |
|
||||||
|
|
||||||
/** |
|
||||||
* 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 |
|
||||||
*/ |
|
||||||
private void interceptUrl(Iterable<? extends RequestMatcher> requestMatchers, |
|
||||||
Collection<ConfigAttribute> configAttributes) { |
|
||||||
for (RequestMatcher requestMatcher : requestMatchers) { |
|
||||||
this.REGISTRY.addMapping( |
|
||||||
new AbstractConfigAttributeRequestMatcherRegistry.UrlMapping(requestMatcher, configAttributes)); |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
@Override |
|
||||||
@SuppressWarnings("rawtypes") |
|
||||||
List<AccessDecisionVoter<?>> getDecisionVoters(H http) { |
|
||||||
List<AccessDecisionVoter<?>> decisionVoters = new ArrayList<>(); |
|
||||||
WebExpressionVoter expressionVoter = new WebExpressionVoter(); |
|
||||||
expressionVoter.setExpressionHandler(getExpressionHandler(http)); |
|
||||||
decisionVoters.add(expressionVoter); |
|
||||||
return decisionVoters; |
|
||||||
} |
|
||||||
|
|
||||||
@Override |
|
||||||
ExpressionBasedFilterInvocationSecurityMetadataSource createMetadataSource(H http) { |
|
||||||
LinkedHashMap<RequestMatcher, Collection<ConfigAttribute>> requestMap = this.REGISTRY.createRequestMap(); |
|
||||||
Assert.state(!requestMap.isEmpty(), |
|
||||||
"At least one mapping is required (i.e. authorizeRequests().anyRequest().authenticated())"); |
|
||||||
return new ExpressionBasedFilterInvocationSecurityMetadataSource(requestMap, getExpressionHandler(http)); |
|
||||||
} |
|
||||||
|
|
||||||
private SecurityExpressionHandler<FilterInvocation> getExpressionHandler(H http) { |
|
||||||
if (this.expressionHandler != null) { |
|
||||||
return this.expressionHandler; |
|
||||||
} |
|
||||||
DefaultWebSecurityExpressionHandler defaultHandler = new DefaultWebSecurityExpressionHandler(); |
|
||||||
AuthenticationTrustResolver trustResolver = http.getSharedObject(AuthenticationTrustResolver.class); |
|
||||||
if (trustResolver != null) { |
|
||||||
defaultHandler.setTrustResolver(trustResolver); |
|
||||||
} |
|
||||||
ApplicationContext context = http.getSharedObject(ApplicationContext.class); |
|
||||||
if (context != null) { |
|
||||||
context.getBeanProvider(RoleHierarchy.class).ifUnique(defaultHandler::setRoleHierarchy); |
|
||||||
context.getBeanProvider(GrantedAuthorityDefaults.class) |
|
||||||
.ifUnique((grantedAuthorityDefaults) -> defaultHandler |
|
||||||
.setDefaultRolePrefix(grantedAuthorityDefaults.getRolePrefix())); |
|
||||||
context.getBeanProvider(PermissionEvaluator.class).ifUnique(defaultHandler::setPermissionEvaluator); |
|
||||||
} |
|
||||||
this.expressionHandler = postProcess(defaultHandler); |
|
||||||
return this.expressionHandler; |
|
||||||
} |
|
||||||
|
|
||||||
private static String hasAnyRole(String rolePrefix, String... authorities) { |
|
||||||
String anyAuthorities = StringUtils.arrayToDelimitedString(authorities, "','" + rolePrefix); |
|
||||||
return "hasAnyRole('" + rolePrefix + anyAuthorities + "')"; |
|
||||||
} |
|
||||||
|
|
||||||
private static String hasRole(String rolePrefix, String role) { |
|
||||||
Assert.notNull(role, "role cannot be null"); |
|
||||||
Assert.isTrue(rolePrefix.isEmpty() || !role.startsWith(rolePrefix), () -> "role should not start with '" |
|
||||||
+ rolePrefix + "' since it is automatically inserted. Got '" + role + "'"); |
|
||||||
return "hasRole('" + rolePrefix + role + "')"; |
|
||||||
} |
|
||||||
|
|
||||||
private static String hasAuthority(String authority) { |
|
||||||
return "hasAuthority('" + authority + "')"; |
|
||||||
} |
|
||||||
|
|
||||||
private static String hasAnyAuthority(String... authorities) { |
|
||||||
String anyAuthorities = StringUtils.arrayToDelimitedString(authorities, "','"); |
|
||||||
return "hasAnyAuthority('" + anyAuthorities + "')"; |
|
||||||
} |
|
||||||
|
|
||||||
private static String hasIpAddress(String ipAddressExpression) { |
|
||||||
return "hasIpAddress('" + ipAddressExpression + "')"; |
|
||||||
} |
|
||||||
|
|
||||||
@Deprecated |
|
||||||
public final class ExpressionInterceptUrlRegistry extends |
|
||||||
ExpressionUrlAuthorizationConfigurer<H>.AbstractInterceptUrlRegistry<ExpressionInterceptUrlRegistry, AuthorizedUrl> { |
|
||||||
|
|
||||||
private ExpressionInterceptUrlRegistry(ApplicationContext context) { |
|
||||||
setApplicationContext(context); |
|
||||||
} |
|
||||||
|
|
||||||
@Override |
|
||||||
protected AuthorizedUrl chainRequestMatchersInternal(List<RequestMatcher> 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<FilterInvocation> 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.getBuilder(); |
|
||||||
} |
|
||||||
|
|
||||||
} |
|
||||||
|
|
||||||
public class AuthorizedUrl { |
|
||||||
|
|
||||||
private List<? extends RequestMatcher> requestMatchers; |
|
||||||
|
|
||||||
private boolean not; |
|
||||||
|
|
||||||
/** |
|
||||||
* Creates a new instance |
|
||||||
* @param requestMatchers the {@link RequestMatcher} instances to map |
|
||||||
*/ |
|
||||||
AuthorizedUrl(List<? extends RequestMatcher> requestMatchers) { |
|
||||||
this.requestMatchers = requestMatchers; |
|
||||||
} |
|
||||||
|
|
||||||
protected List<? extends RequestMatcher> getMatchers() { |
|
||||||
return this.requestMatchers; |
|
||||||
} |
|
||||||
|
|
||||||
/** |
|
||||||
* Negates the following expression. |
|
||||||
* @return the {@link ExpressionUrlAuthorizationConfigurer} for further |
|
||||||
* customization |
|
||||||
*/ |
|
||||||
public AuthorizedUrl not() { |
|
||||||
this.not = true; |
|
||||||
return this; |
|
||||||
} |
|
||||||
|
|
||||||
/** |
|
||||||
* Shortcut for specifying URLs require a particular role. If you do not want to |
|
||||||
* have role prefix (default "ROLE_") automatically inserted see |
|
||||||
* {@link #hasAuthority(String)}. |
|
||||||
* @param role the role to require (i.e. USER, ADMIN, etc). Note, it should not |
|
||||||
* start with role prefix as this is automatically inserted. |
|
||||||
* @return the {@link ExpressionUrlAuthorizationConfigurer} for further |
|
||||||
* customization |
|
||||||
*/ |
|
||||||
public ExpressionInterceptUrlRegistry hasRole(String role) { |
|
||||||
return access(ExpressionUrlAuthorizationConfigurer |
|
||||||
.hasRole(ExpressionUrlAuthorizationConfigurer.this.rolePrefix, role)); |
|
||||||
} |
|
||||||
|
|
||||||
/** |
|
||||||
* Shortcut for specifying URLs require any of a number of roles. If you do not |
|
||||||
* want to have role prefix (default "ROLE_") automatically inserted see |
|
||||||
* {@link #hasAnyAuthority(String...)} |
|
||||||
* @param roles the roles to require (i.e. USER, ADMIN, etc). Note, it should not |
|
||||||
* start with role prefix as this is automatically inserted. |
|
||||||
* @return the {@link ExpressionUrlAuthorizationConfigurer} for further |
|
||||||
* customization |
|
||||||
*/ |
|
||||||
public ExpressionInterceptUrlRegistry hasAnyRole(String... roles) { |
|
||||||
return access(ExpressionUrlAuthorizationConfigurer |
|
||||||
.hasAnyRole(ExpressionUrlAuthorizationConfigurer.this.rolePrefix, roles)); |
|
||||||
} |
|
||||||
|
|
||||||
/** |
|
||||||
* Specify that URLs require a particular authority. |
|
||||||
* @param authority the authority to require (i.e. ROLE_USER, ROLE_ADMIN, etc). |
|
||||||
* @return the {@link ExpressionUrlAuthorizationConfigurer} for further |
|
||||||
* customization |
|
||||||
*/ |
|
||||||
public ExpressionInterceptUrlRegistry hasAuthority(String authority) { |
|
||||||
return access(ExpressionUrlAuthorizationConfigurer.hasAuthority(authority)); |
|
||||||
} |
|
||||||
|
|
||||||
/** |
|
||||||
* Specify that URLs requires any of a number authorities. |
|
||||||
* @param authorities the requests require at least one of the authorities (i.e. |
|
||||||
* "ROLE_USER","ROLE_ADMIN" would mean either "ROLE_USER" or "ROLE_ADMIN" is |
|
||||||
* required). |
|
||||||
* @return the {@link ExpressionUrlAuthorizationConfigurer} for further |
|
||||||
* customization |
|
||||||
*/ |
|
||||||
public ExpressionInterceptUrlRegistry hasAnyAuthority(String... authorities) { |
|
||||||
return access(ExpressionUrlAuthorizationConfigurer.hasAnyAuthority(authorities)); |
|
||||||
} |
|
||||||
|
|
||||||
/** |
|
||||||
* Specify that URLs requires a specific IP Address or subnet. |
|
||||||
* @param ipaddressExpression the ipaddress (i.e. 192.168.1.79) or local subnet |
|
||||||
* (i.e. 192.168.0/24) |
|
||||||
* @return the {@link ExpressionUrlAuthorizationConfigurer} for further |
|
||||||
* customization |
|
||||||
*/ |
|
||||||
public ExpressionInterceptUrlRegistry hasIpAddress(String ipaddressExpression) { |
|
||||||
return access(ExpressionUrlAuthorizationConfigurer.hasIpAddress(ipaddressExpression)); |
|
||||||
} |
|
||||||
|
|
||||||
/** |
|
||||||
* Specify that URLs are allowed by anyone. |
|
||||||
* @return the {@link ExpressionUrlAuthorizationConfigurer} for further |
|
||||||
* customization |
|
||||||
*/ |
|
||||||
public ExpressionInterceptUrlRegistry permitAll() { |
|
||||||
return access(permitAll); |
|
||||||
} |
|
||||||
|
|
||||||
/** |
|
||||||
* Specify that URLs are allowed by anonymous users. |
|
||||||
* @return the {@link ExpressionUrlAuthorizationConfigurer} for further |
|
||||||
* customization |
|
||||||
*/ |
|
||||||
public ExpressionInterceptUrlRegistry anonymous() { |
|
||||||
return access(anonymous); |
|
||||||
} |
|
||||||
|
|
||||||
/** |
|
||||||
* Specify that URLs are allowed by users that have been remembered. |
|
||||||
* @return the {@link ExpressionUrlAuthorizationConfigurer} for further |
|
||||||
* customization |
|
||||||
* @see RememberMeConfigurer |
|
||||||
*/ |
|
||||||
public ExpressionInterceptUrlRegistry rememberMe() { |
|
||||||
return access(rememberMe); |
|
||||||
} |
|
||||||
|
|
||||||
/** |
|
||||||
* Specify that URLs are not allowed by anyone. |
|
||||||
* @return the {@link ExpressionUrlAuthorizationConfigurer} for further |
|
||||||
* customization |
|
||||||
*/ |
|
||||||
public ExpressionInterceptUrlRegistry denyAll() { |
|
||||||
return access(denyAll); |
|
||||||
} |
|
||||||
|
|
||||||
/** |
|
||||||
* Specify that URLs are allowed by any authenticated user. |
|
||||||
* @return the {@link ExpressionUrlAuthorizationConfigurer} for further |
|
||||||
* customization |
|
||||||
*/ |
|
||||||
public ExpressionInterceptUrlRegistry authenticated() { |
|
||||||
return access(authenticated); |
|
||||||
} |
|
||||||
|
|
||||||
/** |
|
||||||
* Specify that URLs are allowed by users who have authenticated and were not |
|
||||||
* "remembered". |
|
||||||
* @return the {@link ExpressionUrlAuthorizationConfigurer} for further |
|
||||||
* customization |
|
||||||
* @see RememberMeConfigurer |
|
||||||
*/ |
|
||||||
public ExpressionInterceptUrlRegistry fullyAuthenticated() { |
|
||||||
return access(fullyAuthenticated); |
|
||||||
} |
|
||||||
|
|
||||||
/** |
|
||||||
* Allows specifying that URLs are secured by an arbitrary expression |
|
||||||
* @param attribute the expression to secure the URLs (i.e. "hasRole('ROLE_USER') |
|
||||||
* and hasRole('ROLE_SUPER')") |
|
||||||
* @return the {@link ExpressionUrlAuthorizationConfigurer} for further |
|
||||||
* customization |
|
||||||
*/ |
|
||||||
public ExpressionInterceptUrlRegistry access(String attribute) { |
|
||||||
if (this.not) { |
|
||||||
attribute = "!" + attribute; |
|
||||||
} |
|
||||||
interceptUrl(this.requestMatchers, SecurityConfig.createList(attribute)); |
|
||||||
return ExpressionUrlAuthorizationConfigurer.this.REGISTRY; |
|
||||||
} |
|
||||||
|
|
||||||
} |
|
||||||
|
|
||||||
} |
|
||||||
@ -1,333 +0,0 @@ |
|||||||
/* |
|
||||||
* Copyright 2002-2025 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 |
|
||||||
* |
|
||||||
* https://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.ArrayList; |
|
||||||
import java.util.Collection; |
|
||||||
import java.util.List; |
|
||||||
|
|
||||||
import org.springframework.context.ApplicationContext; |
|
||||||
import org.springframework.http.HttpMethod; |
|
||||||
import org.springframework.security.access.AccessDecisionManager; |
|
||||||
import org.springframework.security.access.AccessDecisionVoter; |
|
||||||
import org.springframework.security.access.ConfigAttribute; |
|
||||||
import org.springframework.security.access.SecurityConfig; |
|
||||||
import org.springframework.security.access.vote.AuthenticatedVoter; |
|
||||||
import org.springframework.security.access.vote.RoleVoter; |
|
||||||
import org.springframework.security.config.ObjectPostProcessor; |
|
||||||
import org.springframework.security.config.annotation.web.HttpSecurityBuilder; |
|
||||||
import org.springframework.security.web.access.intercept.DefaultFilterInvocationSecurityMetadataSource; |
|
||||||
import org.springframework.security.web.access.intercept.FilterInvocationSecurityMetadataSource; |
|
||||||
import org.springframework.security.web.util.matcher.RequestMatcher; |
|
||||||
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. |
|
||||||
* <h2>Security Filters</h2> |
|
||||||
* |
|
||||||
* <p> |
|
||||||
* Usage includes applying the {@link UrlAuthorizationConfigurer} and then modifying the |
|
||||||
* StandardInterceptUrlRegistry. For example: |
|
||||||
* </p> |
|
||||||
* |
|
||||||
* <pre> |
|
||||||
* @Bean |
|
||||||
* public SecurityFilterChain filterChain(HttpSecurity http, ApplicationContext context) throws Exception { |
|
||||||
* http.apply(new UrlAuthorizationConfigurer<HttpSecurity>(context)).getRegistry() |
|
||||||
* .requestMatchers("/users**", "/sessions/**").hasRole("USER") |
|
||||||
* .requestMatchers("/signup").hasRole("ANONYMOUS").anyRequest().hasRole("USER"); |
|
||||||
* } |
|
||||||
* </pre> |
|
||||||
* |
|
||||||
* The following Filters are populated |
|
||||||
* |
|
||||||
* <ul> |
|
||||||
* <li> |
|
||||||
* {@link org.springframework.security.web.access.intercept.FilterSecurityInterceptor}</li> |
|
||||||
* </ul> |
|
||||||
* |
|
||||||
* <h2>Shared Objects Created</h2> |
|
||||||
* |
|
||||||
* The following shared objects are populated to allow other |
|
||||||
* {@link org.springframework.security.config.annotation.SecurityConfigurer}'s to |
|
||||||
* customize: |
|
||||||
* <ul> |
|
||||||
* <li> |
|
||||||
* {@link org.springframework.security.web.access.intercept.FilterSecurityInterceptor}</li> |
|
||||||
* </ul> |
|
||||||
* |
|
||||||
* <h2>Shared Objects Used</h2> |
|
||||||
* |
|
||||||
* The following shared objects are used: |
|
||||||
* |
|
||||||
* <ul> |
|
||||||
* <li>AuthenticationManager</li> |
|
||||||
* </ul> |
|
||||||
* |
|
||||||
* @param <H> the type of {@link HttpSecurityBuilder} that is being configured |
|
||||||
* @author Rob Winch |
|
||||||
* @since 3.2 |
|
||||||
* @see ExpressionUrlAuthorizationConfigurer |
|
||||||
* @deprecated Use {@link AuthorizeHttpRequestsConfigurer} instead |
|
||||||
*/ |
|
||||||
@Deprecated |
|
||||||
public final class UrlAuthorizationConfigurer<H extends HttpSecurityBuilder<H>> |
|
||||||
extends AbstractInterceptUrlConfigurer<UrlAuthorizationConfigurer<H>, H> { |
|
||||||
|
|
||||||
private final StandardInterceptUrlRegistry registry; |
|
||||||
|
|
||||||
public UrlAuthorizationConfigurer(ApplicationContext context) { |
|
||||||
this.registry = new StandardInterceptUrlRegistry(context); |
|
||||||
} |
|
||||||
|
|
||||||
/** |
|
||||||
* The StandardInterceptUrlRegistry is what users will interact with after applying |
|
||||||
* the {@link UrlAuthorizationConfigurer}. |
|
||||||
* @return the {@link ExpressionUrlAuthorizationConfigurer} for further customizations |
|
||||||
*/ |
|
||||||
public StandardInterceptUrlRegistry getRegistry() { |
|
||||||
return this.registry; |
|
||||||
} |
|
||||||
|
|
||||||
/** |
|
||||||
* Adds an {@link ObjectPostProcessor} for this class. |
|
||||||
* @param objectPostProcessor |
|
||||||
* @return the {@link UrlAuthorizationConfigurer} for further customizations |
|
||||||
*/ |
|
||||||
@Override |
|
||||||
public UrlAuthorizationConfigurer<H> withObjectPostProcessor(ObjectPostProcessor<?> objectPostProcessor) { |
|
||||||
addObjectPostProcessor(objectPostProcessor); |
|
||||||
return this; |
|
||||||
} |
|
||||||
|
|
||||||
/** |
|
||||||
* Creates the default {@link AccessDecisionVoter} instances used if an |
|
||||||
* {@link AccessDecisionManager} was not specified. |
|
||||||
* @param http the builder to use |
|
||||||
*/ |
|
||||||
@Override |
|
||||||
@SuppressWarnings("rawtypes") |
|
||||||
List<AccessDecisionVoter<?>> getDecisionVoters(H http) { |
|
||||||
List<AccessDecisionVoter<?>> decisionVoters = new ArrayList<>(); |
|
||||||
decisionVoters.add(new RoleVoter()); |
|
||||||
decisionVoters.add(new AuthenticatedVoter()); |
|
||||||
return decisionVoters; |
|
||||||
} |
|
||||||
|
|
||||||
/** |
|
||||||
* Creates the {@link FilterInvocationSecurityMetadataSource} to use. The |
|
||||||
* implementation is a {@link DefaultFilterInvocationSecurityMetadataSource}. |
|
||||||
* @param http the builder to use |
|
||||||
*/ |
|
||||||
@Override |
|
||||||
FilterInvocationSecurityMetadataSource createMetadataSource(H http) { |
|
||||||
return new DefaultFilterInvocationSecurityMetadataSource(this.registry.createRequestMap()); |
|
||||||
} |
|
||||||
|
|
||||||
/** |
|
||||||
* Adds a mapping of the {@link RequestMatcher} instances to the |
|
||||||
* {@link ConfigAttribute} instances. |
|
||||||
* @param requestMatchers the {@link RequestMatcher} instances that should map to the |
|
||||||
* provided {@link ConfigAttribute} instances |
|
||||||
* @param configAttributes the {@link ConfigAttribute} instances that should be mapped |
|
||||||
* by the {@link RequestMatcher} instances |
|
||||||
* @return the {@link ExpressionUrlAuthorizationConfigurer} for further customizations |
|
||||||
*/ |
|
||||||
private StandardInterceptUrlRegistry addMapping(Iterable<? extends RequestMatcher> requestMatchers, |
|
||||||
Collection<ConfigAttribute> configAttributes) { |
|
||||||
for (RequestMatcher requestMatcher : requestMatchers) { |
|
||||||
this.registry.addMapping( |
|
||||||
new AbstractConfigAttributeRequestMatcherRegistry.UrlMapping(requestMatcher, configAttributes)); |
|
||||||
} |
|
||||||
return this.registry; |
|
||||||
} |
|
||||||
|
|
||||||
/** |
|
||||||
* Creates a String for specifying a user requires a role. |
|
||||||
* @param role the role that should be required which is prepended with ROLE_ |
|
||||||
* automatically (i.e. USER, ADMIN, etc). It should not start with ROLE_ |
|
||||||
* @return the {@link ConfigAttribute} expressed as a String |
|
||||||
*/ |
|
||||||
private static String hasRole(String role) { |
|
||||||
Assert.isTrue(!role.startsWith("ROLE_"), () -> role |
|
||||||
+ " should not start with ROLE_ since ROLE_ is automatically prepended when using hasRole. Consider using hasAuthority or access instead."); |
|
||||||
return "ROLE_" + role; |
|
||||||
} |
|
||||||
|
|
||||||
/** |
|
||||||
* Creates a String for specifying that a user requires one of many roles. |
|
||||||
* @param roles the roles that the user should have at least one of (i.e. ADMIN, USER, |
|
||||||
* etc). Each role should not start with ROLE_ since it is automatically prepended |
|
||||||
* already. |
|
||||||
* @return the {@link ConfigAttribute} expressed as a String |
|
||||||
*/ |
|
||||||
private static String[] hasAnyRole(String... roles) { |
|
||||||
for (int i = 0; i < roles.length; i++) { |
|
||||||
roles[i] = "ROLE_" + roles[i]; |
|
||||||
} |
|
||||||
return roles; |
|
||||||
} |
|
||||||
|
|
||||||
/** |
|
||||||
* Creates a String for specifying that a user requires one of many authorities |
|
||||||
* @param authorities the authorities that the user should have at least one of (i.e. |
|
||||||
* ROLE_USER, ROLE_ADMIN, etc). |
|
||||||
* @return the {@link ConfigAttribute} expressed as a String. |
|
||||||
*/ |
|
||||||
private static String[] hasAnyAuthority(String... authorities) { |
|
||||||
return authorities; |
|
||||||
} |
|
||||||
|
|
||||||
@Deprecated |
|
||||||
public final class StandardInterceptUrlRegistry extends |
|
||||||
UrlAuthorizationConfigurer<H>.AbstractInterceptUrlRegistry<StandardInterceptUrlRegistry, AuthorizedUrl> { |
|
||||||
|
|
||||||
private StandardInterceptUrlRegistry(ApplicationContext context) { |
|
||||||
setApplicationContext(context); |
|
||||||
} |
|
||||||
|
|
||||||
@Override |
|
||||||
public AuthorizedUrl requestMatchers(String... patterns) { |
|
||||||
return super.requestMatchers(patterns); |
|
||||||
} |
|
||||||
|
|
||||||
@Override |
|
||||||
public AuthorizedUrl requestMatchers(HttpMethod method, String... patterns) { |
|
||||||
return super.requestMatchers(method, patterns); |
|
||||||
} |
|
||||||
|
|
||||||
@Override |
|
||||||
public AuthorizedUrl requestMatchers(HttpMethod method) { |
|
||||||
return super.requestMatchers(method); |
|
||||||
} |
|
||||||
|
|
||||||
@Override |
|
||||||
public AuthorizedUrl requestMatchers(RequestMatcher... requestMatchers) { |
|
||||||
return super.requestMatchers(requestMatchers); |
|
||||||
} |
|
||||||
|
|
||||||
@Override |
|
||||||
protected AuthorizedUrl chainRequestMatchersInternal(List<RequestMatcher> 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.getBuilder(); |
|
||||||
} |
|
||||||
|
|
||||||
} |
|
||||||
|
|
||||||
/** |
|
||||||
* Maps the specified {@link RequestMatcher} instances to {@link ConfigAttribute} |
|
||||||
* instances. |
|
||||||
* |
|
||||||
* @author Rob Winch |
|
||||||
* @since 3.2 |
|
||||||
*/ |
|
||||||
public class AuthorizedUrl { |
|
||||||
|
|
||||||
private final List<? extends RequestMatcher> requestMatchers; |
|
||||||
|
|
||||||
/** |
|
||||||
* Creates a new instance |
|
||||||
* @param requestMatchers the {@link RequestMatcher} instances to map to some |
|
||||||
* {@link ConfigAttribute} instances. |
|
||||||
*/ |
|
||||||
AuthorizedUrl(List<? extends RequestMatcher> requestMatchers) { |
|
||||||
Assert.notEmpty(requestMatchers, "requestMatchers must contain at least one value"); |
|
||||||
this.requestMatchers = requestMatchers; |
|
||||||
} |
|
||||||
|
|
||||||
/** |
|
||||||
* Specifies a user requires a role. |
|
||||||
* @param role the role that should be required which is prepended with ROLE_ |
|
||||||
* automatically (i.e. USER, ADMIN, etc). It should not start with ROLE_ the |
|
||||||
* {@link UrlAuthorizationConfigurer} for further customization |
|
||||||
*/ |
|
||||||
public StandardInterceptUrlRegistry hasRole(String role) { |
|
||||||
return access(UrlAuthorizationConfigurer.hasRole(role)); |
|
||||||
} |
|
||||||
|
|
||||||
/** |
|
||||||
* Specifies that a user requires one of many roles. |
|
||||||
* @param roles the roles that the user should have at least one of (i.e. ADMIN, |
|
||||||
* USER, etc). Each role should not start with ROLE_ since it is automatically |
|
||||||
* prepended already. |
|
||||||
* @return the {@link UrlAuthorizationConfigurer} for further customization |
|
||||||
*/ |
|
||||||
public StandardInterceptUrlRegistry hasAnyRole(String... roles) { |
|
||||||
return access(UrlAuthorizationConfigurer.hasAnyRole(roles)); |
|
||||||
} |
|
||||||
|
|
||||||
/** |
|
||||||
* Specifies a user requires an authority. |
|
||||||
* @param authority the authority that should be required |
|
||||||
* @return the {@link UrlAuthorizationConfigurer} for further customization |
|
||||||
*/ |
|
||||||
public StandardInterceptUrlRegistry hasAuthority(String authority) { |
|
||||||
return access(authority); |
|
||||||
} |
|
||||||
|
|
||||||
/** |
|
||||||
* Specifies that a user requires one of many authorities |
|
||||||
* @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 StandardInterceptUrlRegistry hasAnyAuthority(String... authorities) { |
|
||||||
return access(UrlAuthorizationConfigurer.hasAnyAuthority(authorities)); |
|
||||||
} |
|
||||||
|
|
||||||
/** |
|
||||||
* Specifies that an anonymous user is allowed access |
|
||||||
* @return the {@link UrlAuthorizationConfigurer} for further customization |
|
||||||
*/ |
|
||||||
public StandardInterceptUrlRegistry anonymous() { |
|
||||||
return hasRole("ANONYMOUS"); |
|
||||||
} |
|
||||||
|
|
||||||
/** |
|
||||||
* Specifies that the user must have the specified {@link ConfigAttribute}'s |
|
||||||
* @param attributes the {@link ConfigAttribute}'s that restrict access to a URL |
|
||||||
* @return the {@link UrlAuthorizationConfigurer} for further customization |
|
||||||
*/ |
|
||||||
public StandardInterceptUrlRegistry access(String... attributes) { |
|
||||||
addMapping(this.requestMatchers, SecurityConfig.createList(attributes)); |
|
||||||
return UrlAuthorizationConfigurer.this.registry; |
|
||||||
} |
|
||||||
|
|
||||||
protected List<? extends RequestMatcher> getMatchers() { |
|
||||||
return this.requestMatchers; |
|
||||||
} |
|
||||||
|
|
||||||
} |
|
||||||
|
|
||||||
} |
|
||||||
@ -1,234 +0,0 @@ |
|||||||
/* |
|
||||||
* Copyright 2002-2022 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 |
|
||||||
* |
|
||||||
* https://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 |
|
||||||
|
|
||||||
import org.springframework.http.HttpMethod |
|
||||||
import org.springframework.security.config.annotation.web.builders.HttpSecurity |
|
||||||
import org.springframework.security.config.annotation.web.configurers.ExpressionUrlAuthorizationConfigurer |
|
||||||
import org.springframework.security.web.servlet.util.matcher.PathPatternRequestMatcher |
|
||||||
import org.springframework.security.web.util.matcher.AnyRequestMatcher |
|
||||||
import org.springframework.security.web.util.matcher.RequestMatcher |
|
||||||
|
|
||||||
/** |
|
||||||
* A Kotlin DSL to configure [HttpSecurity] request authorization using idiomatic Kotlin code. |
|
||||||
* |
|
||||||
* @author Eleftheria Stein |
|
||||||
* @since 5.3 |
|
||||||
*/ |
|
||||||
class AuthorizeRequestsDsl : AbstractRequestMatcherDsl() { |
|
||||||
private val authorizationRules = mutableListOf<AuthorizationRule>() |
|
||||||
private val PATTERN_TYPE = PatternType.PATH; |
|
||||||
|
|
||||||
/** |
|
||||||
* Adds a request authorization rule. |
|
||||||
* |
|
||||||
* @param matches the [RequestMatcher] to match incoming requests against |
|
||||||
* @param access the SpEL expression to secure the matching request |
|
||||||
* (i.e. "hasAuthority('ROLE_USER') and hasAuthority('ROLE_SUPER')") |
|
||||||
*/ |
|
||||||
fun authorize(matches: RequestMatcher = AnyRequestMatcher.INSTANCE, |
|
||||||
access: String) { |
|
||||||
authorizationRules.add(MatcherAuthorizationRule(matches, access)) |
|
||||||
} |
|
||||||
|
|
||||||
/** |
|
||||||
* Adds a request authorization rule for an endpoint matching the provided |
|
||||||
* pattern. |
|
||||||
* If Spring MVC is on the classpath, it will use an MVC matcher. |
|
||||||
* If Spring MVC is not on the classpath, it will use an ant matcher. |
|
||||||
* The MVC will use the same rules that Spring MVC uses for matching. |
|
||||||
* For example, often times a mapping of the path "/path" will match on |
|
||||||
* "/path", "/path/", "/path.html", etc. |
|
||||||
* If the current request will not be processed by Spring MVC, a reasonable default |
|
||||||
* using the pattern as an ant pattern will be used. |
|
||||||
* |
|
||||||
* @param pattern the pattern to match incoming requests against. |
|
||||||
* @param access the SpEL expression to secure the matching request |
|
||||||
* (i.e. "hasAuthority('ROLE_USER') and hasAuthority('ROLE_SUPER')") |
|
||||||
*/ |
|
||||||
fun authorize(pattern: String, access: String) { |
|
||||||
authorizationRules.add(PatternAuthorizationRule(pattern = pattern, |
|
||||||
patternType = PATTERN_TYPE, |
|
||||||
rule = access)) |
|
||||||
} |
|
||||||
|
|
||||||
/** |
|
||||||
* Adds a request authorization rule for an endpoint matching the provided |
|
||||||
* pattern. |
|
||||||
* If Spring MVC is on the classpath, it will use an MVC matcher. |
|
||||||
* If Spring MVC is not on the classpath, it will use an ant matcher. |
|
||||||
* The MVC will use the same rules that Spring MVC uses for matching. |
|
||||||
* For example, often times a mapping of the path "/path" will match on |
|
||||||
* "/path", "/path/", "/path.html", etc. |
|
||||||
* If the current request will not be processed by Spring MVC, a reasonable default |
|
||||||
* using the pattern as an ant pattern will be used. |
|
||||||
* |
|
||||||
* @param method the HTTP method to match the income requests against. |
|
||||||
* @param pattern the pattern to match incoming requests against. |
|
||||||
* @param access the SpEL expression to secure the matching request |
|
||||||
* (i.e. "hasAuthority('ROLE_USER') and hasAuthority('ROLE_SUPER')") |
|
||||||
*/ |
|
||||||
fun authorize(method: HttpMethod, pattern: String, access: String) { |
|
||||||
authorizationRules.add(PatternAuthorizationRule(pattern = pattern, |
|
||||||
patternType = PATTERN_TYPE, |
|
||||||
httpMethod = method, |
|
||||||
rule = access)) |
|
||||||
} |
|
||||||
|
|
||||||
/** |
|
||||||
* Adds a request authorization rule for an endpoint matching the provided |
|
||||||
* pattern. |
|
||||||
* If Spring MVC is on the classpath, it will use an MVC matcher. |
|
||||||
* If Spring MVC is not on the classpath, it will use an ant matcher. |
|
||||||
* The MVC will use the same rules that Spring MVC uses for matching. |
|
||||||
* For example, often times a mapping of the path "/path" will match on |
|
||||||
* "/path", "/path/", "/path.html", etc. |
|
||||||
* If the current request will not be processed by Spring MVC, a reasonable default |
|
||||||
* using the pattern as an ant pattern will be used. |
|
||||||
* |
|
||||||
* @param pattern the pattern to match incoming requests against. |
|
||||||
* @param servletPath the servlet path to match incoming requests against. This |
|
||||||
* only applies when using an MVC pattern matcher. |
|
||||||
* @param access the SpEL expression to secure the matching request |
|
||||||
* (i.e. "hasAuthority('ROLE_USER') and hasAuthority('ROLE_SUPER')") |
|
||||||
*/ |
|
||||||
fun authorize(pattern: String, servletPath: String, access: String) { |
|
||||||
authorizationRules.add(PatternAuthorizationRule(pattern = pattern, |
|
||||||
patternType = PATTERN_TYPE, |
|
||||||
servletPath = servletPath, |
|
||||||
rule = access)) |
|
||||||
} |
|
||||||
|
|
||||||
/** |
|
||||||
* Adds a request authorization rule for an endpoint matching the provided |
|
||||||
* pattern. |
|
||||||
* If Spring MVC is on the classpath, it will use an MVC matcher. |
|
||||||
* If Spring MVC is not on the classpath, it will use an ant matcher. |
|
||||||
* The MVC will use the same rules that Spring MVC uses for matching. |
|
||||||
* For example, often times a mapping of the path "/path" will match on |
|
||||||
* "/path", "/path/", "/path.html", etc. |
|
||||||
* If the current request will not be processed by Spring MVC, a reasonable default |
|
||||||
* using the pattern as an ant pattern will be used. |
|
||||||
* |
|
||||||
* @param method the HTTP method to match the income requests against. |
|
||||||
* @param pattern the pattern to match incoming requests against. |
|
||||||
* @param servletPath the servlet path to match incoming requests against. This |
|
||||||
* only applies when using an MVC pattern matcher. |
|
||||||
* @param access the SpEL expression to secure the matching request |
|
||||||
* (i.e. "hasAuthority('ROLE_USER') and hasAuthority('ROLE_SUPER')") |
|
||||||
*/ |
|
||||||
fun authorize(method: HttpMethod, pattern: String, servletPath: String, access: String) { |
|
||||||
authorizationRules.add(PatternAuthorizationRule(pattern = pattern, |
|
||||||
patternType = PATTERN_TYPE, |
|
||||||
servletPath = servletPath, |
|
||||||
httpMethod = method, |
|
||||||
rule = access)) |
|
||||||
} |
|
||||||
|
|
||||||
/** |
|
||||||
* Specify that URLs require a particular authority. |
|
||||||
* |
|
||||||
* @param authority the authority to require (i.e. ROLE_USER, ROLE_ADMIN, etc). |
|
||||||
* @return the SpEL expression "hasAuthority" with the given authority as a |
|
||||||
* parameter |
|
||||||
*/ |
|
||||||
fun hasAuthority(authority: String) = "hasAuthority('$authority')" |
|
||||||
|
|
||||||
/** |
|
||||||
* Specify that URLs require any number of authorities. |
|
||||||
* |
|
||||||
* @param authorities the authorities to require (i.e. ROLE_USER, ROLE_ADMIN, etc). |
|
||||||
* @return the SpEL expression "hasAnyAuthority" with the given authorities as a |
|
||||||
* parameter |
|
||||||
*/ |
|
||||||
fun hasAnyAuthority(vararg authorities: String): String { |
|
||||||
val anyAuthorities = authorities.joinToString("','") |
|
||||||
return "hasAnyAuthority('$anyAuthorities')" |
|
||||||
} |
|
||||||
|
|
||||||
/** |
|
||||||
* Specify that URLs require a particular role. |
|
||||||
* |
|
||||||
* @param role the role to require (i.e. USER, ADMIN, etc). |
|
||||||
* @return the SpEL expression "hasRole" with the given role as a |
|
||||||
* parameter |
|
||||||
*/ |
|
||||||
fun hasRole(role: String) = "hasRole('$role')" |
|
||||||
|
|
||||||
/** |
|
||||||
* Specify that URLs require any number of roles. |
|
||||||
* |
|
||||||
* @param roles the roles to require (i.e. USER, ADMIN, etc). |
|
||||||
* @return the SpEL expression "hasAnyRole" with the given roles as a |
|
||||||
* parameter |
|
||||||
*/ |
|
||||||
fun hasAnyRole(vararg roles: String): String { |
|
||||||
val anyRoles = roles.joinToString("','") |
|
||||||
return "hasAnyRole('$anyRoles')" |
|
||||||
} |
|
||||||
|
|
||||||
/** |
|
||||||
* Specify that URLs are allowed by anyone. |
|
||||||
*/ |
|
||||||
val permitAll = "permitAll" |
|
||||||
|
|
||||||
/** |
|
||||||
* Specify that URLs are allowed by anonymous users. |
|
||||||
*/ |
|
||||||
val anonymous = "anonymous" |
|
||||||
|
|
||||||
/** |
|
||||||
* Specify that URLs are allowed by users that have been remembered. |
|
||||||
*/ |
|
||||||
val rememberMe = "rememberMe" |
|
||||||
|
|
||||||
/** |
|
||||||
* Specify that URLs are not allowed by anyone. |
|
||||||
*/ |
|
||||||
val denyAll = "denyAll" |
|
||||||
|
|
||||||
/** |
|
||||||
* Specify that URLs are allowed by any authenticated user. |
|
||||||
*/ |
|
||||||
val authenticated = "authenticated" |
|
||||||
|
|
||||||
/** |
|
||||||
* Specify that URLs are allowed by users who have authenticated and were not |
|
||||||
* "remembered". |
|
||||||
*/ |
|
||||||
val fullyAuthenticated = "fullyAuthenticated" |
|
||||||
|
|
||||||
internal fun get(): (ExpressionUrlAuthorizationConfigurer<HttpSecurity>.ExpressionInterceptUrlRegistry) -> Unit { |
|
||||||
return { requests -> |
|
||||||
authorizationRules.forEach { rule -> |
|
||||||
when (rule) { |
|
||||||
is MatcherAuthorizationRule -> requests.requestMatchers(rule.matcher).access(rule.rule) |
|
||||||
is PatternAuthorizationRule -> { |
|
||||||
var builder = requests.applicationContext.getBeanProvider( |
|
||||||
PathPatternRequestMatcher.Builder::class.java) |
|
||||||
.getIfUnique(PathPatternRequestMatcher::withDefaults); |
|
||||||
if (rule.servletPath != null) { |
|
||||||
builder = builder.basePath(rule.servletPath) |
|
||||||
} |
|
||||||
requests.requestMatchers(builder.matcher(rule.httpMethod, rule.pattern)).access(rule.rule) |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
@ -1,561 +0,0 @@ |
|||||||
/* |
|
||||||
* Copyright 2002-2025 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 |
|
||||||
* |
|
||||||
* https://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 jakarta.servlet.http.HttpServletResponse; |
|
||||||
import org.junit.jupiter.api.AfterEach; |
|
||||||
import org.junit.jupiter.api.BeforeEach; |
|
||||||
import org.junit.jupiter.api.Test; |
|
||||||
|
|
||||||
import org.springframework.beans.factory.annotation.Autowired; |
|
||||||
import org.springframework.context.annotation.Bean; |
|
||||||
import org.springframework.context.annotation.Configuration; |
|
||||||
import org.springframework.http.HttpMethod; |
|
||||||
import org.springframework.mock.web.MockFilterChain; |
|
||||||
import org.springframework.mock.web.MockHttpServletRequest; |
|
||||||
import org.springframework.mock.web.MockHttpServletResponse; |
|
||||||
import org.springframework.security.access.hierarchicalroles.RoleHierarchy; |
|
||||||
import org.springframework.security.access.hierarchicalroles.RoleHierarchyImpl; |
|
||||||
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; |
|
||||||
import org.springframework.security.config.annotation.web.builders.HttpSecurity; |
|
||||||
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; |
|
||||||
import org.springframework.security.core.authority.AuthorityUtils; |
|
||||||
import org.springframework.security.core.context.SecurityContext; |
|
||||||
import org.springframework.security.core.context.SecurityContextImpl; |
|
||||||
import org.springframework.security.core.userdetails.UserDetailsService; |
|
||||||
import org.springframework.security.provisioning.InMemoryUserDetailsManager; |
|
||||||
import org.springframework.security.web.FilterChainProxy; |
|
||||||
import org.springframework.security.web.SecurityFilterChain; |
|
||||||
import org.springframework.security.web.access.expression.WebExpressionAuthorizationManager; |
|
||||||
import org.springframework.security.web.context.HttpSessionSecurityContextRepository; |
|
||||||
import org.springframework.security.web.servlet.MockServletContext; |
|
||||||
import org.springframework.security.web.servlet.util.matcher.PathPatternRequestMatcher; |
|
||||||
import org.springframework.web.bind.annotation.RequestMapping; |
|
||||||
import org.springframework.web.bind.annotation.RestController; |
|
||||||
import org.springframework.web.context.support.AnnotationConfigWebApplicationContext; |
|
||||||
import org.springframework.web.servlet.config.annotation.EnableWebMvc; |
|
||||||
import org.springframework.web.util.pattern.PathPatternParser; |
|
||||||
|
|
||||||
import static org.assertj.core.api.Assertions.assertThat; |
|
||||||
import static org.mockito.Mockito.spy; |
|
||||||
import static org.springframework.security.config.Customizer.withDefaults; |
|
||||||
import static org.springframework.security.web.servlet.util.matcher.PathPatternRequestMatcher.pathPattern; |
|
||||||
|
|
||||||
/** |
|
||||||
* @author Rob Winch |
|
||||||
* |
|
||||||
*/ |
|
||||||
public class AuthorizeRequestsTests { |
|
||||||
|
|
||||||
AnnotationConfigWebApplicationContext context; |
|
||||||
|
|
||||||
MockHttpServletRequest request; |
|
||||||
|
|
||||||
MockHttpServletResponse response; |
|
||||||
|
|
||||||
MockFilterChain chain; |
|
||||||
|
|
||||||
MockServletContext servletContext; |
|
||||||
|
|
||||||
@Autowired |
|
||||||
FilterChainProxy springSecurityFilterChain; |
|
||||||
|
|
||||||
@BeforeEach |
|
||||||
public void setup() { |
|
||||||
this.servletContext = spy(MockServletContext.mvc()); |
|
||||||
this.request = new MockHttpServletRequest(this.servletContext, "GET", ""); |
|
||||||
this.response = new MockHttpServletResponse(); |
|
||||||
this.chain = new MockFilterChain(); |
|
||||||
} |
|
||||||
|
|
||||||
@AfterEach |
|
||||||
public void cleanup() { |
|
||||||
if (this.context != null) { |
|
||||||
this.context.close(); |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
// SEC-3135
|
|
||||||
@Test |
|
||||||
public void antMatchersMethodAndNoPatterns() throws Exception { |
|
||||||
loadConfig(AntMatchersNoPatternsConfig.class); |
|
||||||
this.request.setMethod("POST"); |
|
||||||
this.springSecurityFilterChain.doFilter(this.request, this.response, this.chain); |
|
||||||
assertThat(this.response.getStatus()).isEqualTo(HttpServletResponse.SC_FORBIDDEN); |
|
||||||
} |
|
||||||
|
|
||||||
@Test |
|
||||||
public void postWhenPostDenyAllInLambdaThenRespondsWithForbidden() throws Exception { |
|
||||||
loadConfig(AntMatchersNoPatternsInLambdaConfig.class); |
|
||||||
this.request.setMethod("POST"); |
|
||||||
this.springSecurityFilterChain.doFilter(this.request, this.response, this.chain); |
|
||||||
assertThat(this.response.getStatus()).isEqualTo(HttpServletResponse.SC_FORBIDDEN); |
|
||||||
} |
|
||||||
|
|
||||||
// SEC-2256
|
|
||||||
@Test |
|
||||||
public void antMatchersPathVariables() throws Exception { |
|
||||||
loadConfig(AntPatchersPathVariables.class); |
|
||||||
this.request.setServletPath("/user/user"); |
|
||||||
this.request.setRequestURI("/user/user"); |
|
||||||
this.springSecurityFilterChain.doFilter(this.request, this.response, this.chain); |
|
||||||
assertThat(this.response.getStatus()).isEqualTo(HttpServletResponse.SC_OK); |
|
||||||
this.setup(); |
|
||||||
this.request.setServletPath("/user/deny"); |
|
||||||
this.request.setRequestURI("/user/deny"); |
|
||||||
this.springSecurityFilterChain.doFilter(this.request, this.response, this.chain); |
|
||||||
assertThat(this.response.getStatus()).isEqualTo(HttpServletResponse.SC_FORBIDDEN); |
|
||||||
} |
|
||||||
|
|
||||||
// SEC-2256
|
|
||||||
@Test |
|
||||||
public void antMatchersPathVariablesCaseInsensitive() throws Exception { |
|
||||||
loadConfig(AntPatchersPathVariables.class); |
|
||||||
this.request.setRequestURI("/USER/user"); |
|
||||||
this.request.setServletPath("/USER/user"); |
|
||||||
this.request.setRequestURI("/USER/user"); |
|
||||||
this.springSecurityFilterChain.doFilter(this.request, this.response, this.chain); |
|
||||||
assertThat(this.response.getStatus()).isEqualTo(HttpServletResponse.SC_OK); |
|
||||||
this.setup(); |
|
||||||
this.request.setRequestURI("/USER/deny"); |
|
||||||
this.request.setServletPath("/USER/deny"); |
|
||||||
this.request.setRequestURI("/USER/deny"); |
|
||||||
this.springSecurityFilterChain.doFilter(this.request, this.response, this.chain); |
|
||||||
assertThat(this.response.getStatus()).isEqualTo(HttpServletResponse.SC_FORBIDDEN); |
|
||||||
} |
|
||||||
|
|
||||||
// gh-3786
|
|
||||||
@Test |
|
||||||
public void antMatchersPathVariablesCaseInsensitiveCamelCaseVariables() throws Exception { |
|
||||||
loadConfig(AntMatchersPathVariablesCamelCaseVariables.class); |
|
||||||
this.request.setServletPath("/USER/user"); |
|
||||||
this.request.setRequestURI("/USER/user"); |
|
||||||
this.springSecurityFilterChain.doFilter(this.request, this.response, this.chain); |
|
||||||
assertThat(this.response.getStatus()).isEqualTo(HttpServletResponse.SC_OK); |
|
||||||
this.setup(); |
|
||||||
this.request.setServletPath("/USER/deny"); |
|
||||||
this.request.setRequestURI("/USER/deny"); |
|
||||||
this.springSecurityFilterChain.doFilter(this.request, this.response, this.chain); |
|
||||||
assertThat(this.response.getStatus()).isEqualTo(HttpServletResponse.SC_FORBIDDEN); |
|
||||||
} |
|
||||||
|
|
||||||
// gh-3394
|
|
||||||
@Test |
|
||||||
public void roleHiearchy() throws Exception { |
|
||||||
loadConfig(RoleHiearchyConfig.class); |
|
||||||
SecurityContext securityContext = new SecurityContextImpl(); |
|
||||||
securityContext.setAuthentication(UsernamePasswordAuthenticationToken.authenticated("test", "notused", |
|
||||||
AuthorityUtils.createAuthorityList("ROLE_USER"))); |
|
||||||
this.request.getSession() |
|
||||||
.setAttribute(HttpSessionSecurityContextRepository.SPRING_SECURITY_CONTEXT_KEY, securityContext); |
|
||||||
this.springSecurityFilterChain.doFilter(this.request, this.response, this.chain); |
|
||||||
assertThat(this.response.getStatus()).isEqualTo(HttpServletResponse.SC_OK); |
|
||||||
} |
|
||||||
|
|
||||||
@Test |
|
||||||
public void mvcMatcherPathVariables() throws Exception { |
|
||||||
loadConfig(MvcMatcherPathVariablesConfig.class); |
|
||||||
this.request.setRequestURI("/user/user"); |
|
||||||
this.springSecurityFilterChain.doFilter(this.request, this.response, this.chain); |
|
||||||
assertThat(this.response.getStatus()).isEqualTo(HttpServletResponse.SC_OK); |
|
||||||
this.setup(); |
|
||||||
this.request.setRequestURI("/user/deny"); |
|
||||||
this.springSecurityFilterChain.doFilter(this.request, this.response, this.chain); |
|
||||||
assertThat(this.response.getStatus()).isEqualTo(HttpServletResponse.SC_UNAUTHORIZED); |
|
||||||
} |
|
||||||
|
|
||||||
@Test |
|
||||||
public void requestWhenMvcMatcherPathVariablesThenMatchesOnPathVariables() throws Exception { |
|
||||||
loadConfig(MvcMatcherPathVariablesInLambdaConfig.class); |
|
||||||
this.request.setRequestURI("/user/user"); |
|
||||||
this.springSecurityFilterChain.doFilter(this.request, this.response, this.chain); |
|
||||||
assertThat(this.response.getStatus()).isEqualTo(HttpServletResponse.SC_OK); |
|
||||||
this.setup(); |
|
||||||
this.request.setRequestURI("/user/deny"); |
|
||||||
this.springSecurityFilterChain.doFilter(this.request, this.response, this.chain); |
|
||||||
assertThat(this.response.getStatus()).isEqualTo(HttpServletResponse.SC_UNAUTHORIZED); |
|
||||||
} |
|
||||||
|
|
||||||
public void loadConfig(Class<?>... configs) { |
|
||||||
this.context = new AnnotationConfigWebApplicationContext(); |
|
||||||
this.context.register(configs); |
|
||||||
this.context.setServletContext(this.servletContext); |
|
||||||
this.context.refresh(); |
|
||||||
this.context.getAutowireCapableBeanFactory().autowireBean(this); |
|
||||||
} |
|
||||||
|
|
||||||
@EnableWebSecurity |
|
||||||
@Configuration |
|
||||||
static class AntMatchersNoPatternsConfig { |
|
||||||
|
|
||||||
@Bean |
|
||||||
SecurityFilterChain filterChain(HttpSecurity http) throws Exception { |
|
||||||
// @formatter:off
|
|
||||||
http |
|
||||||
.authorizeHttpRequests((requests) -> requests |
|
||||||
.requestMatchers(pathPattern(HttpMethod.POST, "/**")).denyAll()); |
|
||||||
// @formatter:on
|
|
||||||
return http.build(); |
|
||||||
} |
|
||||||
|
|
||||||
@Bean |
|
||||||
UserDetailsService userDetailsService() { |
|
||||||
return new InMemoryUserDetailsManager(); |
|
||||||
} |
|
||||||
|
|
||||||
} |
|
||||||
|
|
||||||
@EnableWebSecurity |
|
||||||
@Configuration |
|
||||||
static class AntMatchersNoPatternsInLambdaConfig { |
|
||||||
|
|
||||||
@Bean |
|
||||||
SecurityFilterChain filterChain(HttpSecurity http) throws Exception { |
|
||||||
// @formatter:off
|
|
||||||
http |
|
||||||
.authorizeHttpRequests((authorize) -> authorize |
|
||||||
.requestMatchers(pathPattern(HttpMethod.POST, "/**")).denyAll() |
|
||||||
); |
|
||||||
// @formatter:on
|
|
||||||
return http.build(); |
|
||||||
} |
|
||||||
|
|
||||||
@Bean |
|
||||||
UserDetailsService userDetailsService() { |
|
||||||
return new InMemoryUserDetailsManager(); |
|
||||||
} |
|
||||||
|
|
||||||
} |
|
||||||
|
|
||||||
@EnableWebSecurity |
|
||||||
@Configuration |
|
||||||
static class AntPatchersPathVariables { |
|
||||||
|
|
||||||
@Bean |
|
||||||
SecurityFilterChain filterChain(HttpSecurity http) throws Exception { |
|
||||||
PathPatternParser parser = new PathPatternParser(); |
|
||||||
parser.setCaseSensitive(false); |
|
||||||
PathPatternRequestMatcher.Builder builder = PathPatternRequestMatcher.withPathPatternParser(parser); |
|
||||||
WebExpressionAuthorizationManager authz = new WebExpressionAuthorizationManager("#user == 'user'"); |
|
||||||
// @formatter:off
|
|
||||||
http |
|
||||||
.authorizeHttpRequests((requests) -> requests |
|
||||||
.requestMatchers(builder.matcher("/user/{user}")).access(authz) |
|
||||||
.anyRequest().denyAll()); |
|
||||||
// @formatter:on
|
|
||||||
return http.build(); |
|
||||||
} |
|
||||||
|
|
||||||
@Bean |
|
||||||
UserDetailsService userDetailsService() { |
|
||||||
return new InMemoryUserDetailsManager(); |
|
||||||
} |
|
||||||
|
|
||||||
} |
|
||||||
|
|
||||||
@EnableWebSecurity |
|
||||||
@Configuration |
|
||||||
static class AntMatchersPathVariablesCamelCaseVariables { |
|
||||||
|
|
||||||
@Bean |
|
||||||
SecurityFilterChain filterChain(HttpSecurity http) throws Exception { |
|
||||||
PathPatternParser parser = new PathPatternParser(); |
|
||||||
parser.setCaseSensitive(false); |
|
||||||
PathPatternRequestMatcher.Builder builder = PathPatternRequestMatcher.withPathPatternParser(parser); |
|
||||||
WebExpressionAuthorizationManager authz = new WebExpressionAuthorizationManager("#userName == 'user'"); |
|
||||||
|
|
||||||
// @formatter:off
|
|
||||||
http |
|
||||||
.authorizeHttpRequests((requests) -> requests |
|
||||||
.requestMatchers(builder.matcher("/user/{userName}")).access(authz) |
|
||||||
.anyRequest().denyAll()); |
|
||||||
// @formatter:on
|
|
||||||
return http.build(); |
|
||||||
} |
|
||||||
|
|
||||||
@Bean |
|
||||||
UserDetailsService userDetailsService() { |
|
||||||
return new InMemoryUserDetailsManager(); |
|
||||||
} |
|
||||||
|
|
||||||
} |
|
||||||
|
|
||||||
@EnableWebSecurity |
|
||||||
@Configuration |
|
||||||
static class RoleHiearchyConfig { |
|
||||||
|
|
||||||
@Bean |
|
||||||
SecurityFilterChain filterChain(HttpSecurity http) throws Exception { |
|
||||||
// @formatter:off
|
|
||||||
http |
|
||||||
.authorizeHttpRequests((requests) -> requests |
|
||||||
.anyRequest().hasRole("ADMIN")); |
|
||||||
// @formatter:on
|
|
||||||
return http.build(); |
|
||||||
} |
|
||||||
|
|
||||||
@Bean |
|
||||||
UserDetailsService userDetailsService() { |
|
||||||
return new InMemoryUserDetailsManager(); |
|
||||||
} |
|
||||||
|
|
||||||
@Bean |
|
||||||
RoleHierarchy roleHiearchy() { |
|
||||||
return RoleHierarchyImpl.fromHierarchy("ROLE_USER > ROLE_ADMIN"); |
|
||||||
} |
|
||||||
|
|
||||||
} |
|
||||||
|
|
||||||
@EnableWebSecurity |
|
||||||
@Configuration |
|
||||||
@EnableWebMvc |
|
||||||
static class MvcMatcherConfig { |
|
||||||
|
|
||||||
@Bean |
|
||||||
SecurityFilterChain filterChain(HttpSecurity http) throws Exception { |
|
||||||
// @formatter:off
|
|
||||||
http |
|
||||||
.httpBasic(withDefaults()) |
|
||||||
.authorizeHttpRequests((requests) -> requests |
|
||||||
.requestMatchers("/path").denyAll()); |
|
||||||
// @formatter:on
|
|
||||||
return http.build(); |
|
||||||
} |
|
||||||
|
|
||||||
@Bean |
|
||||||
UserDetailsService userDetailsService() { |
|
||||||
return new InMemoryUserDetailsManager(); |
|
||||||
} |
|
||||||
|
|
||||||
@RestController |
|
||||||
static class PathController { |
|
||||||
|
|
||||||
@RequestMapping("/path") |
|
||||||
String path() { |
|
||||||
return "path"; |
|
||||||
} |
|
||||||
|
|
||||||
} |
|
||||||
|
|
||||||
} |
|
||||||
|
|
||||||
@EnableWebSecurity |
|
||||||
@Configuration |
|
||||||
@EnableWebMvc |
|
||||||
static class MvcMatcherInLambdaConfig { |
|
||||||
|
|
||||||
@Bean |
|
||||||
SecurityFilterChain filterChain(HttpSecurity http) throws Exception { |
|
||||||
// @formatter:off
|
|
||||||
http |
|
||||||
.httpBasic(withDefaults()) |
|
||||||
.authorizeHttpRequests((authorize) -> authorize |
|
||||||
.requestMatchers("/path").denyAll() |
|
||||||
); |
|
||||||
// @formatter:on
|
|
||||||
return http.build(); |
|
||||||
} |
|
||||||
|
|
||||||
@Bean |
|
||||||
UserDetailsService userDetailsService() { |
|
||||||
return new InMemoryUserDetailsManager(); |
|
||||||
} |
|
||||||
|
|
||||||
@RestController |
|
||||||
static class PathController { |
|
||||||
|
|
||||||
@RequestMapping("/path") |
|
||||||
String path() { |
|
||||||
return "path"; |
|
||||||
} |
|
||||||
|
|
||||||
} |
|
||||||
|
|
||||||
} |
|
||||||
|
|
||||||
@EnableWebSecurity |
|
||||||
@Configuration |
|
||||||
@EnableWebMvc |
|
||||||
static class MvcMatcherServletPathConfig { |
|
||||||
|
|
||||||
@Bean |
|
||||||
SecurityFilterChain filterChain(HttpSecurity http, PathPatternRequestMatcher.Builder builder) throws Exception { |
|
||||||
PathPatternRequestMatcher.Builder spring = builder.basePath("/spring"); |
|
||||||
// @formatter:off
|
|
||||||
http |
|
||||||
.httpBasic(withDefaults()) |
|
||||||
.authorizeHttpRequests((requests) -> requests |
|
||||||
.requestMatchers(spring.matcher("/path")).denyAll()); |
|
||||||
// @formatter:on
|
|
||||||
return http.build(); |
|
||||||
} |
|
||||||
|
|
||||||
@Bean |
|
||||||
UserDetailsService userDetailsService() { |
|
||||||
return new InMemoryUserDetailsManager(); |
|
||||||
} |
|
||||||
|
|
||||||
@RestController |
|
||||||
static class PathController { |
|
||||||
|
|
||||||
@RequestMapping("/path") |
|
||||||
String path() { |
|
||||||
return "path"; |
|
||||||
} |
|
||||||
|
|
||||||
} |
|
||||||
|
|
||||||
} |
|
||||||
|
|
||||||
@EnableWebSecurity |
|
||||||
@Configuration |
|
||||||
@EnableWebMvc |
|
||||||
static class MvcMatcherServletPathInLambdaConfig { |
|
||||||
|
|
||||||
@Bean |
|
||||||
SecurityFilterChain filterChain(HttpSecurity http, PathPatternRequestMatcher.Builder builder) throws Exception { |
|
||||||
PathPatternRequestMatcher.Builder spring = builder.basePath("/spring"); |
|
||||||
// @formatter:off
|
|
||||||
http |
|
||||||
.httpBasic(withDefaults()) |
|
||||||
.authorizeHttpRequests((authorize) -> authorize |
|
||||||
.requestMatchers(spring.matcher("/path")).denyAll() |
|
||||||
); |
|
||||||
// @formatter:on
|
|
||||||
return http.build(); |
|
||||||
} |
|
||||||
|
|
||||||
@Bean |
|
||||||
UserDetailsService userDetailsService() { |
|
||||||
return new InMemoryUserDetailsManager(); |
|
||||||
} |
|
||||||
|
|
||||||
@RestController |
|
||||||
static class PathController { |
|
||||||
|
|
||||||
@RequestMapping("/path") |
|
||||||
String path() { |
|
||||||
return "path"; |
|
||||||
} |
|
||||||
|
|
||||||
} |
|
||||||
|
|
||||||
} |
|
||||||
|
|
||||||
@EnableWebSecurity |
|
||||||
@Configuration |
|
||||||
@EnableWebMvc |
|
||||||
static class MvcMatcherPathVariablesConfig { |
|
||||||
|
|
||||||
@Bean |
|
||||||
SecurityFilterChain filterChain(HttpSecurity http) throws Exception { |
|
||||||
WebExpressionAuthorizationManager authz = new WebExpressionAuthorizationManager("#userName == 'user'"); |
|
||||||
// @formatter:off
|
|
||||||
http |
|
||||||
.httpBasic(withDefaults()) |
|
||||||
.authorizeHttpRequests((requests) -> requests |
|
||||||
.requestMatchers("/user/{userName}").access(authz)); |
|
||||||
// @formatter:on
|
|
||||||
return http.build(); |
|
||||||
} |
|
||||||
|
|
||||||
@Bean |
|
||||||
UserDetailsService userDetailsService() { |
|
||||||
return new InMemoryUserDetailsManager(); |
|
||||||
} |
|
||||||
|
|
||||||
@RestController |
|
||||||
static class PathController { |
|
||||||
|
|
||||||
@RequestMapping("/path") |
|
||||||
String path() { |
|
||||||
return "path"; |
|
||||||
} |
|
||||||
|
|
||||||
} |
|
||||||
|
|
||||||
} |
|
||||||
|
|
||||||
@EnableWebSecurity |
|
||||||
@Configuration |
|
||||||
@EnableWebMvc |
|
||||||
static class MvcMatcherPathVariablesInLambdaConfig { |
|
||||||
|
|
||||||
@Bean |
|
||||||
SecurityFilterChain filterChain(HttpSecurity http) throws Exception { |
|
||||||
WebExpressionAuthorizationManager authz = new WebExpressionAuthorizationManager("#userName == 'user'"); |
|
||||||
// @formatter:off
|
|
||||||
http |
|
||||||
.httpBasic(withDefaults()) |
|
||||||
.authorizeHttpRequests((authorize) -> authorize |
|
||||||
.requestMatchers("/user/{userName}").access(authz) |
|
||||||
); |
|
||||||
// @formatter:on
|
|
||||||
return http.build(); |
|
||||||
} |
|
||||||
|
|
||||||
@Bean |
|
||||||
UserDetailsService userDetailsService() { |
|
||||||
return new InMemoryUserDetailsManager(); |
|
||||||
} |
|
||||||
|
|
||||||
@RestController |
|
||||||
static class PathController { |
|
||||||
|
|
||||||
@RequestMapping("/path") |
|
||||||
String path() { |
|
||||||
return "path"; |
|
||||||
} |
|
||||||
|
|
||||||
} |
|
||||||
|
|
||||||
} |
|
||||||
|
|
||||||
@EnableWebSecurity |
|
||||||
@Configuration |
|
||||||
@EnableWebMvc |
|
||||||
static class MvcMatcherPathServletPathRequiredConfig { |
|
||||||
|
|
||||||
@Bean |
|
||||||
SecurityFilterChain filterChain(HttpSecurity http) throws Exception { |
|
||||||
// @formatter:off
|
|
||||||
http |
|
||||||
.httpBasic(withDefaults()) |
|
||||||
.authorizeHttpRequests((requests) -> requests |
|
||||||
.requestMatchers("/user").denyAll()); |
|
||||||
// @formatter:on
|
|
||||||
return http.build(); |
|
||||||
} |
|
||||||
|
|
||||||
@Bean |
|
||||||
UserDetailsService userDetailsService() { |
|
||||||
return new InMemoryUserDetailsManager(); |
|
||||||
} |
|
||||||
|
|
||||||
@RestController |
|
||||||
static class PathController { |
|
||||||
|
|
||||||
@RequestMapping("/path") |
|
||||||
String path() { |
|
||||||
return "path"; |
|
||||||
} |
|
||||||
|
|
||||||
} |
|
||||||
|
|
||||||
} |
|
||||||
|
|
||||||
} |
|
||||||
File diff suppressed because it is too large
Load Diff
@ -1,254 +0,0 @@ |
|||||||
/* |
|
||||||
* Copyright 2002-2024 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 |
|
||||||
* |
|
||||||
* https://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.Base64; |
|
||||||
|
|
||||||
import jakarta.servlet.http.HttpServletResponse; |
|
||||||
import org.junit.jupiter.api.AfterEach; |
|
||||||
import org.junit.jupiter.api.BeforeEach; |
|
||||||
import org.junit.jupiter.api.Test; |
|
||||||
|
|
||||||
import org.springframework.beans.factory.annotation.Autowired; |
|
||||||
import org.springframework.context.ApplicationContext; |
|
||||||
import org.springframework.context.annotation.Bean; |
|
||||||
import org.springframework.context.annotation.Configuration; |
|
||||||
import org.springframework.mock.web.MockFilterChain; |
|
||||||
import org.springframework.mock.web.MockHttpServletRequest; |
|
||||||
import org.springframework.mock.web.MockHttpServletResponse; |
|
||||||
import org.springframework.security.config.Customizer; |
|
||||||
import org.springframework.security.config.annotation.web.builders.HttpSecurity; |
|
||||||
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; |
|
||||||
import org.springframework.security.core.userdetails.PasswordEncodedUser; |
|
||||||
import org.springframework.security.core.userdetails.User; |
|
||||||
import org.springframework.security.core.userdetails.UserDetails; |
|
||||||
import org.springframework.security.core.userdetails.UserDetailsService; |
|
||||||
import org.springframework.security.provisioning.InMemoryUserDetailsManager; |
|
||||||
import org.springframework.security.web.FilterChainProxy; |
|
||||||
import org.springframework.security.web.SecurityFilterChain; |
|
||||||
import org.springframework.security.web.servlet.MockServletContext; |
|
||||||
import org.springframework.security.web.servlet.util.matcher.PathPatternRequestMatcher; |
|
||||||
import org.springframework.web.bind.annotation.RequestMapping; |
|
||||||
import org.springframework.web.bind.annotation.RestController; |
|
||||||
import org.springframework.web.context.support.AnnotationConfigWebApplicationContext; |
|
||||||
import org.springframework.web.servlet.config.annotation.EnableWebMvc; |
|
||||||
|
|
||||||
import static org.assertj.core.api.Assertions.assertThat; |
|
||||||
import static org.springframework.security.config.Customizer.withDefaults; |
|
||||||
|
|
||||||
/** |
|
||||||
* @author Rob Winch |
|
||||||
* @author M.S. Dousti |
|
||||||
* |
|
||||||
*/ |
|
||||||
public class UrlAuthorizationConfigurerTests { |
|
||||||
|
|
||||||
AnnotationConfigWebApplicationContext context; |
|
||||||
|
|
||||||
MockHttpServletRequest request; |
|
||||||
|
|
||||||
MockHttpServletResponse response; |
|
||||||
|
|
||||||
MockFilterChain chain; |
|
||||||
|
|
||||||
@Autowired |
|
||||||
FilterChainProxy springSecurityFilterChain; |
|
||||||
|
|
||||||
@BeforeEach |
|
||||||
public void setup() { |
|
||||||
this.request = new MockHttpServletRequest(MockServletContext.mvc(), "GET", ""); |
|
||||||
this.request.setMethod("GET"); |
|
||||||
this.response = new MockHttpServletResponse(); |
|
||||||
this.chain = new MockFilterChain(); |
|
||||||
} |
|
||||||
|
|
||||||
@AfterEach |
|
||||||
public void cleanup() { |
|
||||||
if (this.context != null) { |
|
||||||
this.context.close(); |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
@Test |
|
||||||
public void anonymousUrlAuthorization() { |
|
||||||
loadConfig(AnonymousUrlAuthorizationConfig.class); |
|
||||||
} |
|
||||||
|
|
||||||
// gh-10956
|
|
||||||
@Test |
|
||||||
public void multiMvcMatchersConfig() throws Exception { |
|
||||||
loadConfig(MultiMvcMatcherConfig.class); |
|
||||||
this.request.addHeader("Authorization", |
|
||||||
"Basic " + new String(Base64.getEncoder().encode("user:password".getBytes()))); |
|
||||||
this.request.setRequestURI("/test-1"); |
|
||||||
this.springSecurityFilterChain.doFilter(this.request, this.response, this.chain); |
|
||||||
assertThat(this.response.getStatus()).isEqualTo(HttpServletResponse.SC_FORBIDDEN); |
|
||||||
setup(); |
|
||||||
this.request.addHeader("Authorization", |
|
||||||
"Basic " + new String(Base64.getEncoder().encode("user:password".getBytes()))); |
|
||||||
this.request.setRequestURI("/test-2"); |
|
||||||
this.springSecurityFilterChain.doFilter(this.request, this.response, this.chain); |
|
||||||
assertThat(this.response.getStatus()).isEqualTo(HttpServletResponse.SC_FORBIDDEN); |
|
||||||
setup(); |
|
||||||
this.request.addHeader("Authorization", |
|
||||||
"Basic " + new String(Base64.getEncoder().encode("user:password".getBytes()))); |
|
||||||
this.request.setRequestURI("/test-3"); |
|
||||||
this.springSecurityFilterChain.doFilter(this.request, this.response, this.chain); |
|
||||||
assertThat(this.response.getStatus()).isEqualTo(HttpServletResponse.SC_FORBIDDEN); |
|
||||||
setup(); |
|
||||||
this.request.addHeader("Authorization", |
|
||||||
"Basic " + new String(Base64.getEncoder().encode("user:password".getBytes()))); |
|
||||||
this.request.setRequestURI("/test-x"); |
|
||||||
this.springSecurityFilterChain.doFilter(this.request, this.response, this.chain); |
|
||||||
assertThat(this.response.getStatus()).isEqualTo(HttpServletResponse.SC_OK); |
|
||||||
} |
|
||||||
|
|
||||||
public void loadConfig(Class<?>... configs) { |
|
||||||
this.context = new AnnotationConfigWebApplicationContext(); |
|
||||||
this.context.register(configs); |
|
||||||
this.context.setServletContext(MockServletContext.mvc()); |
|
||||||
this.context.refresh(); |
|
||||||
this.context.getAutowireCapableBeanFactory().autowireBean(this); |
|
||||||
} |
|
||||||
|
|
||||||
@EnableWebSecurity |
|
||||||
@Configuration |
|
||||||
@EnableWebMvc |
|
||||||
static class MvcMatcherConfig { |
|
||||||
|
|
||||||
@Bean |
|
||||||
SecurityFilterChain filterChain(HttpSecurity http, ApplicationContext context, |
|
||||||
PathPatternRequestMatcher.Builder builder) throws Exception { |
|
||||||
// @formatter:off
|
|
||||||
http |
|
||||||
.httpBasic(withDefaults()) |
|
||||||
.apply(new UrlAuthorizationConfigurer(context)).getRegistry() |
|
||||||
.requestMatchers(builder.matcher("/path")).hasRole("ADMIN"); |
|
||||||
// @formatter:on
|
|
||||||
return http.build(); |
|
||||||
} |
|
||||||
|
|
||||||
@Bean |
|
||||||
UserDetailsService userDetailsService() { |
|
||||||
return new InMemoryUserDetailsManager(PasswordEncodedUser.user()); |
|
||||||
} |
|
||||||
|
|
||||||
@RestController |
|
||||||
static class PathController { |
|
||||||
|
|
||||||
@RequestMapping("/path") |
|
||||||
String path() { |
|
||||||
return "path"; |
|
||||||
} |
|
||||||
|
|
||||||
} |
|
||||||
|
|
||||||
} |
|
||||||
|
|
||||||
@EnableWebSecurity |
|
||||||
@Configuration |
|
||||||
@EnableWebMvc |
|
||||||
static class MvcMatcherServletPathConfig { |
|
||||||
|
|
||||||
@Bean |
|
||||||
SecurityFilterChain filterChain(HttpSecurity http, ApplicationContext context, |
|
||||||
PathPatternRequestMatcher.Builder builder) throws Exception { |
|
||||||
PathPatternRequestMatcher.Builder spring = builder.basePath("/spring"); |
|
||||||
// @formatter:off
|
|
||||||
http |
|
||||||
.httpBasic(withDefaults()) |
|
||||||
.apply(new UrlAuthorizationConfigurer(context)).getRegistry() |
|
||||||
.requestMatchers(builder.matcher("/path")).hasRole("ADMIN"); |
|
||||||
// @formatter:on
|
|
||||||
return http.build(); |
|
||||||
} |
|
||||||
|
|
||||||
@Bean |
|
||||||
UserDetailsService userDetailsService() { |
|
||||||
return new InMemoryUserDetailsManager(PasswordEncodedUser.user()); |
|
||||||
} |
|
||||||
|
|
||||||
@RestController |
|
||||||
static class PathController { |
|
||||||
|
|
||||||
@RequestMapping("/path") |
|
||||||
String path() { |
|
||||||
return "path"; |
|
||||||
} |
|
||||||
|
|
||||||
} |
|
||||||
|
|
||||||
} |
|
||||||
|
|
||||||
@EnableWebSecurity |
|
||||||
@Configuration |
|
||||||
static class AnonymousUrlAuthorizationConfig { |
|
||||||
|
|
||||||
@Bean |
|
||||||
SecurityFilterChain filterChain(HttpSecurity http) throws Exception { |
|
||||||
// @formatter:off
|
|
||||||
http |
|
||||||
.apply(new UrlAuthorizationConfigurer<>(null)).getRegistry() |
|
||||||
.anyRequest().anonymous(); |
|
||||||
return http.build(); |
|
||||||
// @formatter:on
|
|
||||||
} |
|
||||||
|
|
||||||
} |
|
||||||
|
|
||||||
@EnableWebSecurity |
|
||||||
@Configuration |
|
||||||
@EnableWebMvc |
|
||||||
static class MultiMvcMatcherConfig { |
|
||||||
|
|
||||||
@Bean |
|
||||||
SecurityFilterChain security(HttpSecurity http, ApplicationContext context) throws Exception { |
|
||||||
// @formatter:off
|
|
||||||
http |
|
||||||
.httpBasic(Customizer.withDefaults()) |
|
||||||
.apply(new UrlAuthorizationConfigurer<>(context)).getRegistry() |
|
||||||
.requestMatchers("/test-1").hasRole("ADMIN") |
|
||||||
.requestMatchers("/test-2").hasRole("ADMIN") |
|
||||||
.requestMatchers("/test-3").hasRole("ADMIN") |
|
||||||
.anyRequest().hasRole("USER"); |
|
||||||
// @formatter:on
|
|
||||||
return http.build(); |
|
||||||
} |
|
||||||
|
|
||||||
@Bean |
|
||||||
UserDetailsService userDetailsService() { |
|
||||||
UserDetails user = User.withDefaultPasswordEncoder() |
|
||||||
.username("user") |
|
||||||
.password("password") |
|
||||||
.roles("USER") |
|
||||||
.build(); |
|
||||||
return new InMemoryUserDetailsManager(user); |
|
||||||
} |
|
||||||
|
|
||||||
@RestController |
|
||||||
static class PathController { |
|
||||||
|
|
||||||
@RequestMapping({ "/test-1", "/test-2", "/test-3", "/test-x" }) |
|
||||||
String path() { |
|
||||||
return "path"; |
|
||||||
} |
|
||||||
|
|
||||||
} |
|
||||||
|
|
||||||
} |
|
||||||
|
|
||||||
} |
|
||||||
@ -1,520 +0,0 @@ |
|||||||
/* |
|
||||||
* Copyright 2002-2022 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 |
|
||||||
* |
|
||||||
* https://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 |
|
||||||
|
|
||||||
import org.junit.jupiter.api.Test |
|
||||||
import org.junit.jupiter.api.extension.ExtendWith |
|
||||||
import org.springframework.beans.factory.annotation.Autowired |
|
||||||
import org.springframework.context.annotation.Bean |
|
||||||
import org.springframework.context.annotation.Configuration |
|
||||||
import org.springframework.http.HttpMethod |
|
||||||
import org.springframework.security.config.annotation.web.builders.HttpSecurity |
|
||||||
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity |
|
||||||
import org.springframework.security.config.test.SpringTestContext |
|
||||||
import org.springframework.security.config.test.SpringTestContextExtension |
|
||||||
import org.springframework.security.core.userdetails.User |
|
||||||
import org.springframework.security.core.userdetails.UserDetailsService |
|
||||||
import org.springframework.security.provisioning.InMemoryUserDetailsManager |
|
||||||
import org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.csrf |
|
||||||
import org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.httpBasic |
|
||||||
import org.springframework.security.web.SecurityFilterChain |
|
||||||
import org.springframework.security.web.util.matcher.RegexRequestMatcher |
|
||||||
import org.springframework.test.web.servlet.MockMvc |
|
||||||
import org.springframework.test.web.servlet.get |
|
||||||
import org.springframework.test.web.servlet.post |
|
||||||
import org.springframework.test.web.servlet.put |
|
||||||
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders |
|
||||||
import org.springframework.test.web.servlet.result.MockMvcResultMatchers.status |
|
||||||
import org.springframework.web.bind.annotation.GetMapping |
|
||||||
import org.springframework.web.bind.annotation.PathVariable |
|
||||||
import org.springframework.web.bind.annotation.RequestMapping |
|
||||||
import org.springframework.web.bind.annotation.RestController |
|
||||||
import org.springframework.web.servlet.config.annotation.EnableWebMvc |
|
||||||
|
|
||||||
/** |
|
||||||
* Tests for [AuthorizeRequestsDsl] |
|
||||||
* |
|
||||||
* @author Eleftheria Stein |
|
||||||
*/ |
|
||||||
@ExtendWith(SpringTestContextExtension::class) |
|
||||||
class AuthorizeRequestsDslTests { |
|
||||||
@JvmField |
|
||||||
val spring = SpringTestContext(this) |
|
||||||
|
|
||||||
@Autowired |
|
||||||
lateinit var mockMvc: MockMvc |
|
||||||
|
|
||||||
@Test |
|
||||||
fun `request when secured by regex matcher then responds with forbidden`() { |
|
||||||
this.spring.register(AuthorizeRequestsByRegexConfig::class.java).autowire() |
|
||||||
|
|
||||||
this.mockMvc.get("/private") |
|
||||||
.andExpect { |
|
||||||
status { isForbidden() } |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
@Test |
|
||||||
fun `request when allowed by regex matcher then responds with ok`() { |
|
||||||
this.spring.register(AuthorizeRequestsByRegexConfig::class.java).autowire() |
|
||||||
|
|
||||||
this.mockMvc.get("/path") |
|
||||||
.andExpect { |
|
||||||
status { isOk() } |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
@Test |
|
||||||
fun `request when allowed by regex matcher with http method then responds based on method`() { |
|
||||||
this.spring.register(AuthorizeRequestsByRegexConfig::class.java).autowire() |
|
||||||
|
|
||||||
this.mockMvc.post("/onlyPostPermitted") { with(csrf()) } |
|
||||||
.andExpect { |
|
||||||
status { isOk() } |
|
||||||
} |
|
||||||
|
|
||||||
this.mockMvc.get("/onlyPostPermitted") |
|
||||||
.andExpect { |
|
||||||
status { isForbidden() } |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
@Configuration |
|
||||||
@EnableWebSecurity |
|
||||||
open class AuthorizeRequestsByRegexConfig { |
|
||||||
@Bean |
|
||||||
open fun securityFilterChain(http: HttpSecurity): SecurityFilterChain { |
|
||||||
http { |
|
||||||
authorizeRequests { |
|
||||||
authorize(RegexRequestMatcher("/path", null), permitAll) |
|
||||||
authorize(RegexRequestMatcher("/onlyPostPermitted", "POST"), permitAll) |
|
||||||
authorize(RegexRequestMatcher("/onlyPostPermitted", "GET"), denyAll) |
|
||||||
authorize(RegexRequestMatcher(".*", null), authenticated) |
|
||||||
} |
|
||||||
} |
|
||||||
return http.build() |
|
||||||
} |
|
||||||
|
|
||||||
@RestController |
|
||||||
internal class PathController { |
|
||||||
@RequestMapping("/path") |
|
||||||
fun path(): String { |
|
||||||
return "ok" |
|
||||||
} |
|
||||||
|
|
||||||
@RequestMapping("/onlyPostPermitted") |
|
||||||
fun onlyPostPermitted(): String { |
|
||||||
return "ok" |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
@Test |
|
||||||
fun `request when secured by mvc then responds with forbidden`() { |
|
||||||
this.spring.register(AuthorizeRequestsByMvcConfig::class.java).autowire() |
|
||||||
|
|
||||||
this.mockMvc.get("/private") |
|
||||||
.andExpect { |
|
||||||
status { isForbidden() } |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
@Configuration |
|
||||||
@EnableWebSecurity |
|
||||||
@EnableWebMvc |
|
||||||
open class AuthorizeRequestsByMvcConfig { |
|
||||||
@Bean |
|
||||||
open fun securityFilterChain(http: HttpSecurity): SecurityFilterChain { |
|
||||||
http { |
|
||||||
authorizeRequests { |
|
||||||
authorize("/path", permitAll) |
|
||||||
authorize("/**", authenticated) |
|
||||||
} |
|
||||||
} |
|
||||||
return http.build() |
|
||||||
} |
|
||||||
|
|
||||||
@RestController |
|
||||||
internal class PathController { |
|
||||||
@RequestMapping("/path") |
|
||||||
fun path(): String { |
|
||||||
return "ok" |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
@Test |
|
||||||
fun `request when secured by mvc path variables then responds based on path variable value`() { |
|
||||||
this.spring.register(MvcMatcherPathVariablesConfig::class.java).autowire() |
|
||||||
|
|
||||||
this.mockMvc.get("/user/user") |
|
||||||
.andExpect { |
|
||||||
status { isOk() } |
|
||||||
} |
|
||||||
|
|
||||||
this.mockMvc.get("/user/deny") |
|
||||||
.andExpect { |
|
||||||
status { isForbidden() } |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
@Configuration |
|
||||||
@EnableWebSecurity |
|
||||||
@EnableWebMvc |
|
||||||
open class MvcMatcherPathVariablesConfig { |
|
||||||
@Bean |
|
||||||
open fun securityFilterChain(http: HttpSecurity): SecurityFilterChain { |
|
||||||
http { |
|
||||||
authorizeRequests { |
|
||||||
authorize("/user/{userName}", "#userName == 'user'") |
|
||||||
} |
|
||||||
} |
|
||||||
return http.build() |
|
||||||
} |
|
||||||
|
|
||||||
@RestController |
|
||||||
internal class PathController { |
|
||||||
@RequestMapping("/user/{user}") |
|
||||||
fun path(@PathVariable user: String) { |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
@Test |
|
||||||
fun `request when user has allowed role then responds with OK`() { |
|
||||||
this.spring.register(HasRoleConfig::class.java).autowire() |
|
||||||
|
|
||||||
this.mockMvc.get("/") { |
|
||||||
with(httpBasic("admin", "password")) |
|
||||||
}.andExpect { |
|
||||||
status { isOk() } |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
@Test |
|
||||||
fun `request when user does not have allowed role then responds with forbidden`() { |
|
||||||
this.spring.register(HasRoleConfig::class.java).autowire() |
|
||||||
|
|
||||||
this.mockMvc.get("/") { |
|
||||||
with(httpBasic("user", "password")) |
|
||||||
}.andExpect { |
|
||||||
status { isForbidden() } |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
@Configuration |
|
||||||
@EnableWebSecurity |
|
||||||
@EnableWebMvc |
|
||||||
open class HasRoleConfig { |
|
||||||
@Bean |
|
||||||
open fun securityFilterChain(http: HttpSecurity): SecurityFilterChain { |
|
||||||
http { |
|
||||||
authorizeRequests { |
|
||||||
authorize("/**", hasRole("ADMIN")) |
|
||||||
} |
|
||||||
httpBasic { } |
|
||||||
} |
|
||||||
return http.build() |
|
||||||
} |
|
||||||
|
|
||||||
@RestController |
|
||||||
internal class PathController { |
|
||||||
@GetMapping("/") |
|
||||||
fun index() { |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
@Bean |
|
||||||
open fun userDetailsService(): UserDetailsService { |
|
||||||
val userDetails = User.withDefaultPasswordEncoder() |
|
||||||
.username("user") |
|
||||||
.password("password") |
|
||||||
.roles("USER") |
|
||||||
.build() |
|
||||||
val adminDetails = User.withDefaultPasswordEncoder() |
|
||||||
.username("admin") |
|
||||||
.password("password") |
|
||||||
.roles("ADMIN") |
|
||||||
.build() |
|
||||||
return InMemoryUserDetailsManager(userDetails, adminDetails) |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
@Test |
|
||||||
fun `request when user has some allowed roles then responds with OK`() { |
|
||||||
this.spring.register(HasAnyRoleConfig::class.java).autowire() |
|
||||||
|
|
||||||
this.mockMvc.get("/") { |
|
||||||
with(httpBasic("user", "password")) |
|
||||||
}.andExpect { |
|
||||||
status { isOk() } |
|
||||||
} |
|
||||||
|
|
||||||
this.mockMvc.get("/") { |
|
||||||
with(httpBasic("admin", "password")) |
|
||||||
}.andExpect { |
|
||||||
status { isOk() } |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
@Test |
|
||||||
fun `request when user does not have any allowed roles then responds with forbidden`() { |
|
||||||
this.spring.register(HasAnyRoleConfig::class.java).autowire() |
|
||||||
|
|
||||||
this.mockMvc.get("/") { |
|
||||||
with(httpBasic("other", "password")) |
|
||||||
}.andExpect { |
|
||||||
status { isForbidden() } |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
@Configuration |
|
||||||
@EnableWebSecurity |
|
||||||
@EnableWebMvc |
|
||||||
open class HasAnyRoleConfig { |
|
||||||
@Bean |
|
||||||
open fun securityFilterChain(http: HttpSecurity): SecurityFilterChain { |
|
||||||
http { |
|
||||||
authorizeRequests { |
|
||||||
authorize("/**", hasAnyRole("ADMIN", "USER")) |
|
||||||
} |
|
||||||
httpBasic { } |
|
||||||
} |
|
||||||
return http.build() |
|
||||||
} |
|
||||||
|
|
||||||
@RestController |
|
||||||
internal class PathController { |
|
||||||
@GetMapping("/") |
|
||||||
fun index():String { |
|
||||||
return "ok" |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
@Bean |
|
||||||
open fun userDetailsService(): UserDetailsService { |
|
||||||
val userDetails = User.withDefaultPasswordEncoder() |
|
||||||
.username("user") |
|
||||||
.password("password") |
|
||||||
.roles("USER") |
|
||||||
.build() |
|
||||||
val admin1Details = User.withDefaultPasswordEncoder() |
|
||||||
.username("admin") |
|
||||||
.password("password") |
|
||||||
.roles("ADMIN") |
|
||||||
.build() |
|
||||||
val admin2Details = User.withDefaultPasswordEncoder() |
|
||||||
.username("other") |
|
||||||
.password("password") |
|
||||||
.roles("OTHER") |
|
||||||
.build() |
|
||||||
return InMemoryUserDetailsManager(userDetails, admin1Details, admin2Details) |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
@Test |
|
||||||
fun `request when user has some allowed authorities then responds with OK`() { |
|
||||||
this.spring.register(HasAnyAuthorityConfig::class.java).autowire() |
|
||||||
|
|
||||||
this.mockMvc.get("/") { |
|
||||||
with(httpBasic("user", "password")) |
|
||||||
}.andExpect { |
|
||||||
status { isOk() } |
|
||||||
} |
|
||||||
|
|
||||||
this.mockMvc.get("/") { |
|
||||||
with(httpBasic("admin", "password")) |
|
||||||
}.andExpect { |
|
||||||
status { isOk() } |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
@Test |
|
||||||
fun `request when user does not have any allowed authorities then responds with forbidden`() { |
|
||||||
this.spring.register(HasAnyAuthorityConfig::class.java).autowire() |
|
||||||
|
|
||||||
this.mockMvc.get("/") { |
|
||||||
with(httpBasic("other", "password")) |
|
||||||
}.andExpect { |
|
||||||
status { isForbidden() } |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
@Configuration |
|
||||||
@EnableWebSecurity |
|
||||||
@EnableWebMvc |
|
||||||
open class HasAnyAuthorityConfig { |
|
||||||
@Bean |
|
||||||
open fun securityFilterChain(http: HttpSecurity): SecurityFilterChain { |
|
||||||
http { |
|
||||||
authorizeRequests { |
|
||||||
authorize("/**", hasAnyAuthority("ROLE_ADMIN", "ROLE_USER")) |
|
||||||
} |
|
||||||
httpBasic { } |
|
||||||
} |
|
||||||
return http.build() |
|
||||||
} |
|
||||||
|
|
||||||
@RestController |
|
||||||
internal class PathController { |
|
||||||
@GetMapping("/") |
|
||||||
fun index():String { |
|
||||||
return "ok" |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
@Bean |
|
||||||
open fun userDetailsService(): UserDetailsService { |
|
||||||
val userDetails = User.withDefaultPasswordEncoder() |
|
||||||
.username("user") |
|
||||||
.password("password") |
|
||||||
.authorities("ROLE_USER") |
|
||||||
.build() |
|
||||||
val admin1Details = User.withDefaultPasswordEncoder() |
|
||||||
.username("admin") |
|
||||||
.password("password") |
|
||||||
.authorities("ROLE_ADMIN") |
|
||||||
.build() |
|
||||||
val admin2Details = User.withDefaultPasswordEncoder() |
|
||||||
.username("other") |
|
||||||
.password("password") |
|
||||||
.authorities("ROLE_OTHER") |
|
||||||
.build() |
|
||||||
return InMemoryUserDetailsManager(userDetails, admin1Details, admin2Details) |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
@Test |
|
||||||
fun `request when secured by mvc with servlet path then responds based on servlet path`() { |
|
||||||
this.spring.register(MvcMatcherServletPathConfig::class.java).autowire() |
|
||||||
|
|
||||||
this.mockMvc.perform(MockMvcRequestBuilders.get("/spring/path") |
|
||||||
.servletPath("/spring")) |
|
||||||
.andExpect(status().isForbidden) |
|
||||||
|
|
||||||
this.mockMvc.perform(MockMvcRequestBuilders.get("/other/path") |
|
||||||
.servletPath("/other")) |
|
||||||
.andExpect(status().isOk) |
|
||||||
} |
|
||||||
|
|
||||||
@Configuration |
|
||||||
@EnableWebSecurity |
|
||||||
@EnableWebMvc |
|
||||||
open class MvcMatcherServletPathConfig { |
|
||||||
@Bean |
|
||||||
open fun securityFilterChain(http: HttpSecurity): SecurityFilterChain { |
|
||||||
http { |
|
||||||
authorizeRequests { |
|
||||||
authorize("/path", |
|
||||||
"/spring", |
|
||||||
denyAll) |
|
||||||
} |
|
||||||
} |
|
||||||
return http.build() |
|
||||||
} |
|
||||||
|
|
||||||
@RestController |
|
||||||
internal class PathController { |
|
||||||
@RequestMapping("/path") |
|
||||||
fun path():String { |
|
||||||
return "ok" |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
@Configuration |
|
||||||
@EnableWebSecurity |
|
||||||
@EnableWebMvc |
|
||||||
open class AuthorizeRequestsByMvcConfigWithHttpMethod{ |
|
||||||
@Bean |
|
||||||
open fun securityFilterChain(http: HttpSecurity): SecurityFilterChain { |
|
||||||
http { |
|
||||||
authorizeRequests { |
|
||||||
authorize(HttpMethod.GET, "/path", permitAll) |
|
||||||
authorize(HttpMethod.PUT, "/path", denyAll) |
|
||||||
} |
|
||||||
} |
|
||||||
return http.build() |
|
||||||
} |
|
||||||
|
|
||||||
@RestController |
|
||||||
internal class PathController { |
|
||||||
@RequestMapping("/path") |
|
||||||
fun path(): String { |
|
||||||
return "ok" |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
@Test |
|
||||||
fun `request when secured by mvc with http method then responds based on http method`() { |
|
||||||
this.spring.register(AuthorizeRequestsByMvcConfigWithHttpMethod::class.java).autowire() |
|
||||||
|
|
||||||
this.mockMvc.get("/path") |
|
||||||
.andExpect { |
|
||||||
status { isOk() } |
|
||||||
} |
|
||||||
|
|
||||||
this.mockMvc.put("/path") { with(csrf()) } |
|
||||||
.andExpect { |
|
||||||
status { isForbidden() } |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
@Configuration |
|
||||||
@EnableWebSecurity |
|
||||||
@EnableWebMvc |
|
||||||
open class MvcMatcherServletPathHttpMethodConfig { |
|
||||||
@Bean |
|
||||||
open fun securityFilterChain(http: HttpSecurity): SecurityFilterChain { |
|
||||||
http { |
|
||||||
authorizeRequests { |
|
||||||
authorize(HttpMethod.GET, "/path", "/spring", denyAll) |
|
||||||
authorize(HttpMethod.PUT, "/path", "/spring", denyAll) |
|
||||||
} |
|
||||||
} |
|
||||||
return http.build() |
|
||||||
} |
|
||||||
|
|
||||||
@RestController |
|
||||||
internal class PathController { |
|
||||||
@RequestMapping("/path") |
|
||||||
fun path(): String { |
|
||||||
return "ok" |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@Test |
|
||||||
fun `request when secured by mvc with servlet path and http method then responds based on path and method`() { |
|
||||||
this.spring.register(MvcMatcherServletPathConfig::class.java).autowire() |
|
||||||
|
|
||||||
this.mockMvc.perform(MockMvcRequestBuilders.get("/spring/path") |
|
||||||
.servletPath("/spring")) |
|
||||||
.andExpect(status().isForbidden) |
|
||||||
|
|
||||||
this.mockMvc.perform(MockMvcRequestBuilders.put("/spring/path") |
|
||||||
.servletPath("/spring")) |
|
||||||
.andExpect(status().isForbidden) |
|
||||||
|
|
||||||
this.mockMvc.perform(MockMvcRequestBuilders.get("/other/path") |
|
||||||
.servletPath("/other")) |
|
||||||
.andExpect(status().isOk) |
|
||||||
} |
|
||||||
} |
|
||||||
Loading…
Reference in new issue