1
0
mirror of synced 2026-09-12 12:15:13 +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
@@ -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;
}
}