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

Move Web Access API

Issue gh-17847
This commit is contained in:
Josh Cummings
2025-09-02 16:06:03 -06:00
parent 3182883e2e
commit fa4806dbcc
35 changed files with 67 additions and 54 deletions
+4
View File
@@ -12,6 +12,7 @@ dependencies {
api 'io.micrometer:micrometer-observation'
optional project(':spring-security-messaging')
optional project(':spring-security-web')
optional 'org.springframework:spring-websocket'
optional 'com.fasterxml.jackson.core:jackson-databind'
optional 'io.micrometer:context-propagation'
@@ -22,6 +23,9 @@ dependencies {
optional 'org.springframework:spring-tx'
optional 'org.jetbrains.kotlinx:kotlinx-coroutines-reactor'
provided 'jakarta.servlet:jakarta.servlet-api'
testImplementation project(path : ':spring-security-web', configuration : 'tests')
testImplementation 'commons-collections:commons-collections'
testImplementation 'io.projectreactor:reactor-test'
testImplementation "org.assertj:assertj-core"
@@ -0,0 +1,117 @@
/*
* Copyright 2004-present 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.web.access;
import java.util.Collection;
import jakarta.servlet.ServletContext;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.jspecify.annotations.Nullable;
import org.springframework.core.log.LogMessage;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.access.ConfigAttribute;
import org.springframework.security.access.intercept.AbstractSecurityInterceptor;
import org.springframework.security.core.Authentication;
import org.springframework.security.web.FilterInvocation;
import org.springframework.util.Assert;
import org.springframework.web.context.ServletContextAware;
/**
* Allows users to determine whether they have privileges for a given web URI.
*
* @author Ben Alex
* @author Luke Taylor
* @since 3.0
* @deprecated Use {@link AuthorizationManagerWebInvocationPrivilegeEvaluator} instead
*/
@Deprecated
public class DefaultWebInvocationPrivilegeEvaluator implements WebInvocationPrivilegeEvaluator, ServletContextAware {
protected static final Log logger = LogFactory.getLog(DefaultWebInvocationPrivilegeEvaluator.class);
private final AbstractSecurityInterceptor securityInterceptor;
private @Nullable ServletContext servletContext;
public DefaultWebInvocationPrivilegeEvaluator(AbstractSecurityInterceptor securityInterceptor) {
Assert.notNull(securityInterceptor, "SecurityInterceptor cannot be null");
Assert.isTrue(FilterInvocation.class.equals(securityInterceptor.getSecureObjectClass()),
"AbstractSecurityInterceptor does not support FilterInvocations");
Assert.notNull(securityInterceptor.getAccessDecisionManager(),
"AbstractSecurityInterceptor must provide a non-null AccessDecisionManager");
this.securityInterceptor = securityInterceptor;
}
/**
* Determines whether the user represented by the supplied <tt>Authentication</tt>
* object is allowed to invoke the supplied URI.
* @param uri the URI excluding the context path (a default context path setting will
* be used)
*/
@Override
public boolean isAllowed(String uri, @Nullable Authentication authentication) {
return isAllowed(null, uri, null, authentication);
}
/**
* Determines whether the user represented by the supplied <tt>Authentication</tt>
* object is allowed to invoke the supplied URI, with the given .
* <p>
* Note the default implementation of <tt>FilterInvocationSecurityMetadataSource</tt>
* disregards the <code>contextPath</code> when evaluating which secure object
* metadata applies to a given request URI, so generally the <code>contextPath</code>
* is unimportant unless you are using a custom
* <code>FilterInvocationSecurityMetadataSource</code>.
* @param uri the URI excluding the context path
* @param contextPath the context path (may be null, in which case a default value
* will be used).
* @param method the HTTP method (or null, for any method)
* @param authentication the <tt>Authentication</tt> instance whose authorities should
* be used in evaluation whether access should be granted.
* @return true if access is allowed, false if denied
*/
@Override
public boolean isAllowed(@Nullable String contextPath, String uri, @Nullable String method,
@Nullable Authentication authentication) {
Assert.notNull(uri, "uri parameter is required");
FilterInvocation filterInvocation = new FilterInvocation(contextPath, uri, method, this.servletContext);
Collection<ConfigAttribute> attributes = this.securityInterceptor.obtainSecurityMetadataSource()
.getAttributes(filterInvocation);
if (attributes == null) {
return (!this.securityInterceptor.isRejectPublicInvocations());
}
if (authentication == null) {
return false;
}
try {
this.securityInterceptor.getAccessDecisionManager().decide(authentication, filterInvocation, attributes);
return true;
}
catch (AccessDeniedException ex) {
logger.debug(LogMessage.format("%s denied for %s", filterInvocation, authentication), ex);
return false;
}
}
@Override
public void setServletContext(ServletContext servletContext) {
this.servletContext = servletContext;
}
}
@@ -0,0 +1,104 @@
/*
* Copyright 2004-present 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.web.access.channel;
import java.io.IOException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.jspecify.annotations.Nullable;
import org.springframework.core.log.LogMessage;
import org.springframework.security.web.DefaultRedirectStrategy;
import org.springframework.security.web.PortMapper;
import org.springframework.security.web.PortMapperImpl;
import org.springframework.security.web.RedirectStrategy;
import org.springframework.util.Assert;
/**
* @author Luke Taylor
* @deprecated please use
* {@link org.springframework.security.web.transport.HttpsRedirectFilter} and its
* associated {@link PortMapper}
*/
@Deprecated
public abstract class AbstractRetryEntryPoint implements ChannelEntryPoint {
protected final Log logger = LogFactory.getLog(getClass());
private PortMapper portMapper = new PortMapperImpl();
/**
* The scheme ("http://" or "https://")
*/
private final String scheme;
/**
* The standard port for the scheme (80 for http, 443 for https)
*/
private final int standardPort;
private RedirectStrategy redirectStrategy = new DefaultRedirectStrategy();
public AbstractRetryEntryPoint(String scheme, int standardPort) {
this.scheme = scheme;
this.standardPort = standardPort;
}
@Override
public void commence(HttpServletRequest request, HttpServletResponse response) throws IOException {
String queryString = request.getQueryString();
String redirectUrl = request.getRequestURI() + ((queryString != null) ? ("?" + queryString) : "");
Integer currentPort = this.portMapper.getServerPort(request);
Integer redirectPort = getMappedPort(currentPort);
if (redirectPort != null) {
boolean includePort = redirectPort != this.standardPort;
String port = (includePort) ? (":" + redirectPort) : "";
redirectUrl = this.scheme + request.getServerName() + port + redirectUrl;
}
this.logger.debug(LogMessage.format("Redirecting to: %s", redirectUrl));
this.redirectStrategy.sendRedirect(request, response, redirectUrl);
}
protected abstract @Nullable Integer getMappedPort(Integer mapFromPort);
protected final PortMapper getPortMapper() {
return this.portMapper;
}
public void setPortMapper(PortMapper portMapper) {
Assert.notNull(portMapper, "portMapper cannot be null");
this.portMapper = portMapper;
}
/**
* Sets the strategy to be used for redirecting to the required channel URL. A
* {@code DefaultRedirectStrategy} instance will be used if not set.
* @param redirectStrategy the strategy instance to which the URL will be passed.
*/
public void setRedirectStrategy(RedirectStrategy redirectStrategy) {
Assert.notNull(redirectStrategy, "redirectStrategy cannot be null");
this.redirectStrategy = redirectStrategy;
}
protected final RedirectStrategy getRedirectStrategy() {
return this.redirectStrategy;
}
}
@@ -0,0 +1,59 @@
/*
* Copyright 2004, 2005, 2006 Acegi Technology Pty Limited
*
* 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.web.access.channel;
import java.io.IOException;
import java.util.Collection;
import jakarta.servlet.ServletException;
import org.springframework.security.access.ConfigAttribute;
import org.springframework.security.web.FilterInvocation;
import org.springframework.security.web.util.matcher.RequestMatcher;
/**
* Decides whether a web channel provides sufficient security.
*
* @author Ben Alex
* @deprecated no replacement is planned, though consider using a custom
* {@link RequestMatcher} for any sophisticated decision-making
*/
@Deprecated
public interface ChannelDecisionManager {
/**
* Decided whether the presented {@link FilterInvocation} provides the appropriate
* level of channel security based on the requested list of <tt>ConfigAttribute</tt>s.
*
*/
void decide(FilterInvocation invocation, Collection<ConfigAttribute> config) throws IOException, ServletException;
/**
* Indicates whether this <code>ChannelDecisionManager</code> is able to process the
* passed <code>ConfigAttribute</code>.
* <p>
* This allows the <code>ChannelProcessingFilter</code> to check every configuration
* attribute can be consumed by the configured <code>ChannelDecisionManager</code>.
* </p>
* @param attribute a configuration attribute that has been configured against the
* <code>ChannelProcessingFilter</code>
* @return true if this <code>ChannelDecisionManager</code> can support the passed
* configuration attribute
*/
boolean supports(ConfigAttribute attribute);
}
@@ -0,0 +1,110 @@
/*
* Copyright 2004, 2005, 2006 Acegi Technology Pty Limited
*
* 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.web.access.channel;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import jakarta.servlet.ServletException;
import org.jspecify.annotations.NullUnmarked;
import org.jspecify.annotations.Nullable;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.security.access.ConfigAttribute;
import org.springframework.security.web.FilterInvocation;
import org.springframework.security.web.util.matcher.RequestMatcher;
import org.springframework.util.Assert;
/**
* Implementation of {@link ChannelDecisionManager}.
* <p>
* Iterates through each configured {@link ChannelProcessor}. If a
* <code>ChannelProcessor</code> has any issue with the security of the request, it should
* cause a redirect, exception or whatever other action is appropriate for the
* <code>ChannelProcessor</code> implementation.
* <p>
* Once any response is committed (ie a redirect is written to the response object), the
* <code>ChannelDecisionManagerImpl</code> will not iterate through any further
* <code>ChannelProcessor</code>s.
* <p>
* The attribute "ANY_CHANNEL" if applied to a particular URL, the iteration through the
* channel processors will be skipped (see SEC-494, SEC-335).
*
* @author Ben Alex
* @deprecated no replacement is planned, though consider using a custom
* {@link RequestMatcher} for any sophisticated decision-making
*/
@Deprecated
@NullUnmarked
public class ChannelDecisionManagerImpl implements ChannelDecisionManager, InitializingBean {
public static final String ANY_CHANNEL = "ANY_CHANNEL";
private List<ChannelProcessor> channelProcessors;
@Override
public void afterPropertiesSet() {
Assert.notEmpty(this.channelProcessors, "A list of ChannelProcessors is required");
}
@Override
public void decide(FilterInvocation invocation, Collection<ConfigAttribute> config)
throws IOException, ServletException {
for (ConfigAttribute attribute : config) {
if (ANY_CHANNEL.equals(attribute.getAttribute())) {
return;
}
}
for (ChannelProcessor processor : this.channelProcessors) {
processor.decide(invocation, config);
if (invocation.getResponse().isCommitted()) {
break;
}
}
}
protected @Nullable List<ChannelProcessor> getChannelProcessors() {
return this.channelProcessors;
}
@SuppressWarnings("cast")
public void setChannelProcessors(List<?> channelProcessors) {
Assert.notEmpty(channelProcessors, "A list of ChannelProcessors is required");
this.channelProcessors = new ArrayList<>(channelProcessors.size());
for (Object currentObject : channelProcessors) {
Assert.isInstanceOf(ChannelProcessor.class, currentObject, () -> "ChannelProcessor "
+ currentObject.getClass().getName() + " must implement ChannelProcessor");
this.channelProcessors.add((ChannelProcessor) currentObject);
}
}
@Override
public boolean supports(ConfigAttribute attribute) {
if (ANY_CHANNEL.equals(attribute.getAttribute())) {
return true;
}
for (ChannelProcessor processor : this.channelProcessors) {
if (processor.supports(attribute)) {
return true;
}
}
return false;
}
}
@@ -0,0 +1,55 @@
/*
* Copyright 2004, 2005, 2006 Acegi Technology Pty Limited
*
* 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.web.access.channel;
import java.io.IOException;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.security.web.PortMapper;
/**
* May be used by a {@link ChannelProcessor} to launch a web channel.
*
* <p>
* <code>ChannelProcessor</code>s can elect to launch a new web channel directly, or they
* can delegate to another class. The <code>ChannelEntryPoint</code> is a pluggable
* interface to assist <code>ChannelProcessor</code>s in performing this delegation.
*
* @author Ben Alex
* @deprecated please use
* {@link org.springframework.security.web.transport.HttpsRedirectFilter} and its
* associated {@link PortMapper}
*/
@Deprecated
public interface ChannelEntryPoint {
/**
* Commences a secure channel.
* <p>
* Implementations should modify the headers on the <code>ServletResponse</code> as
* necessary to commence the user agent using the implementation's supported channel
* type.
* @param request that a <code>ChannelProcessor</code> has rejected
* @param response so that the user agent can begin using a new channel
*
*/
void commence(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException;
}
@@ -0,0 +1,160 @@
/*
* Copyright 2004, 2005, 2006 Acegi Technology Pty Limited
*
* 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.web.access.channel;
import java.io.IOException;
import java.util.Collection;
import java.util.HashSet;
import java.util.Set;
import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.ServletRequest;
import jakarta.servlet.ServletResponse;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.jspecify.annotations.Nullable;
import org.springframework.core.log.LogMessage;
import org.springframework.security.access.ConfigAttribute;
import org.springframework.security.web.FilterInvocation;
import org.springframework.security.web.access.intercept.FilterInvocationSecurityMetadataSource;
import org.springframework.util.Assert;
import org.springframework.web.filter.GenericFilterBean;
/**
* Ensures a web request is delivered over the required channel.
* <p>
* Internally uses a {@link FilterInvocation} to represent the request, allowing a
* {@code FilterInvocationSecurityMetadataSource} to be used to lookup the attributes
* which apply.
* <p>
* Delegates the actual channel security decisions and necessary actions to the configured
* {@link ChannelDecisionManager}. If a response is committed by the
* {@code ChannelDecisionManager}, the filter chain will not proceed.
* <p>
* The most common usage is to ensure that a request takes place over HTTPS, where the
* {@link ChannelDecisionManagerImpl} is configured with a {@link SecureChannelProcessor}
* and an {@link InsecureChannelProcessor}. A typical configuration would be
*
* <pre>
*
* &lt;bean id="channelProcessingFilter" class="org.springframework.security.web.access.channel.ChannelProcessingFilter"&gt;
* &lt;property name="channelDecisionManager" ref="channelDecisionManager"/&gt;
* &lt;property name="securityMetadataSource"&gt;
* &lt;security:filter-security-metadata-source request-matcher="regex"&gt;
* &lt;security:intercept-url pattern="\A/secure/.*\Z" access="REQUIRES_SECURE_CHANNEL"/&gt;
* &lt;security:intercept-url pattern="\A/login.jsp.*\Z" access="REQUIRES_SECURE_CHANNEL"/&gt;
* &lt;security:intercept-url pattern="\A/.*\Z" access="ANY_CHANNEL"/&gt;
* &lt;/security:filter-security-metadata-source&gt;
* &lt;/property&gt;
* &lt;/bean&gt;
*
* &lt;bean id="channelDecisionManager" class="org.springframework.security.web.access.channel.ChannelDecisionManagerImpl"&gt;
* &lt;property name="channelProcessors"&gt;
* &lt;list&gt;
* &lt;ref bean="secureChannelProcessor"/&gt;
* &lt;ref bean="insecureChannelProcessor"/&gt;
* &lt;/list&gt;
* &lt;/property&gt;
* &lt;/bean&gt;
*
* &lt;bean id="secureChannelProcessor"
* class="org.springframework.security.web.access.channel.SecureChannelProcessor"/&gt;
* &lt;bean id="insecureChannelProcessor"
* class="org.springframework.security.web.access.channel.InsecureChannelProcessor"/&gt;
*
* </pre>
*
* which would force the login form and any access to the {@code /secure} path to be made
* over HTTPS.
*
* @author Ben Alex
* @deprecated see {@link org.springframework.security.web.transport.HttpsRedirectFilter}
*/
@Deprecated
public class ChannelProcessingFilter extends GenericFilterBean {
@SuppressWarnings("NullAway.Init")
private ChannelDecisionManager channelDecisionManager;
@SuppressWarnings("NullAway.Init")
private FilterInvocationSecurityMetadataSource securityMetadataSource;
@Override
public void afterPropertiesSet() {
Assert.notNull(this.securityMetadataSource, "securityMetadataSource must be specified");
Assert.notNull(this.channelDecisionManager, "channelDecisionManager must be specified");
Collection<ConfigAttribute> attributes = this.securityMetadataSource.getAllConfigAttributes();
if (attributes == null) {
this.logger.warn("Could not validate configuration attributes as the "
+ "FilterInvocationSecurityMetadataSource did not return any attributes");
return;
}
Set<ConfigAttribute> unsupportedAttributes = getUnsupportedAttributes(attributes);
Assert.isTrue(unsupportedAttributes.isEmpty(),
() -> "Unsupported configuration attributes: " + unsupportedAttributes);
this.logger.info("Validated configuration attributes");
}
private Set<ConfigAttribute> getUnsupportedAttributes(Collection<ConfigAttribute> attrDefs) {
Set<ConfigAttribute> unsupportedAttributes = new HashSet<>();
for (ConfigAttribute attr : attrDefs) {
if (!this.channelDecisionManager.supports(attr)) {
unsupportedAttributes.add(attr);
}
}
return unsupportedAttributes;
}
@Override
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain)
throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) req;
HttpServletResponse response = (HttpServletResponse) res;
FilterInvocation filterInvocation = new FilterInvocation(request, response, chain);
Collection<ConfigAttribute> attributes = this.securityMetadataSource.getAttributes(filterInvocation);
if (attributes != null) {
this.logger.debug(LogMessage.format("Request: %s; ConfigAttributes: %s", filterInvocation, attributes));
this.channelDecisionManager.decide(filterInvocation, attributes);
@Nullable HttpServletResponse channelResponse = filterInvocation.getResponse();
Assert.notNull(channelResponse, "HttpServletResponse is required");
if (channelResponse.isCommitted()) {
return;
}
}
chain.doFilter(request, response);
}
protected @Nullable ChannelDecisionManager getChannelDecisionManager() {
return this.channelDecisionManager;
}
protected FilterInvocationSecurityMetadataSource getSecurityMetadataSource() {
return this.securityMetadataSource;
}
public void setChannelDecisionManager(ChannelDecisionManager channelDecisionManager) {
this.channelDecisionManager = channelDecisionManager;
}
public void setSecurityMetadataSource(
FilterInvocationSecurityMetadataSource filterInvocationSecurityMetadataSource) {
this.securityMetadataSource = filterInvocationSecurityMetadataSource;
}
}
@@ -0,0 +1,63 @@
/*
* Copyright 2004, 2005, 2006 Acegi Technology Pty Limited
*
* 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.web.access.channel;
import java.io.IOException;
import java.util.Collection;
import jakarta.servlet.ServletException;
import org.springframework.security.access.ConfigAttribute;
import org.springframework.security.web.FilterInvocation;
import org.springframework.security.web.util.matcher.RequestMatcher;
/**
* Decides whether a web channel meets a specific security condition.
* <p>
* <code>ChannelProcessor</code> implementations are iterated by the
* {@link ChannelDecisionManagerImpl}.
* <p>
* If an implementation has an issue with the channel security, they should take action
* themselves. The callers of the implementation do not take any action.
*
* @author Ben Alex
* @deprecated no replacement is planned, though consider using a custom
* {@link RequestMatcher} for any sophisticated decision-making
*/
@Deprecated
public interface ChannelProcessor {
/**
* Decided whether the presented {@link FilterInvocation} provides the appropriate
* level of channel security based on the requested list of <tt>ConfigAttribute</tt>s.
*/
void decide(FilterInvocation invocation, Collection<ConfigAttribute> config) throws IOException, ServletException;
/**
* Indicates whether this <code>ChannelProcessor</code> is able to process the passed
* <code>ConfigAttribute</code>.
* <p>
* This allows the <code>ChannelProcessingFilter</code> to check every configuration
* attribute can be consumed by the configured <code>ChannelDecisionManager</code>.
* @param attribute a configuration attribute that has been configured against the
* <tt>ChannelProcessingFilter</tt>.
* @return true if this <code>ChannelProcessor</code> can support the passed
* configuration attribute
*/
boolean supports(ConfigAttribute attribute);
}
@@ -0,0 +1,98 @@
/*
* Copyright 2004, 2005, 2006 Acegi Technology Pty Limited
*
* 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.web.access.channel;
import java.io.IOException;
import java.util.Collection;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletResponse;
import org.jspecify.annotations.Nullable;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.security.access.ConfigAttribute;
import org.springframework.security.web.FilterInvocation;
import org.springframework.security.web.util.matcher.RequestMatcher;
import org.springframework.util.Assert;
/**
* Ensures channel security is inactive by review of
* <code>HttpServletRequest.isSecure()</code> responses.
* <p>
* The class responds to one case-sensitive keyword, {@link #getInsecureKeyword}. If this
* keyword is detected, <code>HttpServletRequest.isSecure()</code> is used to determine
* the channel security offered. If channel security is present, the configured
* <code>ChannelEntryPoint</code> is called. By default the entry point is
* {@link RetryWithHttpEntryPoint}.
* <p>
* The default <code>insecureKeyword</code> is <code>REQUIRES_INSECURE_CHANNEL</code>.
*
* @author Ben Alex
* @deprecated no replacement is planned, though consider using a custom
* {@link RequestMatcher} for any sophisticated decision-making
*/
@Deprecated
public class InsecureChannelProcessor implements InitializingBean, ChannelProcessor {
private ChannelEntryPoint entryPoint = new RetryWithHttpEntryPoint();
private String insecureKeyword = "REQUIRES_INSECURE_CHANNEL";
@Override
public void afterPropertiesSet() {
Assert.hasLength(this.insecureKeyword, "insecureKeyword required");
Assert.notNull(this.entryPoint, "entryPoint required");
}
@Override
public void decide(FilterInvocation invocation, Collection<ConfigAttribute> config)
throws IOException, ServletException {
Assert.isTrue(invocation != null && config != null, "Nulls cannot be provided");
for (ConfigAttribute attribute : config) {
if (supports(attribute)) {
if (invocation.getHttpRequest().isSecure()) {
@Nullable HttpServletResponse response = invocation.getResponse();
Assert.notNull(response, "HttpServletResponse required");
this.entryPoint.commence(invocation.getRequest(), response);
}
}
}
}
public ChannelEntryPoint getEntryPoint() {
return this.entryPoint;
}
public String getInsecureKeyword() {
return this.insecureKeyword;
}
public void setEntryPoint(ChannelEntryPoint entryPoint) {
this.entryPoint = entryPoint;
}
public void setInsecureKeyword(String secureKeyword) {
this.insecureKeyword = secureKeyword;
}
@Override
public boolean supports(ConfigAttribute attribute) {
return (attribute != null) && (attribute.getAttribute() != null)
&& attribute.getAttribute().equals(getInsecureKeyword());
}
}
@@ -0,0 +1,47 @@
/*
* Copyright 2004, 2005, 2006 Acegi Technology Pty Limited
*
* 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.web.access.channel;
import org.jspecify.annotations.Nullable;
import org.springframework.security.web.PortMapper;
/**
* Commences an insecure channel by retrying the original request using HTTP.
* <p>
* This entry point should suffice in most circumstances. However, it is not intended to
* properly handle HTTP POSTs or other usage where a standard redirect would cause an
* issue.
*
* @author Ben Alex
* @deprecated please use
* {@link org.springframework.security.web.transport.HttpsRedirectFilter} and its
* associated {@link PortMapper}
*/
@Deprecated(since = "6.5")
public class RetryWithHttpEntryPoint extends AbstractRetryEntryPoint {
public RetryWithHttpEntryPoint() {
super("http://", 80);
}
@Override
protected @Nullable Integer getMappedPort(Integer mapFromPort) {
return getPortMapper().lookupHttpPort(mapFromPort);
}
}
@@ -0,0 +1,48 @@
/*
* Copyright 2004, 2005, 2006 Acegi Technology Pty Limited
*
* 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.web.access.channel;
import org.jspecify.annotations.Nullable;
import org.springframework.security.web.PortMapper;
/**
* Commences a secure channel by retrying the original request using HTTPS.
* <p>
* This entry point should suffice in most circumstances. However, it is not intended to
* properly handle HTTP POSTs or other usage where a standard redirect would cause an
* issue.
* </p>
*
* @author Ben Alex
* @deprecated please use
* {@link org.springframework.security.web.transport.HttpsRedirectFilter} and its
* associated {@link PortMapper}
*/
@Deprecated(since = "6.5")
public class RetryWithHttpsEntryPoint extends AbstractRetryEntryPoint {
public RetryWithHttpsEntryPoint() {
super("https://", 443);
}
@Override
protected @Nullable Integer getMappedPort(Integer mapFromPort) {
return getPortMapper().lookupHttpsPort(mapFromPort);
}
}
@@ -0,0 +1,97 @@
/*
* Copyright 2004, 2005, 2006 Acegi Technology Pty Limited
*
* 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.web.access.channel;
import java.io.IOException;
import java.util.Collection;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.security.access.ConfigAttribute;
import org.springframework.security.web.FilterInvocation;
import org.springframework.security.web.util.matcher.RequestMatcher;
import org.springframework.util.Assert;
/**
* Ensures channel security is active by review of
* <code>HttpServletRequest.isSecure()</code> responses.
* <p>
* The class responds to one case-sensitive keyword, {@link #getSecureKeyword}. If this
* keyword is detected, <code>HttpServletRequest.isSecure()</code> is used to determine
* the channel security offered. If channel security is not present, the configured
* <code>ChannelEntryPoint</code> is called. By default the entry point is
* {@link RetryWithHttpsEntryPoint}.
* <p>
* The default <code>secureKeyword</code> is <code>REQUIRES_SECURE_CHANNEL</code>.
*
* @author Ben Alex
* @deprecated no replacement is planned, though consider using a custom
* {@link RequestMatcher} for any sophisticated decision-making
*/
@Deprecated
public class SecureChannelProcessor implements InitializingBean, ChannelProcessor {
private ChannelEntryPoint entryPoint = new RetryWithHttpsEntryPoint();
private String secureKeyword = "REQUIRES_SECURE_CHANNEL";
@Override
public void afterPropertiesSet() {
Assert.hasLength(this.secureKeyword, "secureKeyword required");
Assert.notNull(this.entryPoint, "entryPoint required");
}
@Override
public void decide(FilterInvocation invocation, Collection<ConfigAttribute> config)
throws IOException, ServletException {
Assert.isTrue((invocation != null) && (config != null), "Nulls cannot be provided");
for (ConfigAttribute attribute : config) {
if (supports(attribute)) {
if (!invocation.getHttpRequest().isSecure()) {
HttpServletResponse response = invocation.getResponse();
Assert.notNull(response, "HttpServletResponse is required");
this.entryPoint.commence(invocation.getRequest(), response);
}
}
}
}
public ChannelEntryPoint getEntryPoint() {
return this.entryPoint;
}
public String getSecureKeyword() {
return this.secureKeyword;
}
public void setEntryPoint(ChannelEntryPoint entryPoint) {
this.entryPoint = entryPoint;
}
public void setSecureKeyword(String secureKeyword) {
this.secureKeyword = secureKeyword;
}
@Override
public boolean supports(ConfigAttribute attribute) {
return (attribute != null) && (attribute.getAttribute() != null)
&& attribute.getAttribute().equals(getSecureKeyword());
}
}
@@ -0,0 +1,25 @@
/*
* Copyright 2004-present 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.
*/
/**
* Classes that ensure web requests are received over required transport channels.
* <p>
* Most commonly used to enforce that requests are submitted over HTTP or HTTPS.
*/
@NullMarked
package org.springframework.security.web.access.channel;
import org.jspecify.annotations.NullMarked;
@@ -0,0 +1,95 @@
/*
* Copyright 2004-present 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.web.access.expression;
import org.jspecify.annotations.Nullable;
import org.springframework.security.access.expression.AbstractSecurityExpressionHandler;
import org.springframework.security.access.expression.SecurityExpressionHandler;
import org.springframework.security.access.expression.SecurityExpressionOperations;
import org.springframework.security.authentication.AuthenticationTrustResolver;
import org.springframework.security.authentication.AuthenticationTrustResolverImpl;
import org.springframework.security.authorization.AuthorizationManagerFactory;
import org.springframework.security.core.Authentication;
import org.springframework.security.web.FilterInvocation;
/**
* @author Luke Taylor
* @author Eddú Meléndez
* @author Steve Riesenberg
* @since 3.0
*/
public class DefaultWebSecurityExpressionHandler extends AbstractSecurityExpressionHandler<FilterInvocation>
implements SecurityExpressionHandler<FilterInvocation> {
private static final String DEFAULT_ROLE_PREFIX = "ROLE_";
private String defaultRolePrefix = DEFAULT_ROLE_PREFIX;
@Override
protected SecurityExpressionOperations createSecurityExpressionRoot(@Nullable Authentication authentication,
FilterInvocation fi) {
FilterInvocationExpressionRoot root = new FilterInvocationExpressionRoot(() -> authentication, fi);
root.setAuthorizationManagerFactory(getAuthorizationManagerFactory());
root.setPermissionEvaluator(getPermissionEvaluator());
if (!DEFAULT_ROLE_PREFIX.equals(this.defaultRolePrefix)) {
// Ensure SecurityExpressionRoot can strip the custom role prefix
root.setDefaultRolePrefix(this.defaultRolePrefix);
}
return root;
}
/**
* Sets the {@link AuthenticationTrustResolver} to be used. The default is
* {@link AuthenticationTrustResolverImpl}.
* @param trustResolver the {@link AuthenticationTrustResolver} to use. Cannot be
* null.
* @deprecated Use
* {@link #setAuthorizationManagerFactory(AuthorizationManagerFactory)} instead
*/
@Deprecated(since = "7.0")
public void setTrustResolver(AuthenticationTrustResolver trustResolver) {
getDefaultAuthorizationManagerFactory().setTrustResolver(trustResolver);
}
/**
* <p>
* Sets the default prefix to be added to
* {@link org.springframework.security.access.expression.SecurityExpressionRoot#hasAnyRole(String...)}
* or
* {@link org.springframework.security.access.expression.SecurityExpressionRoot#hasRole(String)}.
* For example, if hasRole("ADMIN") or hasRole("ROLE_ADMIN") is passed in, then the
* role ROLE_ADMIN will be used when the defaultRolePrefix is "ROLE_" (default).
* </p>
*
* <p>
* If null or empty, then no default role prefix is used.
* </p>
* @param defaultRolePrefix the default prefix to add to roles. Default "ROLE_".
* @deprecated Use
* {@link #setAuthorizationManagerFactory(AuthorizationManagerFactory)} instead
*/
@Deprecated(since = "7.0")
public void setDefaultRolePrefix(@Nullable String defaultRolePrefix) {
if (defaultRolePrefix == null) {
defaultRolePrefix = "";
}
getDefaultAuthorizationManagerFactory().setRolePrefix(defaultRolePrefix);
this.defaultRolePrefix = defaultRolePrefix;
}
}
@@ -0,0 +1,117 @@
/*
* Copyright 2004-present 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.web.access.expression;
import java.util.ArrayList;
import java.util.Collection;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.function.BiConsumer;
import jakarta.servlet.http.HttpServletRequest;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.core.log.LogMessage;
import org.springframework.expression.ExpressionParser;
import org.springframework.expression.ParseException;
import org.springframework.security.access.ConfigAttribute;
import org.springframework.security.access.expression.SecurityExpressionHandler;
import org.springframework.security.authorization.AuthorizationManager;
import org.springframework.security.core.annotation.SecurityAnnotationScanner;
import org.springframework.security.web.FilterInvocation;
import org.springframework.security.web.access.intercept.DefaultFilterInvocationSecurityMetadataSource;
import org.springframework.security.web.util.matcher.RequestMatcher;
import org.springframework.util.Assert;
/**
* Expression-based {@code FilterInvocationSecurityMetadataSource}.
*
* @author Luke Taylor
* @author Eddú Meléndez
* @since 3.0
* @deprecated In modern Spring Security APIs, each API manages its own configuration
* context. As such there is no direct replacement for this interface. In the case of
* method security, please see {@link SecurityAnnotationScanner} and
* {@link AuthorizationManager}. In the case of channel security, please see
* {@code HttpsRedirectFilter}. In the case of web security, please see
* {@link AuthorizationManager}.
*/
@Deprecated
public final class ExpressionBasedFilterInvocationSecurityMetadataSource
extends DefaultFilterInvocationSecurityMetadataSource {
private static final Log logger = LogFactory.getLog(ExpressionBasedFilterInvocationSecurityMetadataSource.class);
public ExpressionBasedFilterInvocationSecurityMetadataSource(
LinkedHashMap<RequestMatcher, Collection<ConfigAttribute>> requestMap,
SecurityExpressionHandler<FilterInvocation> expressionHandler) {
super(processMap(requestMap, expressionHandler.getExpressionParser()));
Assert.notNull(expressionHandler, "A non-null SecurityExpressionHandler is required");
}
private static LinkedHashMap<RequestMatcher, Collection<ConfigAttribute>> processMap(
LinkedHashMap<RequestMatcher, Collection<ConfigAttribute>> requestMap, ExpressionParser parser) {
Assert.notNull(parser, "SecurityExpressionHandler returned a null parser object");
LinkedHashMap<RequestMatcher, Collection<ConfigAttribute>> processed = new LinkedHashMap<>(requestMap);
requestMap.forEach((request, value) -> process(parser, request, value, processed::put));
return processed;
}
private static void process(ExpressionParser parser, RequestMatcher request, Collection<ConfigAttribute> value,
BiConsumer<RequestMatcher, Collection<ConfigAttribute>> consumer) {
String expression = getExpression(request, value);
if (logger.isDebugEnabled()) {
logger.debug(LogMessage.format("Adding web access control expression [%s] for %s", expression, request));
}
AbstractVariableEvaluationContextPostProcessor postProcessor = createPostProcessor(request);
ArrayList<ConfigAttribute> processed = new ArrayList<>(1);
try {
processed.add(new WebExpressionConfigAttribute(parser.parseExpression(expression), postProcessor));
}
catch (ParseException ex) {
throw new IllegalArgumentException("Failed to parse expression '" + expression + "'");
}
consumer.accept(request, processed);
}
private static String getExpression(RequestMatcher request, Collection<ConfigAttribute> value) {
Assert.isTrue(value.size() == 1, () -> "Expected a single expression attribute for " + request);
return value.toArray(new ConfigAttribute[1])[0].getAttribute();
}
private static AbstractVariableEvaluationContextPostProcessor createPostProcessor(RequestMatcher request) {
return new RequestVariablesExtractorEvaluationContextPostProcessor(request);
}
static class RequestVariablesExtractorEvaluationContextPostProcessor
extends AbstractVariableEvaluationContextPostProcessor {
private final RequestMatcher matcher;
RequestVariablesExtractorEvaluationContextPostProcessor(RequestMatcher matcher) {
this.matcher = matcher;
}
@Override
Map<String, String> extractVariables(HttpServletRequest request) {
return this.matcher.matcher(request).getVariables();
}
}
}
@@ -0,0 +1,69 @@
/*
* Copyright 2004-present 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.web.access.expression;
import org.jspecify.annotations.NullUnmarked;
import org.springframework.expression.EvaluationContext;
import org.springframework.expression.Expression;
import org.springframework.security.access.ConfigAttribute;
import org.springframework.security.authorization.AuthorizationManager;
import org.springframework.security.web.FilterInvocation;
/**
* Simple expression configuration attribute for use in web request authorizations.
*
* @author Luke Taylor
* @since 3.0
* @deprecated In modern Spring Security APIs, each API manages its own configuration
* context. As such there is no direct replacement for this interface. Please see
* {@link AuthorizationManager}.
*/
@Deprecated
@NullUnmarked
class WebExpressionConfigAttribute implements ConfigAttribute, EvaluationContextPostProcessor<FilterInvocation> {
private final Expression authorizeExpression;
private final EvaluationContextPostProcessor<FilterInvocation> postProcessor;
WebExpressionConfigAttribute(Expression authorizeExpression,
EvaluationContextPostProcessor<FilterInvocation> postProcessor) {
this.authorizeExpression = authorizeExpression;
this.postProcessor = postProcessor;
}
Expression getAuthorizeExpression() {
return this.authorizeExpression;
}
@Override
public EvaluationContext postProcess(EvaluationContext context, FilterInvocation fi) {
return (this.postProcessor != null) ? this.postProcessor.postProcess(context, fi) : context;
}
@Override
public String getAttribute() {
return null;
}
@Override
public String toString() {
return this.authorizeExpression.getExpressionString();
}
}
@@ -0,0 +1,93 @@
/*
* Copyright 2004-present 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.web.access.expression;
import java.util.Collection;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.jspecify.annotations.Nullable;
import org.springframework.expression.EvaluationContext;
import org.springframework.security.access.AccessDecisionVoter;
import org.springframework.security.access.ConfigAttribute;
import org.springframework.security.access.expression.ExpressionUtils;
import org.springframework.security.access.expression.SecurityExpressionHandler;
import org.springframework.security.core.Authentication;
import org.springframework.security.web.FilterInvocation;
import org.springframework.util.Assert;
/**
* Voter which handles web authorisation decisions.
*
* @author Luke Taylor
* @since 3.0
* @deprecated Use {@link WebExpressionAuthorizationManager} instead
*/
@Deprecated
public class WebExpressionVoter implements AccessDecisionVoter<FilterInvocation> {
private final Log logger = LogFactory.getLog(getClass());
private SecurityExpressionHandler<FilterInvocation> expressionHandler = new DefaultWebSecurityExpressionHandler();
@Override
public int vote(Authentication authentication, FilterInvocation filterInvocation,
Collection<ConfigAttribute> attributes) {
Assert.notNull(authentication, "authentication must not be null");
Assert.notNull(filterInvocation, "filterInvocation must not be null");
Assert.notNull(attributes, "attributes must not be null");
WebExpressionConfigAttribute webExpressionConfigAttribute = findConfigAttribute(attributes);
if (webExpressionConfigAttribute == null) {
this.logger
.trace("Abstained since did not find a config attribute of instance WebExpressionConfigAttribute");
return ACCESS_ABSTAIN;
}
EvaluationContext ctx = webExpressionConfigAttribute.postProcess(
this.expressionHandler.createEvaluationContext(authentication, filterInvocation), filterInvocation);
boolean granted = ExpressionUtils.evaluateAsBoolean(webExpressionConfigAttribute.getAuthorizeExpression(), ctx);
if (granted) {
return ACCESS_GRANTED;
}
this.logger.trace("Voted to deny authorization");
return ACCESS_DENIED;
}
private @Nullable WebExpressionConfigAttribute findConfigAttribute(Collection<ConfigAttribute> attributes) {
for (ConfigAttribute attribute : attributes) {
if (attribute instanceof WebExpressionConfigAttribute) {
return (WebExpressionConfigAttribute) attribute;
}
}
return null;
}
@Override
public boolean supports(ConfigAttribute attribute) {
return attribute instanceof WebExpressionConfigAttribute;
}
@Override
public boolean supports(Class<?> clazz) {
return FilterInvocation.class.isAssignableFrom(clazz);
}
public void setExpressionHandler(SecurityExpressionHandler<FilterInvocation> expressionHandler) {
this.expressionHandler = expressionHandler;
}
}
@@ -0,0 +1,121 @@
/*
* Copyright 2004, 2005, 2006 Acegi Technology Pty Limited
*
* 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.web.access.intercept;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Set;
import jakarta.servlet.http.HttpServletRequest;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.core.log.LogMessage;
import org.springframework.security.access.ConfigAttribute;
import org.springframework.security.authorization.AuthorizationManager;
import org.springframework.security.core.annotation.SecurityAnnotationScanner;
import org.springframework.security.web.FilterInvocation;
import org.springframework.security.web.util.matcher.RequestMatcher;
/**
* Default implementation of <tt>FilterInvocationDefinitionSource</tt>.
* <p>
* Stores an ordered map of {@link RequestMatcher}s to <tt>ConfigAttribute</tt>
* collections and provides matching of {@code FilterInvocation}s against the items stored
* in the map.
* <p>
* The order of the {@link RequestMatcher}s in the map is very important. The <b>first</b>
* one which matches the request will be used. Later matchers in the map will not be
* invoked if a match has already been found. Accordingly, the most specific matchers
* should be registered first, with the most general matches registered last.
* <p>
* The most common method creating an instance is using the Spring Security namespace. For
* example, the {@code pattern} and {@code access} attributes of the
* {@code <intercept-url>} elements defined as children of the {@code <http>} element are
* combined to build the instance used by the {@code FilterSecurityInterceptor}.
*
* @author Ben Alex
* @author Luke Taylor
* @deprecated In modern Spring Security APIs, each API manages its own configuration
* context. As such there is no direct replacement for this interface. In the case of
* method security, please see {@link SecurityAnnotationScanner} and
* {@link AuthorizationManager}. In the case of channel security, please see
* {@code HttpsRedirectFilter}. In the case of web security, please see
* {@link AuthorizationManager}.
*/
@Deprecated
public class DefaultFilterInvocationSecurityMetadataSource implements FilterInvocationSecurityMetadataSource {
protected final Log logger = LogFactory.getLog(getClass());
private final Map<RequestMatcher, Collection<ConfigAttribute>> requestMap;
/**
* Sets the internal request map from the supplied map. The key elements should be of
* type {@link RequestMatcher}, which. The path stored in the key will depend on the
* type of the supplied UrlMatcher.
* @param requestMap order-preserving map of request definitions to attribute lists
*/
public DefaultFilterInvocationSecurityMetadataSource(
LinkedHashMap<RequestMatcher, Collection<ConfigAttribute>> requestMap) {
this.requestMap = requestMap;
}
@Override
public Collection<ConfigAttribute> getAllConfigAttributes() {
Set<ConfigAttribute> allAttributes = new HashSet<>();
this.requestMap.values().forEach(allAttributes::addAll);
return allAttributes;
}
@Override
public Collection<ConfigAttribute> getAttributes(Object object) {
final HttpServletRequest request = getHttpServletRequest(object);
int count = 0;
for (Map.Entry<RequestMatcher, Collection<ConfigAttribute>> entry : this.requestMap.entrySet()) {
if (entry.getKey().matches(request)) {
return entry.getValue();
}
else {
if (this.logger.isTraceEnabled()) {
this.logger.trace(LogMessage.format("Did not match request to %s - %s (%d/%d)", entry.getKey(),
entry.getValue(), ++count, this.requestMap.size()));
}
}
}
return Collections.emptyList();
}
@Override
public boolean supports(Class<?> clazz) {
return FilterInvocation.class.isAssignableFrom(clazz);
}
private HttpServletRequest getHttpServletRequest(Object object) {
if (object instanceof FilterInvocation invocation) {
return invocation.getHttpRequest();
}
if (object instanceof HttpServletRequest request) {
return request;
}
throw new IllegalArgumentException("object must be of type FilterInvocation or HttpServletRequest");
}
}
@@ -0,0 +1,39 @@
/*
* Copyright 2004, 2005, 2006 Acegi Technology Pty Limited
*
* 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.web.access.intercept;
import org.springframework.security.access.SecurityMetadataSource;
import org.springframework.security.authorization.AuthorizationManager;
import org.springframework.security.core.annotation.SecurityAnnotationScanner;
import org.springframework.security.web.FilterInvocation;
/**
* Marker interface for <code>SecurityMetadataSource</code> implementations that are
* designed to perform lookups keyed on {@link FilterInvocation}s.
*
* @author Ben Alex
* @deprecated In modern Spring Security APIs, each API manages its own configuration
* context. As such there is no direct replacement for this interface. In the case of
* method security, please see {@link SecurityAnnotationScanner} and
* {@link AuthorizationManager}. In the case of channel security, please see
* {@code HttpsRedirectFilter}. In the case of web security, please see
* {@link AuthorizationManager}.
*/
@Deprecated
public interface FilterInvocationSecurityMetadataSource extends SecurityMetadataSource {
}
@@ -0,0 +1,149 @@
/*
* Copyright 2004, 2005, 2006 Acegi Technology Pty Limited
*
* 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.web.access.intercept;
import java.io.IOException;
import jakarta.servlet.Filter;
import jakarta.servlet.FilterChain;
import jakarta.servlet.FilterConfig;
import jakarta.servlet.ServletException;
import jakarta.servlet.ServletRequest;
import jakarta.servlet.ServletResponse;
import org.jspecify.annotations.Nullable;
import org.springframework.security.access.SecurityMetadataSource;
import org.springframework.security.access.intercept.AbstractSecurityInterceptor;
import org.springframework.security.access.intercept.InterceptorStatusToken;
import org.springframework.security.web.FilterInvocation;
/**
* Performs security handling of HTTP resources via a filter implementation.
* <p>
* The <code>SecurityMetadataSource</code> required by this security interceptor is of
* type {@link FilterInvocationSecurityMetadataSource}.
* <p>
* Refer to {@link AbstractSecurityInterceptor} for details on the workflow.
* </p>
*
* @author Ben Alex
* @author Rob Winch
* @deprecated Use {@link AuthorizationFilter} instead
*/
@Deprecated
public class FilterSecurityInterceptor extends AbstractSecurityInterceptor implements Filter {
private static final String FILTER_APPLIED = "__spring_security_filterSecurityInterceptor_filterApplied";
private @Nullable FilterInvocationSecurityMetadataSource securityMetadataSource;
private boolean observeOncePerRequest = false;
/**
* Not used (we rely on IoC container lifecycle services instead)
* @param arg0 ignored
*
*/
@Override
public void init(FilterConfig arg0) {
}
/**
* Not used (we rely on IoC container lifecycle services instead)
*/
@Override
public void destroy() {
}
/**
* Method that is actually called by the filter chain. Simply delegates to the
* {@link #invoke(FilterInvocation)} method.
* @param request the servlet request
* @param response the servlet response
* @param chain the filter chain
* @throws IOException if the filter chain fails
* @throws ServletException if the filter chain fails
*/
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
invoke(new FilterInvocation(request, response, chain));
}
public @Nullable FilterInvocationSecurityMetadataSource getSecurityMetadataSource() {
return this.securityMetadataSource;
}
@Override
public @Nullable SecurityMetadataSource obtainSecurityMetadataSource() {
return this.securityMetadataSource;
}
public void setSecurityMetadataSource(FilterInvocationSecurityMetadataSource newSource) {
this.securityMetadataSource = newSource;
}
@Override
public Class<?> getSecureObjectClass() {
return FilterInvocation.class;
}
public void invoke(FilterInvocation filterInvocation) throws IOException, ServletException {
if (isApplied(filterInvocation) && this.observeOncePerRequest) {
// filter already applied to this request and user wants us to observe
// once-per-request handling, so don't re-do security checking
filterInvocation.getChain().doFilter(filterInvocation.getRequest(), filterInvocation.getResponse());
return;
}
// first time this request being called, so perform security checking
if (filterInvocation.getRequest() != null && this.observeOncePerRequest) {
filterInvocation.getRequest().setAttribute(FILTER_APPLIED, Boolean.TRUE);
}
InterceptorStatusToken token = super.beforeInvocation(filterInvocation);
try {
filterInvocation.getChain().doFilter(filterInvocation.getRequest(), filterInvocation.getResponse());
}
finally {
super.finallyInvocation(token);
}
super.afterInvocation(token, null);
}
private boolean isApplied(FilterInvocation filterInvocation) {
return (filterInvocation.getRequest() != null)
&& (filterInvocation.getRequest().getAttribute(FILTER_APPLIED) != null);
}
/**
* Indicates whether once-per-request handling will be observed. By default this is
* <code>true</code>, meaning the <code>FilterSecurityInterceptor</code> will only
* execute once-per-request. Sometimes users may wish it to execute more than once per
* request, such as when JSP forwards are being used and filter security is desired on
* each included fragment of the HTTP request.
* @return <code>true</code> (the default) if once-per-request is honoured, otherwise
* <code>false</code> if <code>FilterSecurityInterceptor</code> will enforce
* authorizations for each and every fragment of the HTTP request.
*/
public boolean isObserveOncePerRequest() {
return this.observeOncePerRequest;
}
public void setObserveOncePerRequest(boolean observeOncePerRequest) {
this.observeOncePerRequest = observeOncePerRequest;
}
}
@@ -0,0 +1,125 @@
/*
* Copyright 2004-present 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.web.access;
import org.assertj.core.api.Assertions;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.mockito.ArgumentCaptor;
import org.mockito.ArgumentMatchers;
import org.mockito.BDDMockito;
import org.mockito.Mockito;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.mock.web.MockServletContext;
import org.springframework.security.access.AccessDecisionManager;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.access.intercept.RunAsManager;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.TestingAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.web.FilterInvocation;
import org.springframework.security.web.access.intercept.FilterInvocationSecurityMetadataSource;
import org.springframework.security.web.access.intercept.FilterSecurityInterceptor;
/**
* Tests
* {@link org.springframework.security.web.access.DefaultWebInvocationPrivilegeEvaluator}.
*
* @author Ben Alex
*/
public class DefaultWebInvocationPrivilegeEvaluatorTests {
private AccessDecisionManager adm;
private FilterInvocationSecurityMetadataSource ods;
private RunAsManager ram;
private FilterSecurityInterceptor interceptor;
@BeforeEach
public final void setUp() {
this.interceptor = new FilterSecurityInterceptor();
this.ods = Mockito.mock(FilterInvocationSecurityMetadataSource.class);
this.adm = Mockito.mock(AccessDecisionManager.class);
this.ram = Mockito.mock(RunAsManager.class);
this.interceptor.setAuthenticationManager(Mockito.mock(AuthenticationManager.class));
this.interceptor.setSecurityMetadataSource(this.ods);
this.interceptor.setAccessDecisionManager(this.adm);
this.interceptor.setRunAsManager(this.ram);
this.interceptor.setApplicationEventPublisher(Mockito.mock(ApplicationEventPublisher.class));
SecurityContextHolder.clearContext();
}
@Test
public void permitsAccessIfNoMatchingAttributesAndPublicInvocationsAllowed() {
DefaultWebInvocationPrivilegeEvaluator wipe = new DefaultWebInvocationPrivilegeEvaluator(this.interceptor);
BDDMockito.given(this.ods.getAttributes(ArgumentMatchers.any())).willReturn(null);
Assertions.assertThat(wipe.isAllowed("/context", "/foo/index.jsp", "GET", Mockito.mock(Authentication.class)))
.isTrue();
}
@Test
public void deniesAccessIfNoMatchingAttributesAndPublicInvocationsNotAllowed() {
DefaultWebInvocationPrivilegeEvaluator wipe = new DefaultWebInvocationPrivilegeEvaluator(this.interceptor);
BDDMockito.given(this.ods.getAttributes(ArgumentMatchers.any())).willReturn(null);
this.interceptor.setRejectPublicInvocations(true);
Assertions.assertThat(wipe.isAllowed("/context", "/foo/index.jsp", "GET", Mockito.mock(Authentication.class)))
.isFalse();
}
@Test
public void deniesAccessIfAuthenticationIsNull() {
DefaultWebInvocationPrivilegeEvaluator wipe = new DefaultWebInvocationPrivilegeEvaluator(this.interceptor);
Assertions.assertThat(wipe.isAllowed("/foo/index.jsp", null)).isFalse();
}
@Test
public void allowsAccessIfAccessDecisionManagerDoes() {
Authentication token = new TestingAuthenticationToken("test", "Password", "MOCK_INDEX");
DefaultWebInvocationPrivilegeEvaluator wipe = new DefaultWebInvocationPrivilegeEvaluator(this.interceptor);
Assertions.assertThat(wipe.isAllowed("/foo/index.jsp", token)).isTrue();
}
@SuppressWarnings("unchecked")
@Test
public void deniesAccessIfAccessDecisionManagerDoes() {
Authentication token = new TestingAuthenticationToken("test", "Password", "MOCK_INDEX");
DefaultWebInvocationPrivilegeEvaluator wipe = new DefaultWebInvocationPrivilegeEvaluator(this.interceptor);
BDDMockito.willThrow(new AccessDeniedException(""))
.given(this.adm)
.decide(ArgumentMatchers.any(Authentication.class), ArgumentMatchers.any(), ArgumentMatchers.anyList());
Assertions.assertThat(wipe.isAllowed("/foo/index.jsp", token)).isFalse();
}
@Test
public void isAllowedWhenServletContextIsSetThenPassedFilterInvocationHasServletContext() {
Authentication token = new TestingAuthenticationToken("test", "Password", "MOCK_INDEX");
MockServletContext servletContext = new MockServletContext();
ArgumentCaptor<FilterInvocation> filterInvocationArgumentCaptor = ArgumentCaptor
.forClass(FilterInvocation.class);
DefaultWebInvocationPrivilegeEvaluator wipe = new DefaultWebInvocationPrivilegeEvaluator(this.interceptor);
wipe.setServletContext(servletContext);
wipe.isAllowed("/foo/index.jsp", token);
Mockito.verify(this.adm)
.decide(ArgumentMatchers.eq(token), filterInvocationArgumentCaptor.capture(), ArgumentMatchers.any());
Assertions.assertThat(filterInvocationArgumentCaptor.getValue().getRequest().getServletContext()).isNotNull();
}
}
@@ -0,0 +1,192 @@
/*
* Copyright 2004, 2005, 2006 Acegi Technology Pty Limited
*
* 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.web.access.channel;
import java.io.IOException;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import java.util.Vector;
import jakarta.servlet.FilterChain;
import org.assertj.core.api.Assertions;
import org.junit.jupiter.api.Test;
import org.springframework.mock.web.MockHttpServletRequest;
import org.springframework.mock.web.MockHttpServletResponse;
import org.springframework.security.access.ConfigAttribute;
import org.springframework.security.access.SecurityConfig;
import org.springframework.security.web.FilterInvocation;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
import static org.assertj.core.api.Assertions.fail;
import static org.mockito.Mockito.mock;
/**
* Tests {@link ChannelDecisionManagerImpl}.
*
* @author Ben Alex
*/
@SuppressWarnings("unchecked")
public class ChannelDecisionManagerImplTests {
@Test
public void testCannotSetEmptyChannelProcessorsList() throws Exception {
ChannelDecisionManagerImpl cdm = new ChannelDecisionManagerImpl();
assertThatIllegalArgumentException().isThrownBy(() -> {
cdm.setChannelProcessors(new Vector());
cdm.afterPropertiesSet();
}).withMessage("A list of ChannelProcessors is required");
}
@Test
public void testCannotSetIncorrectObjectTypesIntoChannelProcessorsList() {
ChannelDecisionManagerImpl cdm = new ChannelDecisionManagerImpl();
List list = new Vector();
list.add("THIS IS NOT A CHANNELPROCESSOR");
assertThatIllegalArgumentException().isThrownBy(() -> cdm.setChannelProcessors(list));
}
@Test
public void testCannotSetNullChannelProcessorsList() throws Exception {
ChannelDecisionManagerImpl cdm = new ChannelDecisionManagerImpl();
assertThatIllegalArgumentException().isThrownBy(() -> {
cdm.setChannelProcessors(null);
cdm.afterPropertiesSet();
}).withMessage("A list of ChannelProcessors is required");
}
@Test
public void testDecideIsOperational() throws Exception {
ChannelDecisionManagerImpl cdm = new ChannelDecisionManagerImpl();
MockChannelProcessor cpXyz = new MockChannelProcessor("xyz", false);
MockChannelProcessor cpAbc = new MockChannelProcessor("abc", true);
List list = new Vector();
list.add(cpXyz);
list.add(cpAbc);
cdm.setChannelProcessors(list);
cdm.afterPropertiesSet();
MockHttpServletRequest request = new MockHttpServletRequest();
MockHttpServletResponse response = new MockHttpServletResponse();
FilterInvocation fi = new FilterInvocation(request, response, mock(FilterChain.class));
List<ConfigAttribute> cad = SecurityConfig.createList("xyz");
cdm.decide(fi, cad);
Assertions.assertThat(fi.getResponse().isCommitted()).isTrue();
}
@Test
public void testAnyChannelAttributeCausesProcessorsToBeSkipped() throws Exception {
ChannelDecisionManagerImpl cdm = new ChannelDecisionManagerImpl();
MockChannelProcessor cpAbc = new MockChannelProcessor("abc", true);
List list = new Vector();
list.add(cpAbc);
cdm.setChannelProcessors(list);
cdm.afterPropertiesSet();
MockHttpServletRequest request = new MockHttpServletRequest();
MockHttpServletResponse response = new MockHttpServletResponse();
FilterInvocation fi = new FilterInvocation(request, response, mock(FilterChain.class));
cdm.decide(fi, SecurityConfig.createList(new String[] { "abc", "ANY_CHANNEL" }));
Assertions.assertThat(fi.getResponse().isCommitted()).isFalse();
}
@Test
public void testDecideIteratesAllProcessorsIfNoneCommitAResponse() throws Exception {
ChannelDecisionManagerImpl cdm = new ChannelDecisionManagerImpl();
MockChannelProcessor cpXyz = new MockChannelProcessor("xyz", false);
MockChannelProcessor cpAbc = new MockChannelProcessor("abc", false);
List list = new Vector();
list.add(cpXyz);
list.add(cpAbc);
cdm.setChannelProcessors(list);
cdm.afterPropertiesSet();
MockHttpServletRequest request = new MockHttpServletRequest();
MockHttpServletResponse response = new MockHttpServletResponse();
FilterInvocation fi = new FilterInvocation(request, response, mock(FilterChain.class));
cdm.decide(fi, SecurityConfig.createList("SOME_ATTRIBUTE_NO_PROCESSORS_SUPPORT"));
Assertions.assertThat(fi.getResponse().isCommitted()).isFalse();
}
@Test
public void testDelegatesSupports() throws Exception {
ChannelDecisionManagerImpl cdm = new ChannelDecisionManagerImpl();
MockChannelProcessor cpXyz = new MockChannelProcessor("xyz", false);
MockChannelProcessor cpAbc = new MockChannelProcessor("abc", false);
List list = new Vector();
list.add(cpXyz);
list.add(cpAbc);
cdm.setChannelProcessors(list);
cdm.afterPropertiesSet();
assertThat(cdm.supports(new SecurityConfig("xyz"))).isTrue();
assertThat(cdm.supports(new SecurityConfig("abc"))).isTrue();
assertThat(cdm.supports(new SecurityConfig("UNSUPPORTED"))).isFalse();
}
@Test
public void testGettersSetters() {
ChannelDecisionManagerImpl cdm = new ChannelDecisionManagerImpl();
assertThat(cdm.getChannelProcessors()).isNull();
MockChannelProcessor cpXyz = new MockChannelProcessor("xyz", false);
MockChannelProcessor cpAbc = new MockChannelProcessor("abc", false);
List list = new Vector();
list.add(cpXyz);
list.add(cpAbc);
cdm.setChannelProcessors(list);
assertThat(cdm.getChannelProcessors()).isEqualTo(list);
}
@Test
public void testStartupFailsWithEmptyChannelProcessorsList() throws Exception {
ChannelDecisionManagerImpl cdm = new ChannelDecisionManagerImpl();
assertThatIllegalArgumentException().isThrownBy(cdm::afterPropertiesSet)
.withMessage("A list of ChannelProcessors is required");
}
private class MockChannelProcessor implements ChannelProcessor {
private String configAttribute;
private boolean failIfCalled;
MockChannelProcessor(String configAttribute, boolean failIfCalled) {
this.configAttribute = configAttribute;
this.failIfCalled = failIfCalled;
}
@Override
public void decide(FilterInvocation invocation, Collection<ConfigAttribute> config) throws IOException {
Iterator iter = config.iterator();
if (this.failIfCalled) {
fail("Should not have called this channel processor: " + this.configAttribute);
}
while (iter.hasNext()) {
ConfigAttribute attr = (ConfigAttribute) iter.next();
if (attr.getAttribute().equals(this.configAttribute)) {
invocation.getHttpResponse().sendRedirect("/redirected");
return;
}
}
}
@Override
public boolean supports(ConfigAttribute attribute) {
return attribute.getAttribute().equals(this.configAttribute);
}
}
}
@@ -0,0 +1,191 @@
/*
* Copyright 2004, 2005, 2006 Acegi Technology Pty Limited
*
* 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.web.access.channel;
import java.io.IOException;
import java.util.Collection;
import jakarta.servlet.FilterChain;
import org.junit.jupiter.api.Test;
import org.springframework.mock.web.MockHttpServletRequest;
import org.springframework.mock.web.MockHttpServletResponse;
import org.springframework.security.access.ConfigAttribute;
import org.springframework.security.access.SecurityConfig;
import org.springframework.security.web.FilterInvocation;
import org.springframework.security.web.access.intercept.FilterInvocationSecurityMetadataSource;
import org.springframework.security.web.servlet.TestMockHttpServletRequests;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
import static org.mockito.Mockito.mock;
/**
* Tests {@link ChannelProcessingFilter}.
*
* @author Ben Alex
*/
public class ChannelProcessingFilterTests {
@Test
public void testDetectsMissingChannelDecisionManager() {
ChannelProcessingFilter filter = new ChannelProcessingFilter();
MockFilterInvocationDefinitionMap fids = new MockFilterInvocationDefinitionMap("/path", true, "MOCK");
filter.setSecurityMetadataSource(fids);
assertThatIllegalArgumentException().isThrownBy(filter::afterPropertiesSet);
}
@Test
public void testDetectsMissingFilterInvocationSecurityMetadataSource() {
ChannelProcessingFilter filter = new ChannelProcessingFilter();
filter.setChannelDecisionManager(new MockChannelDecisionManager(false, "MOCK"));
assertThatIllegalArgumentException().isThrownBy(filter::afterPropertiesSet);
}
@Test
public void testDetectsSupportedConfigAttribute() {
ChannelProcessingFilter filter = new ChannelProcessingFilter();
filter.setChannelDecisionManager(new MockChannelDecisionManager(false, "SUPPORTS_MOCK_ONLY"));
MockFilterInvocationDefinitionMap fids = new MockFilterInvocationDefinitionMap("/path", true,
"SUPPORTS_MOCK_ONLY");
filter.setSecurityMetadataSource(fids);
filter.afterPropertiesSet();
}
@Test
public void testDetectsUnsupportedConfigAttribute() {
ChannelProcessingFilter filter = new ChannelProcessingFilter();
filter.setChannelDecisionManager(new MockChannelDecisionManager(false, "SUPPORTS_MOCK_ONLY"));
MockFilterInvocationDefinitionMap fids = new MockFilterInvocationDefinitionMap("/path", true,
"SUPPORTS_MOCK_ONLY", "INVALID_ATTRIBUTE");
filter.setSecurityMetadataSource(fids);
assertThatIllegalArgumentException().isThrownBy(filter::afterPropertiesSet);
}
@Test
public void testDoFilterWhenManagerDoesCommitResponse() throws Exception {
ChannelProcessingFilter filter = new ChannelProcessingFilter();
filter.setChannelDecisionManager(new MockChannelDecisionManager(true, "SOME_ATTRIBUTE"));
MockFilterInvocationDefinitionMap fids = new MockFilterInvocationDefinitionMap("/path", true, "SOME_ATTRIBUTE");
filter.setSecurityMetadataSource(fids);
MockHttpServletRequest request = TestMockHttpServletRequests.get("/path").build();
request.setQueryString("info=now");
MockHttpServletResponse response = new MockHttpServletResponse();
filter.doFilter(request, response, mock(FilterChain.class));
}
@Test
public void testDoFilterWhenManagerDoesNotCommitResponse() throws Exception {
ChannelProcessingFilter filter = new ChannelProcessingFilter();
filter.setChannelDecisionManager(new MockChannelDecisionManager(false, "SOME_ATTRIBUTE"));
MockFilterInvocationDefinitionMap fids = new MockFilterInvocationDefinitionMap("/path", true, "SOME_ATTRIBUTE");
filter.setSecurityMetadataSource(fids);
MockHttpServletRequest request = TestMockHttpServletRequests.get("/path").build();
request.setQueryString("info=now");
MockHttpServletResponse response = new MockHttpServletResponse();
filter.doFilter(request, response, mock(FilterChain.class));
}
@Test
public void testDoFilterWhenNullConfigAttributeReturned() throws Exception {
ChannelProcessingFilter filter = new ChannelProcessingFilter();
filter.setChannelDecisionManager(new MockChannelDecisionManager(false, "NOT_USED"));
MockFilterInvocationDefinitionMap fids = new MockFilterInvocationDefinitionMap("/path", true, "NOT_USED");
filter.setSecurityMetadataSource(fids);
MockHttpServletRequest request = TestMockHttpServletRequests.get("/PATH_NOT_MATCHING_CONFIG_ATTRIBUTE").build();
request.setQueryString("info=now");
MockHttpServletResponse response = new MockHttpServletResponse();
filter.doFilter(request, response, mock(FilterChain.class));
}
@Test
public void testGetterSetters() {
ChannelProcessingFilter filter = new ChannelProcessingFilter();
filter.setChannelDecisionManager(new MockChannelDecisionManager(false, "MOCK"));
assertThat(filter.getChannelDecisionManager() != null).isTrue();
MockFilterInvocationDefinitionMap fids = new MockFilterInvocationDefinitionMap("/path", false, "MOCK");
filter.setSecurityMetadataSource(fids);
assertThat(filter.getSecurityMetadataSource()).isSameAs(fids);
filter.afterPropertiesSet();
}
private class MockChannelDecisionManager implements ChannelDecisionManager {
private String supportAttribute;
private boolean commitAResponse;
MockChannelDecisionManager(boolean commitAResponse, String supportAttribute) {
this.commitAResponse = commitAResponse;
this.supportAttribute = supportAttribute;
}
@Override
public void decide(FilterInvocation invocation, Collection<ConfigAttribute> config) throws IOException {
if (this.commitAResponse) {
invocation.getHttpResponse().sendRedirect("/redirected");
}
}
@Override
public boolean supports(ConfigAttribute attribute) {
return attribute.getAttribute().equals(this.supportAttribute);
}
}
private class MockFilterInvocationDefinitionMap implements FilterInvocationSecurityMetadataSource {
private Collection<ConfigAttribute> toReturn;
private String servletPath;
private boolean provideIterator;
MockFilterInvocationDefinitionMap(String servletPath, boolean provideIterator, String... toReturn) {
this.servletPath = servletPath;
this.toReturn = SecurityConfig.createList(toReturn);
this.provideIterator = provideIterator;
}
@Override
public Collection<ConfigAttribute> getAttributes(Object object) throws IllegalArgumentException {
FilterInvocation fi = (FilterInvocation) object;
if (this.servletPath.equals(fi.getHttpRequest().getServletPath())) {
return this.toReturn;
}
else {
return null;
}
}
@Override
public Collection<ConfigAttribute> getAllConfigAttributes() {
if (!this.provideIterator) {
return null;
}
return this.toReturn;
}
@Override
public boolean supports(Class<?> clazz) {
return true;
}
}
}
@@ -0,0 +1,112 @@
/*
* Copyright 2004, 2005, 2006 Acegi Technology Pty Limited
*
* 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.web.access.channel;
import jakarta.servlet.FilterChain;
import org.assertj.core.api.Assertions;
import org.junit.jupiter.api.Test;
import org.springframework.mock.web.MockHttpServletRequest;
import org.springframework.mock.web.MockHttpServletResponse;
import org.springframework.security.access.SecurityConfig;
import org.springframework.security.web.FilterInvocation;
import org.springframework.security.web.servlet.TestMockHttpServletRequests;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
import static org.mockito.Mockito.mock;
/**
* Tests {@link InsecureChannelProcessor}.
*
* @author Ben Alex
*/
public class InsecureChannelProcessorTests {
@Test
public void testDecideDetectsAcceptableChannel() throws Exception {
MockHttpServletRequest request = TestMockHttpServletRequests.get("http://localhost:8080")
.requestUri("/bigapp", "/servlet", null)
.queryString("info=true")
.build();
MockHttpServletResponse response = new MockHttpServletResponse();
FilterInvocation fi = new FilterInvocation(request, response, mock(FilterChain.class));
InsecureChannelProcessor processor = new InsecureChannelProcessor();
processor.decide(fi, SecurityConfig.createList("SOME_IGNORED_ATTRIBUTE", "REQUIRES_INSECURE_CHANNEL"));
Assertions.assertThat(fi.getResponse().isCommitted()).isFalse();
}
@Test
public void testDecideDetectsUnacceptableChannel() throws Exception {
MockHttpServletRequest request = TestMockHttpServletRequests.get("https://localhost:8443")
.requestUri("/bigapp", "/servlet", null)
.queryString("info=true")
.build();
MockHttpServletResponse response = new MockHttpServletResponse();
FilterInvocation fi = new FilterInvocation(request, response, mock(FilterChain.class));
InsecureChannelProcessor processor = new InsecureChannelProcessor();
processor.decide(fi,
SecurityConfig.createList(new String[] { "SOME_IGNORED_ATTRIBUTE", "REQUIRES_INSECURE_CHANNEL" }));
Assertions.assertThat(fi.getResponse().isCommitted()).isTrue();
}
@Test
public void testDecideRejectsNulls() throws Exception {
InsecureChannelProcessor processor = new InsecureChannelProcessor();
processor.afterPropertiesSet();
assertThatIllegalArgumentException().isThrownBy(() -> processor.decide(null, null));
}
@Test
public void testGettersSetters() {
InsecureChannelProcessor processor = new InsecureChannelProcessor();
assertThat(processor.getInsecureKeyword()).isEqualTo("REQUIRES_INSECURE_CHANNEL");
processor.setInsecureKeyword("X");
assertThat(processor.getInsecureKeyword()).isEqualTo("X");
assertThat(processor.getEntryPoint() != null).isTrue();
processor.setEntryPoint(null);
assertThat(processor.getEntryPoint() == null).isTrue();
}
@Test
public void testMissingEntryPoint() throws Exception {
InsecureChannelProcessor processor = new InsecureChannelProcessor();
processor.setEntryPoint(null);
assertThatIllegalArgumentException().isThrownBy(processor::afterPropertiesSet)
.withMessage("entryPoint required");
}
@Test
public void testMissingSecureChannelKeyword() throws Exception {
InsecureChannelProcessor processor = new InsecureChannelProcessor();
processor.setInsecureKeyword(null);
assertThatIllegalArgumentException().isThrownBy(processor::afterPropertiesSet)
.withMessage("insecureKeyword required");
processor.setInsecureKeyword("");
assertThatIllegalArgumentException().isThrownBy(processor::afterPropertiesSet)
.withMessage("insecureKeyword required");
}
@Test
public void testSupports() {
InsecureChannelProcessor processor = new InsecureChannelProcessor();
assertThat(processor.supports(new SecurityConfig("REQUIRES_INSECURE_CHANNEL"))).isTrue();
assertThat(processor.supports(null)).isFalse();
assertThat(processor.supports(new SecurityConfig("NOT_SUPPORTED"))).isFalse();
}
}
@@ -0,0 +1,118 @@
/*
* Copyright 2004, 2005, 2006 Acegi Technology Pty Limited
*
* 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.web.access.channel;
import java.util.HashMap;
import java.util.Map;
import org.junit.jupiter.api.Test;
import org.springframework.mock.web.MockHttpServletRequest;
import org.springframework.mock.web.MockHttpServletResponse;
import org.springframework.security.web.PortMapper;
import org.springframework.security.web.PortMapperImpl;
import org.springframework.security.web.RedirectStrategy;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
import static org.mockito.Mockito.mock;
/**
* Tests {@link RetryWithHttpEntryPoint}.
*
* @author Ben Alex
*/
public class RetryWithHttpEntryPointTests {
@Test
public void testDetectsMissingPortMapper() {
RetryWithHttpEntryPoint ep = new RetryWithHttpEntryPoint();
assertThatIllegalArgumentException().isThrownBy(() -> ep.setPortMapper(null));
}
@Test
public void testGettersSetters() {
RetryWithHttpEntryPoint ep = new RetryWithHttpEntryPoint();
PortMapper portMapper = mock(PortMapper.class);
RedirectStrategy redirector = mock(RedirectStrategy.class);
ep.setPortMapper(portMapper);
ep.setRedirectStrategy(redirector);
assertThat(ep.getPortMapper()).isSameAs(portMapper);
assertThat(ep.getRedirectStrategy()).isSameAs(redirector);
}
@Test
public void testNormalOperation() throws Exception {
MockHttpServletRequest request = new MockHttpServletRequest("GET", "/bigWebApp/hello/pathInfo.html");
request.setQueryString("open=true");
request.setScheme("https");
request.setServerName("localhost");
request.setServerPort(443);
MockHttpServletResponse response = new MockHttpServletResponse();
RetryWithHttpEntryPoint ep = new RetryWithHttpEntryPoint();
ep.setPortMapper(new PortMapperImpl());
ep.commence(request, response);
assertThat(response.getRedirectedUrl()).isEqualTo("http://localhost/bigWebApp/hello/pathInfo.html?open=true");
}
@Test
public void testNormalOperationWithNullQueryString() throws Exception {
MockHttpServletRequest request = new MockHttpServletRequest("GET", "/bigWebApp/hello");
request.setScheme("https");
request.setServerName("localhost");
request.setServerPort(443);
MockHttpServletResponse response = new MockHttpServletResponse();
RetryWithHttpEntryPoint ep = new RetryWithHttpEntryPoint();
ep.setPortMapper(new PortMapperImpl());
ep.commence(request, response);
assertThat(response.getRedirectedUrl()).isEqualTo("http://localhost/bigWebApp/hello");
}
@Test
public void testOperationWhenTargetPortIsUnknown() throws Exception {
MockHttpServletRequest request = new MockHttpServletRequest("GET", "/bigWebApp");
request.setQueryString("open=true");
request.setScheme("https");
request.setServerName("www.example.com");
request.setServerPort(8768);
MockHttpServletResponse response = new MockHttpServletResponse();
RetryWithHttpEntryPoint ep = new RetryWithHttpEntryPoint();
ep.setPortMapper(new PortMapperImpl());
ep.commence(request, response);
assertThat(response.getRedirectedUrl()).isEqualTo("/bigWebApp?open=true");
}
@Test
public void testOperationWithNonStandardPort() throws Exception {
MockHttpServletRequest request = new MockHttpServletRequest("GET", "/bigWebApp/hello/pathInfo.html");
request.setQueryString("open=true");
request.setScheme("https");
request.setServerName("localhost");
request.setServerPort(9999);
MockHttpServletResponse response = new MockHttpServletResponse();
PortMapperImpl portMapper = new PortMapperImpl();
Map<String, String> map = new HashMap<>();
map.put("8888", "9999");
portMapper.setPortMappings(map);
RetryWithHttpEntryPoint ep = new RetryWithHttpEntryPoint();
ep.setPortMapper(portMapper);
ep.commence(request, response);
assertThat(response.getRedirectedUrl())
.isEqualTo("http://localhost:8888/bigWebApp/hello/pathInfo.html?open=true");
}
}
@@ -0,0 +1,112 @@
/*
* Copyright 2004, 2005, 2006 Acegi Technology Pty Limited
*
* 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.web.access.channel;
import java.util.HashMap;
import java.util.Map;
import org.junit.jupiter.api.Test;
import org.springframework.mock.web.MockHttpServletRequest;
import org.springframework.mock.web.MockHttpServletResponse;
import org.springframework.security.web.PortMapperImpl;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
/**
* Tests {@link RetryWithHttpsEntryPoint}.
*
* @author Ben Alex
*/
public class RetryWithHttpsEntryPointTests {
@Test
public void testDetectsMissingPortMapper() {
RetryWithHttpsEntryPoint ep = new RetryWithHttpsEntryPoint();
assertThatIllegalArgumentException().isThrownBy(() -> ep.setPortMapper(null));
}
@Test
public void testGettersSetters() {
RetryWithHttpsEntryPoint ep = new RetryWithHttpsEntryPoint();
ep.setPortMapper(new PortMapperImpl());
assertThat(ep.getPortMapper() != null).isTrue();
}
@Test
public void testNormalOperation() throws Exception {
MockHttpServletRequest request = new MockHttpServletRequest("GET", "/bigWebApp/hello/pathInfo.html");
request.setQueryString("open=true");
request.setScheme("http");
request.setServerName("www.example.com");
request.setServerPort(80);
MockHttpServletResponse response = new MockHttpServletResponse();
RetryWithHttpsEntryPoint ep = new RetryWithHttpsEntryPoint();
ep.setPortMapper(new PortMapperImpl());
ep.commence(request, response);
assertThat(response.getRedirectedUrl())
.isEqualTo("https://www.example.com/bigWebApp/hello/pathInfo.html?open=true");
}
@Test
public void testNormalOperationWithNullQueryString() throws Exception {
MockHttpServletRequest request = new MockHttpServletRequest("GET", "/bigWebApp/hello");
request.setScheme("http");
request.setServerName("www.example.com");
request.setServerPort(80);
MockHttpServletResponse response = new MockHttpServletResponse();
RetryWithHttpsEntryPoint ep = new RetryWithHttpsEntryPoint();
ep.setPortMapper(new PortMapperImpl());
ep.commence(request, response);
assertThat(response.getRedirectedUrl()).isEqualTo("https://www.example.com/bigWebApp/hello");
}
@Test
public void testOperationWhenTargetPortIsUnknown() throws Exception {
MockHttpServletRequest request = new MockHttpServletRequest("GET", "/bigWebApp");
request.setQueryString("open=true");
request.setScheme("http");
request.setServerName("www.example.com");
request.setServerPort(8768);
MockHttpServletResponse response = new MockHttpServletResponse();
RetryWithHttpsEntryPoint ep = new RetryWithHttpsEntryPoint();
ep.setPortMapper(new PortMapperImpl());
ep.commence(request, response);
assertThat(response.getRedirectedUrl()).isEqualTo("/bigWebApp?open=true");
}
@Test
public void testOperationWithNonStandardPort() throws Exception {
MockHttpServletRequest request = new MockHttpServletRequest("GET", "/bigWebApp/hello/pathInfo.html");
request.setQueryString("open=true");
request.setScheme("http");
request.setServerName("www.example.com");
request.setServerPort(8888);
MockHttpServletResponse response = new MockHttpServletResponse();
PortMapperImpl portMapper = new PortMapperImpl();
Map<String, String> map = new HashMap<>();
map.put("8888", "9999");
portMapper.setPortMappings(map);
RetryWithHttpsEntryPoint ep = new RetryWithHttpsEntryPoint();
ep.setPortMapper(portMapper);
ep.commence(request, response);
assertThat(response.getRedirectedUrl())
.isEqualTo("https://www.example.com:9999/bigWebApp/hello/pathInfo.html?open=true");
}
}
@@ -0,0 +1,112 @@
/*
* Copyright 2004, 2005, 2006 Acegi Technology Pty Limited
*
* 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.web.access.channel;
import jakarta.servlet.FilterChain;
import org.assertj.core.api.Assertions;
import org.junit.jupiter.api.Test;
import org.springframework.mock.web.MockHttpServletRequest;
import org.springframework.mock.web.MockHttpServletResponse;
import org.springframework.security.access.SecurityConfig;
import org.springframework.security.web.FilterInvocation;
import org.springframework.security.web.servlet.TestMockHttpServletRequests;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
import static org.mockito.Mockito.mock;
/**
* Tests {@link SecureChannelProcessor}.
*
* @author Ben Alex
*/
public class SecureChannelProcessorTests {
@Test
public void testDecideDetectsAcceptableChannel() throws Exception {
MockHttpServletRequest request = TestMockHttpServletRequests.get("https://localhost:8443")
.requestUri("/bigapp", "/servlet", null)
.queryString("info=true")
.build();
MockHttpServletResponse response = new MockHttpServletResponse();
FilterInvocation fi = new FilterInvocation(request, response, mock(FilterChain.class));
SecureChannelProcessor processor = new SecureChannelProcessor();
processor.decide(fi, SecurityConfig.createList("SOME_IGNORED_ATTRIBUTE", "REQUIRES_SECURE_CHANNEL"));
Assertions.assertThat(fi.getResponse().isCommitted()).isFalse();
}
@Test
public void testDecideDetectsUnacceptableChannel() throws Exception {
MockHttpServletRequest request = TestMockHttpServletRequests.get("http://localhost:8080")
.requestUri("/bigapp", "/servlet", null)
.queryString("info=true")
.build();
MockHttpServletResponse response = new MockHttpServletResponse();
FilterInvocation fi = new FilterInvocation(request, response, mock(FilterChain.class));
SecureChannelProcessor processor = new SecureChannelProcessor();
processor.decide(fi,
SecurityConfig.createList(new String[] { "SOME_IGNORED_ATTRIBUTE", "REQUIRES_SECURE_CHANNEL" }));
Assertions.assertThat(fi.getResponse().isCommitted()).isTrue();
}
@Test
public void testDecideRejectsNulls() throws Exception {
SecureChannelProcessor processor = new SecureChannelProcessor();
processor.afterPropertiesSet();
assertThatIllegalArgumentException().isThrownBy(() -> processor.decide(null, null));
}
@Test
public void testGettersSetters() {
SecureChannelProcessor processor = new SecureChannelProcessor();
assertThat(processor.getSecureKeyword()).isEqualTo("REQUIRES_SECURE_CHANNEL");
processor.setSecureKeyword("X");
assertThat(processor.getSecureKeyword()).isEqualTo("X");
assertThat(processor.getEntryPoint() != null).isTrue();
processor.setEntryPoint(null);
assertThat(processor.getEntryPoint() == null).isTrue();
}
@Test
public void testMissingEntryPoint() throws Exception {
SecureChannelProcessor processor = new SecureChannelProcessor();
processor.setEntryPoint(null);
assertThatIllegalArgumentException().isThrownBy(processor::afterPropertiesSet)
.withMessage("entryPoint required");
}
@Test
public void testMissingSecureChannelKeyword() throws Exception {
SecureChannelProcessor processor = new SecureChannelProcessor();
processor.setSecureKeyword(null);
assertThatIllegalArgumentException().isThrownBy(processor::afterPropertiesSet)
.withMessage("secureKeyword required");
processor.setSecureKeyword("");
assertThatIllegalArgumentException().isThrownBy(() -> processor.afterPropertiesSet())
.withMessage("secureKeyword required");
}
@Test
public void testSupports() {
SecureChannelProcessor processor = new SecureChannelProcessor();
assertThat(processor.supports(new SecurityConfig("REQUIRES_SECURE_CHANNEL"))).isTrue();
assertThat(processor.supports(null)).isFalse();
assertThat(processor.supports(new SecurityConfig("NOT_SUPPORTED"))).isFalse();
}
}
@@ -0,0 +1,94 @@
/*
* Copyright 2004-present 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.web.access.expression;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import org.springframework.beans.factory.support.RootBeanDefinition;
import org.springframework.context.support.StaticApplicationContext;
import org.springframework.expression.EvaluationContext;
import org.springframework.expression.Expression;
import org.springframework.expression.ExpressionParser;
import org.springframework.security.access.SecurityConfig;
import org.springframework.security.authentication.AuthenticationTrustResolver;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.web.FilterInvocation;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
@ExtendWith(MockitoExtension.class)
public class DefaultWebSecurityExpressionHandlerTests {
@Mock
private AuthenticationTrustResolver trustResolver;
@Mock
private Authentication authentication;
@Mock
private FilterInvocation invocation;
private DefaultWebSecurityExpressionHandler handler;
@BeforeEach
public void setup() {
this.handler = new DefaultWebSecurityExpressionHandler();
}
@AfterEach
public void cleanup() {
SecurityContextHolder.clearContext();
}
@Test
public void expressionPropertiesAreResolvedAgainstAppContextBeans() {
StaticApplicationContext appContext = new StaticApplicationContext();
RootBeanDefinition bean = new RootBeanDefinition(SecurityConfig.class);
bean.getConstructorArgumentValues().addGenericArgumentValue("ROLE_A");
appContext.registerBeanDefinition("role", bean);
this.handler.setApplicationContext(appContext);
EvaluationContext ctx = this.handler.createEvaluationContext(mock(Authentication.class),
mock(FilterInvocation.class));
ExpressionParser parser = this.handler.getExpressionParser();
assertThat(parser.parseExpression("@role.getAttribute() == 'ROLE_A'").getValue(ctx, Boolean.class)).isTrue();
assertThat(parser.parseExpression("@role.attribute == 'ROLE_A'").getValue(ctx, Boolean.class)).isTrue();
}
@Test
public void setTrustResolverNull() {
assertThatIllegalArgumentException().isThrownBy(() -> this.handler.setTrustResolver(null));
}
@Test
public void createEvaluationContextCustomTrustResolver() {
this.handler.setTrustResolver(this.trustResolver);
Expression expression = this.handler.getExpressionParser().parseExpression("anonymous");
EvaluationContext context = this.handler.createEvaluationContext(this.authentication, this.invocation);
assertThat(expression.getValue(context, Boolean.class)).isFalse();
verify(this.trustResolver).isAnonymous(this.authentication);
}
}
@@ -0,0 +1,63 @@
/*
* Copyright 2004-present 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.web.access.expression;
import java.util.Collection;
import java.util.LinkedHashMap;
import org.junit.jupiter.api.Test;
import org.springframework.security.access.ConfigAttribute;
import org.springframework.security.access.SecurityConfig;
import org.springframework.security.web.FilterInvocation;
import org.springframework.security.web.util.matcher.AnyRequestMatcher;
import org.springframework.security.web.util.matcher.RequestMatcher;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
/**
* @author Luke Taylor
*/
public class ExpressionBasedFilterInvocationSecurityMetadataSourceTests {
@Test
public void expectedAttributeIsReturned() {
final String expression = "hasRole('X')";
LinkedHashMap<RequestMatcher, Collection<ConfigAttribute>> requestMap = new LinkedHashMap<>();
requestMap.put(AnyRequestMatcher.INSTANCE, SecurityConfig.createList(expression));
ExpressionBasedFilterInvocationSecurityMetadataSource mds = new ExpressionBasedFilterInvocationSecurityMetadataSource(
requestMap, new DefaultWebSecurityExpressionHandler());
assertThat(mds.getAllConfigAttributes()).hasSize(1);
Collection<ConfigAttribute> attrs = mds.getAttributes(new FilterInvocation("/path", "GET"));
assertThat(attrs).hasSize(1);
WebExpressionConfigAttribute attribute = (WebExpressionConfigAttribute) attrs.toArray()[0];
assertThat(attribute.getAttribute()).isNull();
assertThat(attribute.getAuthorizeExpression().getExpressionString()).isEqualTo(expression);
assertThat(attribute.toString()).isEqualTo(expression);
}
@Test
public void invalidExpressionIsRejected() {
LinkedHashMap<RequestMatcher, Collection<ConfigAttribute>> requestMap = new LinkedHashMap<>();
requestMap.put(AnyRequestMatcher.INSTANCE, SecurityConfig.createList("hasRole('X'"));
assertThatIllegalArgumentException()
.isThrownBy(() -> new ExpressionBasedFilterInvocationSecurityMetadataSource(requestMap,
new DefaultWebSecurityExpressionHandler()));
}
}
@@ -0,0 +1,116 @@
/*
* Copyright 2004-present 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.web.access.expression;
import java.util.ArrayList;
import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletRequest;
import jakarta.servlet.ServletResponse;
import org.aopalliance.intercept.MethodInvocation;
import org.junit.jupiter.api.Test;
import org.springframework.expression.EvaluationContext;
import org.springframework.expression.Expression;
import org.springframework.security.access.AccessDecisionVoter;
import org.springframework.security.access.SecurityConfig;
import org.springframework.security.access.expression.SecurityExpressionHandler;
import org.springframework.security.authentication.TestingAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.web.FilterInvocation;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.BDDMockito.given;
import static org.mockito.Mockito.mock;
/**
* @author Luke Taylor
*/
@SuppressWarnings({ "unchecked" })
public class WebExpressionVoterTests {
private Authentication user = new TestingAuthenticationToken("user", "pass", "X");
@Test
public void supportsWebConfigAttributeAndFilterInvocation() {
WebExpressionVoter voter = new WebExpressionVoter();
assertThat(voter.supports(
new WebExpressionConfigAttribute(mock(Expression.class), mock(EvaluationContextPostProcessor.class))))
.isTrue();
assertThat(voter.supports(FilterInvocation.class)).isTrue();
assertThat(voter.supports(MethodInvocation.class)).isFalse();
}
@Test
public void abstainsIfNoAttributeFound() {
WebExpressionVoter voter = new WebExpressionVoter();
assertThat(
voter.vote(this.user, new FilterInvocation("/path", "GET"), SecurityConfig.createList("A", "B", "C")))
.isEqualTo(AccessDecisionVoter.ACCESS_ABSTAIN);
}
@Test
public void grantsAccessIfExpressionIsTrueDeniesIfFalse() {
WebExpressionVoter voter = new WebExpressionVoter();
Expression ex = mock(Expression.class);
EvaluationContextPostProcessor postProcessor = mock(EvaluationContextPostProcessor.class);
given(postProcessor.postProcess(any(EvaluationContext.class), any(FilterInvocation.class)))
.willAnswer((invocation) -> invocation.getArgument(0));
WebExpressionConfigAttribute weca = new WebExpressionConfigAttribute(ex, postProcessor);
EvaluationContext ctx = mock(EvaluationContext.class);
SecurityExpressionHandler eh = mock(SecurityExpressionHandler.class);
FilterInvocation fi = new FilterInvocation("/path", "GET");
voter.setExpressionHandler(eh);
given(eh.createEvaluationContext(this.user, fi)).willReturn(ctx);
given(ex.getValue(ctx, Boolean.class)).willReturn(Boolean.TRUE, Boolean.FALSE);
ArrayList attributes = new ArrayList();
attributes.addAll(SecurityConfig.createList("A", "B", "C"));
attributes.add(weca);
assertThat(voter.vote(this.user, fi, attributes)).isEqualTo(AccessDecisionVoter.ACCESS_GRANTED);
// Second time false
assertThat(voter.vote(this.user, fi, attributes)).isEqualTo(AccessDecisionVoter.ACCESS_DENIED);
}
// SEC-2507
@Test
public void supportFilterInvocationSubClass() {
WebExpressionVoter voter = new WebExpressionVoter();
assertThat(voter.supports(FilterInvocationChild.class)).isTrue();
}
@Test
public void supportFilterInvocation() {
WebExpressionVoter voter = new WebExpressionVoter();
assertThat(voter.supports(FilterInvocation.class)).isTrue();
}
@Test
public void supportsObjectIsFalse() {
WebExpressionVoter voter = new WebExpressionVoter();
assertThat(voter.supports(Object.class)).isFalse();
}
private static class FilterInvocationChild extends FilterInvocation {
FilterInvocationChild(ServletRequest request, ServletResponse response, FilterChain chain) {
super(request, response, chain);
}
}
}
@@ -0,0 +1,152 @@
/*
* Copyright 2004, 2005, 2006 Acegi Technology Pty Limited
*
* 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.web.access.intercept;
import java.util.Collection;
import java.util.LinkedHashMap;
import jakarta.servlet.FilterChain;
import org.junit.jupiter.api.Test;
import org.springframework.http.HttpMethod;
import org.springframework.mock.web.MockHttpServletRequest;
import org.springframework.mock.web.MockHttpServletResponse;
import org.springframework.security.access.ConfigAttribute;
import org.springframework.security.access.SecurityConfig;
import org.springframework.security.web.FilterInvocation;
import org.springframework.security.web.servlet.TestMockHttpServletRequests;
import org.springframework.security.web.servlet.util.matcher.PathPatternRequestMatcher;
import org.springframework.security.web.util.matcher.RequestMatcher;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.mock;
/**
* Tests {@link DefaultFilterInvocationSecurityMetadataSource}.
*
* @author Ben Alex
*/
public class DefaultFilterInvocationSecurityMetadataSourceTests {
private DefaultFilterInvocationSecurityMetadataSource fids;
private Collection<ConfigAttribute> def = SecurityConfig.createList("ROLE_ONE");
private void createFids(String pattern, HttpMethod method) {
LinkedHashMap<RequestMatcher, Collection<ConfigAttribute>> requestMap = new LinkedHashMap<>();
requestMap.put(PathPatternRequestMatcher.pathPattern(method, pattern), this.def);
this.fids = new DefaultFilterInvocationSecurityMetadataSource(requestMap);
}
@Test
public void lookupNotRequiringExactMatchSucceedsIfNotMatching() {
createFids("/secure/super/**", null);
FilterInvocation fi = createFilterInvocation("/secure/super/somefile.html", null, null, "GET");
assertThat(this.fids.getAttributes(fi)).isEqualTo(this.def);
}
/**
* SEC-501. Note that as of 2.0, lower case comparisons are the default for this
* class.
*/
@Test
public void lookupNotRequiringExactMatchSucceedsIfSecureUrlPathContainsUpperCase() {
createFids("/secure/super/**", null);
FilterInvocation fi = createFilterInvocation("/secure", "/super/somefile.html", null, "GET");
Collection<ConfigAttribute> response = this.fids.getAttributes(fi);
assertThat(response).isEqualTo(this.def);
}
@Test
public void lookupRequiringExactMatchIsSuccessful() {
createFids("/SeCurE/super/**", null);
FilterInvocation fi = createFilterInvocation("/SeCurE/super/somefile.html", null, null, "GET");
Collection<ConfigAttribute> response = this.fids.getAttributes(fi);
assertThat(response).isEqualTo(this.def);
}
@Test
public void lookupRequiringExactMatchWithAdditionalSlashesIsSuccessful() {
createFids("/someAdminPage.html**", null);
FilterInvocation fi = createFilterInvocation("/someAdminPage.html", null, "a=/test", "GET");
Collection<ConfigAttribute> response = this.fids.getAttributes(fi);
assertThat(response); // see SEC-161 (it should truncate after ?
// sign).isEqualTo(def)
}
@Test
public void httpMethodLookupSucceeds() {
createFids("/somepage**", HttpMethod.GET);
FilterInvocation fi = createFilterInvocation("/somepage", null, null, "GET");
Collection<ConfigAttribute> attrs = this.fids.getAttributes(fi);
assertThat(attrs).isEqualTo(this.def);
}
@Test
public void generalMatchIsUsedIfNoMethodSpecificMatchExists() {
createFids("/somepage**", null);
FilterInvocation fi = createFilterInvocation("/somepage", null, null, "GET");
Collection<ConfigAttribute> attrs = this.fids.getAttributes(fi);
assertThat(attrs).isEqualTo(this.def);
}
@Test
public void requestWithDifferentHttpMethodDoesntMatch() {
createFids("/somepage**", HttpMethod.GET);
FilterInvocation fi = createFilterInvocation("/somepage", null, null, "POST");
Collection<ConfigAttribute> attrs = this.fids.getAttributes(fi);
assertThat(attrs).isEmpty();
}
// SEC-1236
@Test
public void mixingPatternsWithAndWithoutHttpMethodsIsSupported() {
LinkedHashMap<RequestMatcher, Collection<ConfigAttribute>> requestMap = new LinkedHashMap<>();
Collection<ConfigAttribute> userAttrs = SecurityConfig.createList("A");
requestMap.put(PathPatternRequestMatcher.pathPattern("/user/**"), userAttrs);
requestMap.put(PathPatternRequestMatcher.pathPattern(HttpMethod.GET, "/teller/**"),
SecurityConfig.createList("B"));
this.fids = new DefaultFilterInvocationSecurityMetadataSource(requestMap);
FilterInvocation fi = createFilterInvocation("/user", null, null, "GET");
Collection<ConfigAttribute> attrs = this.fids.getAttributes(fi);
assertThat(attrs).isEqualTo(userAttrs);
}
/**
* Check fixes for SEC-321
*/
@Test
public void extraQuestionMarkStillMatches() {
createFids("/someAdminPage.html*", null);
FilterInvocation fi = createFilterInvocation("/someAdminPage.html", null, null, "GET");
Collection<ConfigAttribute> response = this.fids.getAttributes(fi);
assertThat(response).isEqualTo(this.def);
fi = createFilterInvocation("/someAdminPage.html", null, "?", "GET");
response = this.fids.getAttributes(fi);
assertThat(response).isEqualTo(this.def);
}
private FilterInvocation createFilterInvocation(String servletPath, String pathInfo, String queryString,
String method) {
MockHttpServletRequest request = TestMockHttpServletRequests.request(method)
.requestUri(null, servletPath, pathInfo)
.queryString(queryString)
.build();
return new FilterInvocation(request, new MockHttpServletResponse(), mock(FilterChain.class));
}
}
@@ -0,0 +1,198 @@
/*
* Copyright 2004, 2005, 2006 Acegi Technology Pty Limited
*
* 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.web.access.intercept;
import jakarta.servlet.FilterChain;
import jakarta.servlet.http.HttpServletRequest;
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.context.ApplicationEventPublisher;
import org.springframework.mock.web.MockFilterChain;
import org.springframework.mock.web.MockHttpServletRequest;
import org.springframework.mock.web.MockHttpServletResponse;
import org.springframework.security.access.AccessDecisionManager;
import org.springframework.security.access.SecurityConfig;
import org.springframework.security.access.event.AuthorizedEvent;
import org.springframework.security.access.intercept.AfterInvocationManager;
import org.springframework.security.access.intercept.RunAsManager;
import org.springframework.security.access.intercept.RunAsUserToken;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.TestingAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContext;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.web.FilterInvocation;
import org.springframework.security.web.servlet.TestMockHttpServletRequests;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyCollection;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.BDDMockito.given;
import static org.mockito.BDDMockito.willThrow;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyNoMoreInteractions;
/**
* Tests {@link FilterSecurityInterceptor}.
*
* @author Ben Alex
* @author Luke Taylor
* @author Rob Winch
*/
public class FilterSecurityInterceptorTests {
private AuthenticationManager am;
private AccessDecisionManager adm;
private FilterInvocationSecurityMetadataSource ods;
private RunAsManager ram;
private FilterSecurityInterceptor interceptor;
private ApplicationEventPublisher publisher;
@BeforeEach
public final void setUp() {
this.interceptor = new FilterSecurityInterceptor();
this.am = mock(AuthenticationManager.class);
this.ods = mock(FilterInvocationSecurityMetadataSource.class);
this.adm = mock(AccessDecisionManager.class);
this.ram = mock(RunAsManager.class);
this.publisher = mock(ApplicationEventPublisher.class);
this.interceptor.setAuthenticationManager(this.am);
this.interceptor.setSecurityMetadataSource(this.ods);
this.interceptor.setAccessDecisionManager(this.adm);
this.interceptor.setRunAsManager(this.ram);
this.interceptor.setApplicationEventPublisher(this.publisher);
SecurityContextHolder.clearContext();
}
@AfterEach
public void tearDown() {
SecurityContextHolder.clearContext();
}
@Test
public void testEnsuresAccessDecisionManagerSupportsFilterInvocationClass() throws Exception {
given(this.adm.supports(FilterInvocation.class)).willReturn(true);
assertThatIllegalArgumentException().isThrownBy(this.interceptor::afterPropertiesSet);
}
@Test
public void testEnsuresRunAsManagerSupportsFilterInvocationClass() throws Exception {
given(this.adm.supports(FilterInvocation.class)).willReturn(false);
assertThatIllegalArgumentException().isThrownBy(this.interceptor::afterPropertiesSet);
}
/**
* We just test invocation works in a success event. There is no need to test access
* denied events as the abstract parent enforces that logic, which is extensively
* tested separately.
*/
@Test
public void testSuccessfulInvocation() throws Throwable {
// Setup a Context
Authentication token = new TestingAuthenticationToken("Test", "Password", "NOT_USED");
SecurityContextHolder.getContext().setAuthentication(token);
FilterInvocation fi = createinvocation();
given(this.ods.getAttributes(fi)).willReturn(SecurityConfig.createList("MOCK_OK"));
this.interceptor.invoke(fi);
// SEC-1697
verify(this.publisher, never()).publishEvent(any(AuthorizedEvent.class));
}
@Test
public void afterInvocationIsNotInvokedIfExceptionThrown() throws Exception {
Authentication token = new TestingAuthenticationToken("Test", "Password", "NOT_USED");
SecurityContextHolder.getContext().setAuthentication(token);
FilterInvocation fi = createinvocation();
FilterChain chain = fi.getChain();
willThrow(new RuntimeException()).given(chain)
.doFilter(any(HttpServletRequest.class), any(HttpServletResponse.class));
given(this.ods.getAttributes(fi)).willReturn(SecurityConfig.createList("MOCK_OK"));
AfterInvocationManager aim = mock(AfterInvocationManager.class);
this.interceptor.setAfterInvocationManager(aim);
assertThatExceptionOfType(RuntimeException.class).isThrownBy(() -> this.interceptor.invoke(fi));
verifyNoMoreInteractions(aim);
}
// SEC-1967
@Test
@SuppressWarnings("unchecked")
public void finallyInvocationIsInvokedIfExceptionThrown() throws Exception {
SecurityContext ctx = SecurityContextHolder.getContext();
Authentication token = new TestingAuthenticationToken("Test", "Password", "NOT_USED");
token.setAuthenticated(true);
ctx.setAuthentication(token);
RunAsManager runAsManager = mock(RunAsManager.class);
given(runAsManager.buildRunAs(eq(token), any(), anyCollection()))
.willReturn(new RunAsUserToken("key", "someone", "creds", token.getAuthorities(), token.getClass()));
this.interceptor.setRunAsManager(runAsManager);
FilterInvocation fi = createinvocation();
FilterChain chain = fi.getChain();
willThrow(new RuntimeException()).given(chain)
.doFilter(any(HttpServletRequest.class), any(HttpServletResponse.class));
given(this.ods.getAttributes(fi)).willReturn(SecurityConfig.createList("MOCK_OK"));
AfterInvocationManager aim = mock(AfterInvocationManager.class);
this.interceptor.setAfterInvocationManager(aim);
assertThatExceptionOfType(RuntimeException.class).isThrownBy(() -> this.interceptor.invoke(fi));
// Check we've changed back
assertThat(SecurityContextHolder.getContext()).isSameAs(ctx);
assertThat(SecurityContextHolder.getContext().getAuthentication()).isSameAs(token);
}
@Test
// gh-4997
public void doFilterWhenObserveOncePerRequestThenAttributeNotSet() throws Exception {
this.interceptor.setObserveOncePerRequest(false);
MockHttpServletResponse response = new MockHttpServletResponse();
MockHttpServletRequest request = new MockHttpServletRequest();
this.interceptor.doFilter(request, response, new MockFilterChain());
assertThat(request.getAttributeNames().hasMoreElements()).isFalse();
}
@Test
public void doFilterWhenObserveOncePerRequestFalseAndInvokedTwiceThenObserveTwice() throws Throwable {
Authentication token = new TestingAuthenticationToken("Test", "Password", "NOT_USED");
SecurityContextHolder.getContext().setAuthentication(token);
FilterInvocation fi = createinvocation();
given(this.ods.getAttributes(fi)).willReturn(SecurityConfig.createList("MOCK_OK"));
this.interceptor.invoke(fi);
this.interceptor.invoke(fi);
verify(this.adm, times(2)).decide(any(), any(), any());
}
private FilterInvocation createinvocation() {
MockHttpServletResponse response = new MockHttpServletResponse();
MockHttpServletRequest request = TestMockHttpServletRequests.get("/secure/page.html").build();
FilterChain chain = mock(FilterChain.class);
FilterInvocation fi = new FilterInvocation(request, response, chain);
return fi;
}
}