Significantly enhance channel processing filter.

This commit is contained in:
Ben Alex 2004-04-27 06:21:00 +00:00
parent e555d77d4e
commit 901c7d4752
17 changed files with 1542 additions and 145 deletions

View File

@ -1,4 +1,4 @@
Changes in version 0.5 (2004-xx-xx)
Changes in version 0.5 (2004-04-28)
-----------------------------------
* Added single sign on support via Yale Central Authentication Service (CAS)
@ -13,7 +13,9 @@ Changes in version 0.5 (2004-xx-xx)
* Added definable prefixes to avoid expectation of "ROLE_" GrantedAuthoritys
* Added pluggable AuthenticationEntryPoints to SecurityEnforcementFilter
* Added Apache Ant path syntax support to SecurityEnforcementFilter
* Added filter to automate entry into secure channels, such as HTTPS
* Updated JAR to Spring 1.0.1
* Updated several classes to use absolute (not relative) redirection URLs
* Refactored filters to use Spring application context lifecycle support
* Improved constructor detection of nulls in User and other key objects
* Fixed FilterInvocation.getRequestUrl() to also include getPathInfo()

View File

@ -29,10 +29,11 @@ public interface ChannelDecisionManager {
//~ Methods ================================================================
/**
* Decided whether the presented {@link FilterInvocation} provides
* sufficient security based on the requested {@link
* Decided whether the presented {@link FilterInvocation} provides the
* appropriate level of channel security based on the requested {@link
* ConfigAttributeDefinition}.
*/
public void decide(FilterInvocation invocation,
ConfigAttributeDefinition config) throws SecureChannelRequiredException;
ConfigAttributeDefinition config)
throws InsecureChannelRequiredException, SecureChannelRequiredException;
}

View File

@ -26,14 +26,31 @@ import java.util.Iterator;
/**
* <p>
* Requires a secure channel for a web request if a {@link
* ConfigAttribute#getAttribute()} keyword is detected.
* Ensures configuration attribute requested channel security is present by
* review of <code>HttpServletRequest.isSecure()</code> responses.
* </p>
*
* <P>
* The default keyword string is <Code>REQUIRES_SECURE_CHANNEL</code>, but this
* may be overriden to any value. The <code>ConfigAttribute</code> must
* exactly match the case of the keyword string.
* The class responds to two and only two case-sensitive keywords: {@link
* #getInsecureKeyword()} and {@link #getSecureKeyword}. If either of these
* keywords are detected, <code>HttpServletRequest.isSecure()</code> is used
* to determine the channel security offered. If the channel security differs
* from that requested by the keyword, the relevant exception is thrown.
* </p>
*
* <P>
* If both the <code>secureKeyword</code> and <code>insecureKeyword</code>
* configuration attributes are detected, the request will be deemed to be
* requesting a secure channel. This is a reasonable approach, as when in
* doubt, the decision manager assumes the most secure outcome is desired. Of
* course, you <b>should</b> indicate one configuration attribute or the other
* (not both).
* </p>
*
* <P>
* The default <code>secureKeyword</code> and <code>insecureKeyword</code> is
* <code>REQUIRES_SECURE_CHANNEL</code> and
* <code>REQUIRES_INSECURE_CHANNEL</code> respectively.
* </p>
*
* @author Ben Alex
@ -43,21 +60,34 @@ public class ChannelDecisionManagerImpl implements InitializingBean,
ChannelDecisionManager {
//~ Instance fields ========================================================
private String keyword = "REQUIRES_SECURE_CHANNEL";
private String insecureKeyword = "REQUIRES_INSECURE_CHANNEL";
private String secureKeyword = "REQUIRES_SECURE_CHANNEL";
//~ Methods ================================================================
public void setKeyword(String keyword) {
this.keyword = keyword;
public void setInsecureKeyword(String insecureKeyword) {
this.insecureKeyword = insecureKeyword;
}
public String getKeyword() {
return keyword;
public String getInsecureKeyword() {
return insecureKeyword;
}
public void setSecureKeyword(String secureKeyword) {
this.secureKeyword = secureKeyword;
}
public String getSecureKeyword() {
return secureKeyword;
}
public void afterPropertiesSet() throws Exception {
if ((keyword == null) || "".equals(keyword)) {
throw new IllegalArgumentException("keyword required");
if ((secureKeyword == null) || "".equals(secureKeyword)) {
throw new IllegalArgumentException("secureKeyword required");
}
if ((insecureKeyword == null) || "".equals(insecureKeyword)) {
throw new IllegalArgumentException("insecureKeyword required");
}
}
@ -72,12 +102,19 @@ public class ChannelDecisionManagerImpl implements InitializingBean,
while (iter.hasNext()) {
ConfigAttribute attribute = (ConfigAttribute) iter.next();
if (attribute.equals(keyword)) {
if (attribute.equals(secureKeyword)) {
if (!invocation.getHttpRequest().isSecure()) {
throw new SecureChannelRequiredException(
"Request is not being made over a secure channel");
}
}
if (attribute.equals(insecureKeyword)) {
if (invocation.getHttpRequest().isSecure()) {
throw new InsecureChannelRequiredException(
"Request is being made over a secure channel when an insecure channel is required");
}
}
}
}
}

View File

@ -23,7 +23,12 @@ import javax.servlet.ServletResponse;
/**
* Used by {@link ChannelProcessingFilter} to launch a secure web channel.
* Used by {@link ChannelProcessingFilter} to launch a web channel.
*
* <P>
* Depending on the implementation, a secure or insecure channel will be
* launched.
* </p>
*
* @author Ben Alex
* @version $Id$
@ -37,12 +42,14 @@ public interface ChannelEntryPoint {
* <P>
* Implementations should modify the headers on the
* <code>ServletResponse</code> as necessary to commence the user agent
* using the secure channel.
* using the implementation's supported channel type (ie secure or
* insecure).
* </p>
*
* @param request that resulted in a
* <code>SecureChannelRequiredException</code>
* @param response so that the user agent can begin using a secure channel
* <code>SecureChannelRequiredException</code> or
* <code>InsecureChannelRequiredException</code>
* @param response so that the user agent can begin using a new channel
*/
public void commence(ServletRequest request, ServletResponse response)
throws IOException, ServletException;

View File

@ -37,7 +37,7 @@ import javax.servlet.http.HttpServletResponse;
/**
* Ensures a request is delivered over a secure channel.
* Ensures a web request is delivered over the required channel.
*
* <p>
* Internally uses a {@link FilterInvocation} to represent the request, so that
@ -62,7 +62,8 @@ public class ChannelProcessingFilter implements InitializingBean, Filter {
//~ Instance fields ========================================================
private ChannelDecisionManager channelDecisionManager;
private ChannelEntryPoint channelEntryPoint;
private ChannelEntryPoint insecureChannelEntryPoint;
private ChannelEntryPoint secureChannelEntryPoint;
private FilterInvocationDefinitionSource filterInvocationDefinitionSource;
//~ Methods ================================================================
@ -76,14 +77,6 @@ public class ChannelProcessingFilter implements InitializingBean, Filter {
return channelDecisionManager;
}
public void setChannelEntryPoint(ChannelEntryPoint channelEntryPoint) {
this.channelEntryPoint = channelEntryPoint;
}
public ChannelEntryPoint getChannelEntryPoint() {
return channelEntryPoint;
}
public void setFilterInvocationDefinitionSource(
FilterInvocationDefinitionSource filterInvocationDefinitionSource) {
this.filterInvocationDefinitionSource = filterInvocationDefinitionSource;
@ -93,6 +86,23 @@ public class ChannelProcessingFilter implements InitializingBean, Filter {
return filterInvocationDefinitionSource;
}
public void setInsecureChannelEntryPoint(
ChannelEntryPoint insecureChannelEntryPoint) {
this.insecureChannelEntryPoint = insecureChannelEntryPoint;
}
public ChannelEntryPoint getInsecureChannelEntryPoint() {
return insecureChannelEntryPoint;
}
public void setSecureChannelEntryPoint(ChannelEntryPoint channelEntryPoint) {
this.secureChannelEntryPoint = channelEntryPoint;
}
public ChannelEntryPoint getSecureChannelEntryPoint() {
return secureChannelEntryPoint;
}
public void afterPropertiesSet() throws Exception {
if (filterInvocationDefinitionSource == null) {
throw new IllegalArgumentException(
@ -104,9 +114,14 @@ public class ChannelProcessingFilter implements InitializingBean, Filter {
"channelDecisionManager must be specified");
}
if (channelEntryPoint == null) {
if (secureChannelEntryPoint == null) {
throw new IllegalArgumentException(
"channelEntryPoint must be specified");
"secureChannelEntryPoint must be specified");
}
if (insecureChannelEntryPoint == null) {
throw new IllegalArgumentException(
"insecureChannelEntryPoint must be specified");
}
}
@ -128,20 +143,32 @@ public class ChannelProcessingFilter implements InitializingBean, Filter {
if (attr != null) {
if (logger.isDebugEnabled()) {
logger.debug("Request : " + request.toString()
logger.debug("Request: " + fi.getFullRequestUrl()
+ "; ConfigAttributes: " + attr.toString());
}
try {
channelDecisionManager.decide(fi, attr);
} catch (SecureChannelRequiredException channelException) {
} catch (SecureChannelRequiredException secureException) {
if (logger.isDebugEnabled()) {
logger.debug("Channel insufficient ("
+ channelException.getMessage()
+ "); delegating to channelEntryPoint");
logger.debug("Channel insufficient security ("
+ secureException.getMessage()
+ "); delegating to secureChannelEntryPoint");
}
channelEntryPoint.commence(request, response);
secureChannelEntryPoint.commence(request, response);
return;
} catch (InsecureChannelRequiredException insecureException) {
if (logger.isDebugEnabled()) {
logger.debug("Channel too much security ("
+ insecureException.getMessage()
+ "); delegating to insecureChannelEntryPoint");
}
insecureChannelEntryPoint.commence(request, response);
return;
}
}

View File

@ -0,0 +1,50 @@
/* Copyright 2004 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
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.sf.acegisecurity.securechannel;
import net.sf.acegisecurity.AccessDeniedException;
/**
* Thrown if a secure web channel is detected, but is not required.
*
* @author Ben Alex
* @version $Id$
*/
public class InsecureChannelRequiredException extends AccessDeniedException {
//~ Constructors ===========================================================
/**
* Constructs an <code>InsecureChannelRequiredException</code> with the
* specified message.
*
* @param msg the detail message.
*/
public InsecureChannelRequiredException(String msg) {
super(msg);
}
/**
* Constructs an <code>InsecureChannelRequiredException</code> with the
* specified message and root cause.
*
* @param msg the detail message.
* @param t root cause
*/
public InsecureChannelRequiredException(String msg, Throwable t) {
super(msg, t);
}
}

View File

@ -0,0 +1,120 @@
/* Copyright 2004 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
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.sf.acegisecurity.securechannel;
import net.sf.acegisecurity.util.PortMapper;
import net.sf.acegisecurity.util.PortResolver;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.factory.InitializingBean;
import java.io.IOException;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/**
* 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.
* </p>
*
* @author Ben Alex
* @version $Id$
*/
public class RetryWithHttpEntryPoint implements InitializingBean,
ChannelEntryPoint {
//~ Static fields/initializers =============================================
private static final Log logger = LogFactory.getLog(RetryWithHttpEntryPoint.class);
//~ Instance fields ========================================================
private PortMapper portMapper;
private PortResolver portResolver;
//~ Methods ================================================================
public void setPortMapper(PortMapper portMapper) {
this.portMapper = portMapper;
}
public PortMapper getPortMapper() {
return portMapper;
}
public void setPortResolver(PortResolver portResolver) {
this.portResolver = portResolver;
}
public PortResolver getPortResolver() {
return portResolver;
}
public void afterPropertiesSet() throws Exception {
if (portMapper == null) {
throw new IllegalArgumentException("portMapper is required");
}
if (portResolver == null) {
throw new IllegalArgumentException("portResolver is required");
}
}
public void commence(ServletRequest request, ServletResponse response)
throws IOException, ServletException {
HttpServletRequest req = (HttpServletRequest) request;
String pathInfo = req.getPathInfo();
String queryString = req.getQueryString();
String contextPath = req.getContextPath();
String destination = req.getServletPath()
+ ((pathInfo == null) ? "" : pathInfo)
+ ((queryString == null) ? "" : ("?" + queryString));
String redirectUrl = contextPath;
Integer httpsPort = new Integer(portResolver.getServerPort(req));
Integer httpPort = portMapper.lookupHttpPort(httpsPort);
if (httpPort != null) {
boolean includePort = true;
if (httpPort.intValue() == 80) {
includePort = false;
}
redirectUrl = "http://" + req.getServerName()
+ ((includePort) ? (":" + httpPort) : "") + contextPath
+ destination;
}
if (logger.isDebugEnabled()) {
logger.debug("Redirecting to: " + redirectUrl);
}
((HttpServletResponse) response).sendRedirect(redirectUrl);
}
}

View File

@ -15,6 +15,9 @@
package net.sf.acegisecurity.securechannel;
import net.sf.acegisecurity.util.PortMapper;
import net.sf.acegisecurity.util.PortResolver;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
@ -22,10 +25,6 @@ import org.springframework.beans.factory.InitializingBean;
import java.io.IOException;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
@ -53,71 +52,34 @@ public class RetryWithHttpsEntryPoint implements InitializingBean,
//~ Instance fields ========================================================
private Map httpsPortMappings;
//~ Constructors ===========================================================
public RetryWithHttpsEntryPoint() {
httpsPortMappings = new HashMap();
httpsPortMappings.put(new Integer(80), new Integer(443));
httpsPortMappings.put(new Integer(8080), new Integer(8443));
}
private PortMapper portMapper;
private PortResolver portResolver;
//~ Methods ================================================================
/**
* <p>
* Set to override the default http port to https port mappings of 80:443,
* and 8080:8443.
* </p>
* In a Spring XML ApplicationContext, a definition would look something
* like this:
* <pre>
* &lt;property name="httpsPortMapping">
* &lt;map>
* &lt;entry key="80">&lt;value>443&lt;/value>&lt;/entry>
* &lt;entry key="8080">&lt;value>8443&lt;/value>&lt;/entry>
* &lt;/map>
* &lt;/property>
* </pre>
*
* @param newMappings A Map consisting of String keys and String values,
* where for each entry the key is the string representation of an
* integer http port number, and the value is the string
* representation of the corresponding integer https port number.
*
* @throws IllegalArgumentException if input map does not consist of String
* keys and values, each representing an integer port number in
* the range 1-65535 for that mapping.
*/
public void setHttpsPortMappings(HashMap newMappings) {
httpsPortMappings.clear();
Iterator it = newMappings.entrySet().iterator();
while (it.hasNext()) {
Map.Entry entry = (Map.Entry) it.next();
Integer httpPort = new Integer((String) entry.getKey());
Integer httpsPort = new Integer((String) entry.getValue());
if ((httpPort.intValue() < 1) || (httpPort.intValue() > 65535)
|| (httpsPort.intValue() < 1) || (httpsPort.intValue() > 65535)) {
throw new IllegalArgumentException(
"one or both ports out of legal range: " + httpPort + ", "
+ httpsPort);
public void setPortMapper(PortMapper portMapper) {
this.portMapper = portMapper;
}
httpsPortMappings.put(httpPort, httpsPort);
public PortMapper getPortMapper() {
return portMapper;
}
if (httpsPortMappings.size() < 1) {
throw new IllegalArgumentException("must map at least one port");
}
public void setPortResolver(PortResolver portResolver) {
this.portResolver = portResolver;
}
public PortResolver getPortResolver() {
return portResolver;
}
public void afterPropertiesSet() throws Exception {
if (httpsPortMappings == null) {
throw new IllegalArgumentException("httpsPortMappings required");
if (portMapper == null) {
throw new IllegalArgumentException("portMapper is required");
}
if (portResolver == null) {
throw new IllegalArgumentException("portResolver is required");
}
}
@ -134,25 +96,25 @@ public class RetryWithHttpsEntryPoint implements InitializingBean,
String redirectUrl = contextPath;
Integer httpPort = new Integer(req.getServerPort());
Integer httpsPort = (Integer) httpsPortMappings.get(httpPort);
Integer httpPort = new Integer(portResolver.getServerPort(req));
Integer httpsPort = portMapper.lookupHttpsPort(httpPort);
if (httpsPort != null) {
String serverName = req.getServerName();
redirectUrl = "https://" + serverName + ":" + httpsPort
+ contextPath + destination;
boolean includePort = true;
if (httpsPort.intValue() == 443) {
includePort = false;
}
redirectUrl = "https://" + req.getServerName()
+ ((includePort) ? (":" + httpsPort) : "") + contextPath
+ destination;
}
if (logger.isDebugEnabled()) {
logger.debug("Redirecting to: " + redirectUrl);
}
((HttpServletResponse) response).sendRedirect(redirectUrl);
}
/**
* Returns the translated (Integer -> Integer) version of the original port
* mapping specified via setHttpsPortMapping()
*
* @return DOCUMENT ME!
*/
protected Map getTranslatedHttpsPortMappings() {
return httpsPortMappings;
}
}

View File

@ -0,0 +1,182 @@
/* Copyright 2004 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
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.sf.acegisecurity.securechannel;
import junit.framework.TestCase;
import net.sf.acegisecurity.ConfigAttributeDefinition;
import net.sf.acegisecurity.MockFilterChain;
import net.sf.acegisecurity.MockHttpServletRequest;
import net.sf.acegisecurity.MockHttpServletResponse;
import net.sf.acegisecurity.SecurityConfig;
import net.sf.acegisecurity.intercept.web.FilterInvocation;
/**
* Tests {@link ChannelDecisionManagerImpl}.
*
* @author Ben Alex
* @version $Id$
*/
public class ChannelDecisionManagerImplTests extends TestCase {
//~ Methods ================================================================
public final void setUp() throws Exception {
super.setUp();
}
public static void main(String[] args) {
junit.textui.TestRunner.run(ChannelDecisionManagerImplTests.class);
}
public void testDetectsInvalidInsecureKeyword() throws Exception {
ChannelDecisionManagerImpl cdm = new ChannelDecisionManagerImpl();
cdm.setInsecureKeyword("");
try {
cdm.afterPropertiesSet();
fail("Should have thrown IllegalArgumentException");
} catch (IllegalArgumentException expected) {
assertEquals("insecureKeyword required", expected.getMessage());
}
cdm.setInsecureKeyword(null);
try {
cdm.afterPropertiesSet();
fail("Should have thrown IllegalArgumentException");
} catch (IllegalArgumentException expected) {
assertEquals("insecureKeyword required", expected.getMessage());
}
}
public void testDetectsInvalidSecureKeyword() throws Exception {
ChannelDecisionManagerImpl cdm = new ChannelDecisionManagerImpl();
cdm.setSecureKeyword("");
try {
cdm.afterPropertiesSet();
fail("Should have thrown IllegalArgumentException");
} catch (IllegalArgumentException expected) {
assertEquals("secureKeyword required", expected.getMessage());
}
cdm.setSecureKeyword(null);
try {
cdm.afterPropertiesSet();
fail("Should have thrown IllegalArgumentException");
} catch (IllegalArgumentException expected) {
assertEquals("secureKeyword required", expected.getMessage());
}
}
public void testDetectsNullsPassedToMainMethod() {
ChannelDecisionManagerImpl cdm = new ChannelDecisionManagerImpl();
try {
cdm.decide(null, new ConfigAttributeDefinition());
fail("Should have thrown IllegalArgumentException");
} catch (IllegalArgumentException expected) {
assertEquals("Nulls cannot be provided", expected.getMessage());
}
try {
cdm.decide(new FilterInvocation(new MockHttpServletRequest("x"),
new MockHttpServletResponse(), new MockFilterChain()), null);
fail("Should have thrown IllegalArgumentException");
} catch (IllegalArgumentException expected) {
assertEquals("Nulls cannot be provided", expected.getMessage());
}
}
public void testDetectsWhenInsecureChannelNeededAndInsecureSchemeUsed() {
ConfigAttributeDefinition attr = new ConfigAttributeDefinition();
attr.addConfigAttribute(new SecurityConfig(
"SOME_CONFIG_ATTRIBUTE_TO_IGNORE"));
attr.addConfigAttribute(new SecurityConfig("REQUIRES_INSECURE_CHANNEL"));
MockHttpServletRequest request = new MockHttpServletRequest("foo=bar");
request.setScheme("http");
ChannelDecisionManagerImpl cdm = new ChannelDecisionManagerImpl();
cdm.decide(new FilterInvocation(request, new MockHttpServletResponse(),
new MockFilterChain()), attr);
assertTrue(true);
}
public void testDetectsWhenInsecureChannelNeededAndSecureSchemeUsed() {
ConfigAttributeDefinition attr = new ConfigAttributeDefinition();
attr.addConfigAttribute(new SecurityConfig(
"SOME_CONFIG_ATTRIBUTE_TO_IGNORE"));
attr.addConfigAttribute(new SecurityConfig("REQUIRES_INSECURE_CHANNEL"));
MockHttpServletRequest request = new MockHttpServletRequest("foo=bar");
request.setScheme("https");
ChannelDecisionManagerImpl cdm = new ChannelDecisionManagerImpl();
try {
cdm.decide(new FilterInvocation(request,
new MockHttpServletResponse(), new MockFilterChain()), attr);
} catch (InsecureChannelRequiredException expected) {
assertTrue(true);
}
}
public void testDetectsWhenSecureChannelNeeded() {
ConfigAttributeDefinition attr = new ConfigAttributeDefinition();
attr.addConfigAttribute(new SecurityConfig(
"SOME_CONFIG_ATTRIBUTE_TO_IGNORE"));
attr.addConfigAttribute(new SecurityConfig("REQUIRES_SECURE_CHANNEL"));
MockHttpServletRequest request = new MockHttpServletRequest("foo=bar");
request.setScheme("http");
ChannelDecisionManagerImpl cdm = new ChannelDecisionManagerImpl();
try {
cdm.decide(new FilterInvocation(request,
new MockHttpServletResponse(), new MockFilterChain()), attr);
} catch (SecureChannelRequiredException expected) {
assertTrue(true);
}
}
public void testGetterSetters() throws Exception {
ChannelDecisionManagerImpl cdm = new ChannelDecisionManagerImpl();
cdm.afterPropertiesSet();
assertEquals("REQUIRES_INSECURE_CHANNEL", cdm.getInsecureKeyword());
assertEquals("REQUIRES_SECURE_CHANNEL", cdm.getSecureKeyword());
cdm.setInsecureKeyword("MY_INSECURE");
cdm.setSecureKeyword("MY_SECURE");
assertEquals("MY_INSECURE", cdm.getInsecureKeyword());
assertEquals("MY_SECURE", cdm.getSecureKeyword());
}
public void testIgnoresOtherConfigAttributes() {
ConfigAttributeDefinition attr = new ConfigAttributeDefinition();
attr.addConfigAttribute(new SecurityConfig("XYZ"));
ChannelDecisionManagerImpl cdm = new ChannelDecisionManagerImpl();
cdm.decide(new FilterInvocation(new MockHttpServletRequest("x"),
new MockHttpServletResponse(), new MockFilterChain()), attr);
assertTrue(true);
}
}
;

View File

@ -0,0 +1,339 @@
/* Copyright 2004 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
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.sf.acegisecurity.securechannel;
import junit.framework.TestCase;
import net.sf.acegisecurity.ConfigAttributeDefinition;
import net.sf.acegisecurity.MockFilterConfig;
import net.sf.acegisecurity.MockHttpServletRequest;
import net.sf.acegisecurity.MockHttpServletResponse;
import net.sf.acegisecurity.SecurityConfig;
import net.sf.acegisecurity.intercept.web.FilterInvocation;
import net.sf.acegisecurity.intercept.web.FilterInvocationDefinitionSource;
import net.sf.acegisecurity.intercept.web.RegExpBasedFilterInvocationDefinitionMap;
import java.io.IOException;
import java.util.Iterator;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
/**
* Tests {@link ChannelProcessingFilter}.
*
* @author Ben Alex
* @version $Id$
*/
public class ChannelProcessingFilterTests extends TestCase {
//~ Methods ================================================================
public final void setUp() throws Exception {
super.setUp();
}
public static void main(String[] args) {
junit.textui.TestRunner.run(ChannelProcessingFilterTests.class);
}
public void testCallsInsecureEntryPointWhenTooMuchChannelSecurity()
throws Exception {
ConfigAttributeDefinition attr = new ConfigAttributeDefinition();
attr.addConfigAttribute(new SecurityConfig("REQUIRES_INSECURE_CHANNEL"));
MockFilterInvocationDefinitionMap fids = new MockFilterInvocationDefinitionMap("/path",
attr);
ChannelProcessingFilter filter = new ChannelProcessingFilter();
filter.setInsecureChannelEntryPoint(new MockEntryPoint(true));
filter.setSecureChannelEntryPoint(new MockEntryPoint(false));
filter.setFilterInvocationDefinitionSource(fids);
filter.setChannelDecisionManager(new ChannelDecisionManagerImpl());
MockHttpServletRequest request = new MockHttpServletRequest("info=now");
request.setServletPath("/path");
request.setScheme("https");
MockHttpServletResponse response = new MockHttpServletResponse();
MockFilterChain chain = new MockFilterChain(false);
filter.doFilter(request, response, chain);
assertTrue(true);
}
public void testCallsSecureEntryPointWhenTooLittleChannelSecurity()
throws Exception {
ConfigAttributeDefinition attr = new ConfigAttributeDefinition();
attr.addConfigAttribute(new SecurityConfig("REQUIRES_SECURE_CHANNEL"));
MockFilterInvocationDefinitionMap fids = new MockFilterInvocationDefinitionMap("/path",
attr);
ChannelProcessingFilter filter = new ChannelProcessingFilter();
filter.setInsecureChannelEntryPoint(new MockEntryPoint(false));
filter.setSecureChannelEntryPoint(new MockEntryPoint(true));
filter.setFilterInvocationDefinitionSource(fids);
filter.setChannelDecisionManager(new ChannelDecisionManagerImpl());
MockHttpServletRequest request = new MockHttpServletRequest("info=now");
request.setServletPath("/path");
request.setScheme("http");
MockHttpServletResponse response = new MockHttpServletResponse();
MockFilterChain chain = new MockFilterChain(false);
filter.doFilter(request, response, chain);
assertTrue(true);
}
public void testDetectsMissingChannelDecisionManager()
throws Exception {
ChannelProcessingFilter filter = new ChannelProcessingFilter();
filter.setSecureChannelEntryPoint(new RetryWithHttpsEntryPoint());
filter.setFilterInvocationDefinitionSource(new RegExpBasedFilterInvocationDefinitionMap());
try {
filter.afterPropertiesSet();
fail("Should have thrown IllegalArgumentException");
} catch (IllegalArgumentException expected) {
assertEquals("channelDecisionManager must be specified",
expected.getMessage());
}
}
public void testDetectsMissingFilterInvocationDefinitionMap()
throws Exception {
ChannelProcessingFilter filter = new ChannelProcessingFilter();
filter.setInsecureChannelEntryPoint(new RetryWithHttpEntryPoint());
filter.setSecureChannelEntryPoint(new RetryWithHttpsEntryPoint());
filter.setChannelDecisionManager(new ChannelDecisionManagerImpl());
try {
filter.afterPropertiesSet();
fail("Should have thrown IllegalArgumentException");
} catch (IllegalArgumentException expected) {
assertEquals("filterInvocationDefinitionSource must be specified",
expected.getMessage());
}
}
public void testDetectsMissingInsecureChannelEntryPoint()
throws Exception {
ChannelProcessingFilter filter = new ChannelProcessingFilter();
filter.setSecureChannelEntryPoint(new RetryWithHttpsEntryPoint());
filter.setFilterInvocationDefinitionSource(new RegExpBasedFilterInvocationDefinitionMap());
filter.setChannelDecisionManager(new ChannelDecisionManagerImpl());
try {
filter.afterPropertiesSet();
fail("Should have thrown IllegalArgumentException");
} catch (IllegalArgumentException expected) {
assertEquals("insecureChannelEntryPoint must be specified",
expected.getMessage());
}
}
public void testDetectsMissingSecureChannelEntryPoint()
throws Exception {
ChannelProcessingFilter filter = new ChannelProcessingFilter();
filter.setInsecureChannelEntryPoint(new RetryWithHttpEntryPoint());
filter.setFilterInvocationDefinitionSource(new RegExpBasedFilterInvocationDefinitionMap());
filter.setChannelDecisionManager(new ChannelDecisionManagerImpl());
try {
filter.afterPropertiesSet();
fail("Should have thrown IllegalArgumentException");
} catch (IllegalArgumentException expected) {
assertEquals("secureChannelEntryPoint must be specified",
expected.getMessage());
}
}
public void testDoFilterWithNonHttpServletRequestDetected()
throws Exception {
ChannelProcessingFilter filter = new ChannelProcessingFilter();
try {
filter.doFilter(null, new MockHttpServletResponse(),
new MockFilterChain());
fail("Should have thrown ServletException");
} catch (ServletException expected) {
assertEquals("HttpServletRequest required", expected.getMessage());
}
}
public void testDoFilterWithNonHttpServletResponseDetected()
throws Exception {
ChannelProcessingFilter filter = new ChannelProcessingFilter();
try {
filter.doFilter(new MockHttpServletRequest(null, null), null,
new MockFilterChain());
fail("Should have thrown ServletException");
} catch (ServletException expected) {
assertEquals("HttpServletResponse required", expected.getMessage());
}
}
public void testDoesNotInterruptRequestsWithCorrectChannelSecurity()
throws Exception {
ConfigAttributeDefinition attr = new ConfigAttributeDefinition();
attr.addConfigAttribute(new SecurityConfig("REQUIRES_SECURE_CHANNEL"));
MockFilterInvocationDefinitionMap fids = new MockFilterInvocationDefinitionMap("/path",
attr);
ChannelProcessingFilter filter = new ChannelProcessingFilter();
filter.setInsecureChannelEntryPoint(new RetryWithHttpEntryPoint());
filter.setSecureChannelEntryPoint(new RetryWithHttpsEntryPoint());
filter.setFilterInvocationDefinitionSource(fids);
filter.setChannelDecisionManager(new ChannelDecisionManagerImpl());
MockHttpServletRequest request = new MockHttpServletRequest("info=now");
request.setServletPath("/path");
request.setScheme("https");
MockHttpServletResponse response = new MockHttpServletResponse();
MockFilterChain chain = new MockFilterChain(true);
filter.doFilter(request, response, chain);
assertTrue(true);
}
public void testDoesNotInterruptRequestsWithNoConfigAttribute()
throws Exception {
ChannelProcessingFilter filter = new ChannelProcessingFilter();
filter.setInsecureChannelEntryPoint(new RetryWithHttpEntryPoint());
filter.setSecureChannelEntryPoint(new RetryWithHttpsEntryPoint());
filter.setFilterInvocationDefinitionSource(new RegExpBasedFilterInvocationDefinitionMap());
filter.setChannelDecisionManager(new ChannelDecisionManagerImpl());
MockHttpServletRequest request = new MockHttpServletRequest("info=now");
MockHttpServletResponse response = new MockHttpServletResponse();
MockFilterChain chain = new MockFilterChain(true);
filter.doFilter(request, response, chain);
assertTrue(true);
}
public void testGetterSetters() {
ChannelProcessingFilter filter = new ChannelProcessingFilter();
filter.setInsecureChannelEntryPoint(new RetryWithHttpEntryPoint());
filter.setSecureChannelEntryPoint(new RetryWithHttpsEntryPoint());
filter.setFilterInvocationDefinitionSource(new RegExpBasedFilterInvocationDefinitionMap());
filter.setChannelDecisionManager(new ChannelDecisionManagerImpl());
assertTrue(filter.getInsecureChannelEntryPoint() != null);
assertTrue(filter.getSecureChannelEntryPoint() != null);
assertTrue(filter.getFilterInvocationDefinitionSource() != null);
assertTrue(filter.getChannelDecisionManager() != null);
}
public void testLifecycle() throws Exception {
ChannelProcessingFilter filter = new ChannelProcessingFilter();
filter.setInsecureChannelEntryPoint(new RetryWithHttpEntryPoint());
filter.setSecureChannelEntryPoint(new RetryWithHttpsEntryPoint());
filter.setFilterInvocationDefinitionSource(new RegExpBasedFilterInvocationDefinitionMap());
filter.setChannelDecisionManager(new ChannelDecisionManagerImpl());
filter.afterPropertiesSet();
filter.init(new MockFilterConfig());
filter.destroy();
}
//~ Inner Classes ==========================================================
private class MockEntryPoint implements ChannelEntryPoint {
private boolean expectToBeCalled;
public MockEntryPoint(boolean expectToBeCalled) {
this.expectToBeCalled = expectToBeCalled;
}
private MockEntryPoint() {
super();
}
public void commence(ServletRequest request, ServletResponse response)
throws IOException, ServletException {
if (expectToBeCalled) {
assertTrue(true);
} else {
fail("Did not expect this ChannelEntryPoint to be called");
}
}
}
private class MockFilterChain implements FilterChain {
private boolean expectToProceed;
public MockFilterChain(boolean expectToProceed) {
this.expectToProceed = expectToProceed;
}
private MockFilterChain() {
super();
}
public void doFilter(ServletRequest request, ServletResponse response)
throws IOException, ServletException {
if (expectToProceed) {
assertTrue(true);
} else {
fail("Did not expect filter chain to proceed");
}
}
}
private class MockFilterInvocationDefinitionMap
implements FilterInvocationDefinitionSource {
private ConfigAttributeDefinition toReturn;
private String servletPath;
public MockFilterInvocationDefinitionMap(String servletPath,
ConfigAttributeDefinition toReturn) {
this.servletPath = servletPath;
this.toReturn = toReturn;
}
private MockFilterInvocationDefinitionMap() {
super();
}
public ConfigAttributeDefinition getAttributes(Object object)
throws IllegalArgumentException {
FilterInvocation fi = (FilterInvocation) object;
if (servletPath.equals(fi.getHttpRequest().getServletPath())) {
return toReturn;
} else {
return null;
}
}
public Iterator getConfigAttributeDefinitions() {
return null;
}
public boolean supports(Class clazz) {
return true;
}
}
}

View File

@ -0,0 +1,171 @@
/* Copyright 2004 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
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.sf.acegisecurity.securechannel;
import junit.framework.TestCase;
import net.sf.acegisecurity.MockHttpServletRequest;
import net.sf.acegisecurity.MockHttpServletResponse;
import net.sf.acegisecurity.MockPortResolver;
import net.sf.acegisecurity.util.PortMapperImpl;
import java.util.HashMap;
import java.util.Map;
/**
* Tests {@link RetryWithHttpEntryPoint}.
*
* @author Ben Alex
* @version $Id$
*/
public class RetryWithHttpEntryPointTests extends TestCase {
//~ Methods ================================================================
public final void setUp() throws Exception {
super.setUp();
}
public static void main(String[] args) {
junit.textui.TestRunner.run(RetryWithHttpEntryPointTests.class);
}
public void testDetectsMissingPortMapper() throws Exception {
RetryWithHttpEntryPoint ep = new RetryWithHttpEntryPoint();
ep.setPortResolver(new MockPortResolver(80, 443));
try {
ep.afterPropertiesSet();
fail("Should have thrown IllegalArgumentException");
} catch (IllegalArgumentException expected) {
assertEquals("portMapper is required", expected.getMessage());
}
}
public void testDetectsMissingPortResolver() throws Exception {
RetryWithHttpEntryPoint ep = new RetryWithHttpEntryPoint();
ep.setPortMapper(new PortMapperImpl());
try {
ep.afterPropertiesSet();
fail("Should have thrown IllegalArgumentException");
} catch (IllegalArgumentException expected) {
assertEquals("portResolver is required", expected.getMessage());
}
}
public void testGettersSetters() {
RetryWithHttpEntryPoint ep = new RetryWithHttpEntryPoint();
ep.setPortMapper(new PortMapperImpl());
ep.setPortResolver(new MockPortResolver(8080, 8443));
assertTrue(ep.getPortMapper() != null);
assertTrue(ep.getPortResolver() != null);
}
public void testNormalOperation() throws Exception {
MockHttpServletRequest request = new MockHttpServletRequest("open=true");
request.setScheme("https");
request.setServerName("www.example.com");
request.setContextPath("/bigWebApp");
request.setServletPath("/hello");
request.setPathInfo("/pathInfo.html");
request.setServerPort(443);
MockHttpServletResponse response = new MockHttpServletResponse();
RetryWithHttpEntryPoint ep = new RetryWithHttpEntryPoint();
ep.setPortMapper(new PortMapperImpl());
ep.setPortResolver(new MockPortResolver(80, 443));
ep.afterPropertiesSet();
ep.commence(request, response);
System.out.println(response.getRedirect());
assertEquals("http://www.example.com/bigWebApp/hello/pathInfo.html?open=true",
response.getRedirect());
}
public void testNormalOperationWithNullPathInfoAndNullQueryString()
throws Exception {
MockHttpServletRequest request = new MockHttpServletRequest(null);
request.setScheme("https");
request.setServerName("www.example.com");
request.setContextPath("/bigWebApp");
request.setServletPath("/hello");
request.setPathInfo(null);
request.setServerPort(443);
MockHttpServletResponse response = new MockHttpServletResponse();
RetryWithHttpEntryPoint ep = new RetryWithHttpEntryPoint();
ep.setPortMapper(new PortMapperImpl());
ep.setPortResolver(new MockPortResolver(80, 443));
ep.afterPropertiesSet();
ep.commence(request, response);
System.out.println(response.getRedirect());
assertEquals("http://www.example.com/bigWebApp/hello",
response.getRedirect());
}
public void testOperationWhenTargetPortIsUnknown()
throws Exception {
MockHttpServletRequest request = new MockHttpServletRequest("open=true");
request.setScheme("https");
request.setServerName("www.example.com");
request.setContextPath("/bigWebApp");
request.setServletPath("/hello");
request.setPathInfo("/pathInfo.html");
request.setServerPort(8768);
MockHttpServletResponse response = new MockHttpServletResponse();
RetryWithHttpEntryPoint ep = new RetryWithHttpEntryPoint();
ep.setPortMapper(new PortMapperImpl());
ep.setPortResolver(new MockPortResolver(8768, 1234));
ep.afterPropertiesSet();
ep.commence(request, response);
System.out.println(response.getRedirect());
assertEquals("/bigWebApp", response.getRedirect());
}
public void testOperationWithNonStandardPort() throws Exception {
MockHttpServletRequest request = new MockHttpServletRequest("open=true");
request.setScheme("https");
request.setServerName("www.example.com");
request.setContextPath("/bigWebApp");
request.setServletPath("/hello");
request.setPathInfo("/pathInfo.html");
request.setServerPort(9999);
MockHttpServletResponse response = new MockHttpServletResponse();
PortMapperImpl portMapper = new PortMapperImpl();
Map map = new HashMap();
map.put("8888", "9999");
portMapper.setPortMappings(map);
RetryWithHttpEntryPoint ep = new RetryWithHttpEntryPoint();
ep.setPortResolver(new MockPortResolver(8888, 9999));
ep.setPortMapper(portMapper);
ep.afterPropertiesSet();
ep.commence(request, response);
System.out.println(response.getRedirect());
assertEquals("http://www.example.com:8888/bigWebApp/hello/pathInfo.html?open=true",
response.getRedirect());
}
}

View File

@ -0,0 +1,171 @@
/* Copyright 2004 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
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.sf.acegisecurity.securechannel;
import junit.framework.TestCase;
import net.sf.acegisecurity.MockHttpServletRequest;
import net.sf.acegisecurity.MockHttpServletResponse;
import net.sf.acegisecurity.MockPortResolver;
import net.sf.acegisecurity.util.PortMapperImpl;
import java.util.HashMap;
import java.util.Map;
/**
* Tests {@link RetryWithHttpsEntryPoint}.
*
* @author Ben Alex
* @version $Id$
*/
public class RetryWithHttpsEntryPointTests extends TestCase {
//~ Methods ================================================================
public final void setUp() throws Exception {
super.setUp();
}
public static void main(String[] args) {
junit.textui.TestRunner.run(RetryWithHttpsEntryPointTests.class);
}
public void testDetectsMissingPortMapper() throws Exception {
RetryWithHttpsEntryPoint ep = new RetryWithHttpsEntryPoint();
ep.setPortResolver(new MockPortResolver(80, 443));
try {
ep.afterPropertiesSet();
fail("Should have thrown IllegalArgumentException");
} catch (IllegalArgumentException expected) {
assertEquals("portMapper is required", expected.getMessage());
}
}
public void testDetectsMissingPortResolver() throws Exception {
RetryWithHttpsEntryPoint ep = new RetryWithHttpsEntryPoint();
ep.setPortMapper(new PortMapperImpl());
try {
ep.afterPropertiesSet();
fail("Should have thrown IllegalArgumentException");
} catch (IllegalArgumentException expected) {
assertEquals("portResolver is required", expected.getMessage());
}
}
public void testGettersSetters() {
RetryWithHttpsEntryPoint ep = new RetryWithHttpsEntryPoint();
ep.setPortMapper(new PortMapperImpl());
ep.setPortResolver(new MockPortResolver(8080, 8443));
assertTrue(ep.getPortMapper() != null);
assertTrue(ep.getPortResolver() != null);
}
public void testNormalOperation() throws Exception {
MockHttpServletRequest request = new MockHttpServletRequest("open=true");
request.setScheme("http");
request.setServerName("www.example.com");
request.setContextPath("/bigWebApp");
request.setServletPath("/hello");
request.setPathInfo("/pathInfo.html");
request.setServerPort(80);
MockHttpServletResponse response = new MockHttpServletResponse();
RetryWithHttpsEntryPoint ep = new RetryWithHttpsEntryPoint();
ep.setPortMapper(new PortMapperImpl());
ep.setPortResolver(new MockPortResolver(80, 443));
ep.afterPropertiesSet();
ep.commence(request, response);
System.out.println(response.getRedirect());
assertEquals("https://www.example.com/bigWebApp/hello/pathInfo.html?open=true",
response.getRedirect());
}
public void testNormalOperationWithNullPathInfoAndNullQueryString()
throws Exception {
MockHttpServletRequest request = new MockHttpServletRequest(null);
request.setScheme("http");
request.setServerName("www.example.com");
request.setContextPath("/bigWebApp");
request.setServletPath("/hello");
request.setPathInfo(null);
request.setServerPort(80);
MockHttpServletResponse response = new MockHttpServletResponse();
RetryWithHttpsEntryPoint ep = new RetryWithHttpsEntryPoint();
ep.setPortMapper(new PortMapperImpl());
ep.setPortResolver(new MockPortResolver(80, 443));
ep.afterPropertiesSet();
ep.commence(request, response);
System.out.println(response.getRedirect());
assertEquals("https://www.example.com/bigWebApp/hello",
response.getRedirect());
}
public void testOperationWhenTargetPortIsUnknown()
throws Exception {
MockHttpServletRequest request = new MockHttpServletRequest("open=true");
request.setScheme("http");
request.setServerName("www.example.com");
request.setContextPath("/bigWebApp");
request.setServletPath("/hello");
request.setPathInfo("/pathInfo.html");
request.setServerPort(8768);
MockHttpServletResponse response = new MockHttpServletResponse();
RetryWithHttpsEntryPoint ep = new RetryWithHttpsEntryPoint();
ep.setPortMapper(new PortMapperImpl());
ep.setPortResolver(new MockPortResolver(8768, 1234));
ep.afterPropertiesSet();
ep.commence(request, response);
System.out.println(response.getRedirect());
assertEquals("/bigWebApp", response.getRedirect());
}
public void testOperationWithNonStandardPort() throws Exception {
MockHttpServletRequest request = new MockHttpServletRequest("open=true");
request.setScheme("http");
request.setServerName("www.example.com");
request.setContextPath("/bigWebApp");
request.setServletPath("/hello");
request.setPathInfo("/pathInfo.html");
request.setServerPort(8888);
MockHttpServletResponse response = new MockHttpServletResponse();
PortMapperImpl portMapper = new PortMapperImpl();
Map map = new HashMap();
map.put("8888", "9999");
portMapper.setPortMappings(map);
RetryWithHttpsEntryPoint ep = new RetryWithHttpsEntryPoint();
ep.setPortResolver(new MockPortResolver(8888, 9999));
ep.setPortMapper(portMapper);
ep.afterPropertiesSet();
ep.commence(request, response);
System.out.println(response.getRedirect());
assertEquals("https://www.example.com:9999/bigWebApp/hello/pathInfo.html?open=true",
response.getRedirect());
}
}

View File

@ -519,33 +519,23 @@
<para>Notice that the filter is actually a
<literal>FilterToBeanProxy</literal>. Most of the filters used by the
Acegi Security System for Spring use this class . What it does is
delegate the <literal>Filter</literal>'s methods through to a bean
which is obtained from the Spring application context. This enables
the bean to benefit from the Spring application context lifecycle
support and configuration flexibility.
<literal>FilterToBeanProxy</literal> only requires a single
initialization parameter, <literal>targetClass</literal> or
<literal>targetBean</literal>. The <literal>targetClass</literal>
parameter locates the first object in the application context of the
specified class, whilst <literal>targetBean</literal> locates the
object by bean name. Like standard Spring web applications, the
<literal>FilterToBeanProxy</literal> accesses the application context
via<literal>
WebApplicationContextUtils.getWebApplicationContext(ServletContext)</literal>,
so you should configure a <literal>ContextLoaderListener</literal> in
<literal>web.xml</literal>.</para>
Acegi Security System for Spring use this class. Refer to the Filters
section to learn more about this bean.</para>
<para>In the application context you will need to configure three
<para>In the application context you will need to configure four
beans:</para>
<programlisting>&lt;bean id="securityEnforcementFilter" class="net.sf.acegisecurity.intercept.web.SecurityEnforcementFilter"&gt;
&lt;property name="filterSecurityInterceptor"&gt;&lt;ref bean="filterInvocationInterceptor"/&gt;&lt;/property&gt;
&lt;property name="authenticationEntryPoint"&gt;&lt;ref bean="authenticationEntryPoint"/&gt;&lt;/property&gt;
&lt;property name="portResolver"&gt;&lt;ref bean="portResolver"/&gt;&lt;/property&gt;
&lt;/bean&gt;
&lt;bean id="authenticationEntryPoint" class="net.sf.acegisecurity.ui.webapp.AuthenticationProcessingFilterEntryPoint"&gt;
&lt;property name="loginFormUrl"&gt;&lt;value&gt;/acegilogin.jsp&lt;/value&gt;&lt;/property&gt;
&lt;property name="forceHttps"&gt;&lt;value&gt;false&lt;/value&gt;&lt;/property&gt;
&lt;property name="portResolver"&gt;&lt;ref bean="portResolver"/&gt;&lt;/property&gt;
&lt;property name="portMapper"&gt;&lt;ref bean="portMapper"/&gt;&lt;/property&gt;
&lt;/bean&gt;
&lt;bean id="filterInvocationInterceptor" class="net.sf.acegisecurity.intercept.web.FilterSecurityInterceptor"&gt;
@ -559,6 +549,12 @@
\A/secure/.*\Z=ROLE_SUPERVISOR,ROLE_TELLER
&lt;/value&gt;
&lt;/property&gt;
&lt;/bean&gt;
&lt;!-- Comment the always[Scheme]Port properties to use ServletRequest.getServerPort() --&gt;
&lt;bean id="portResolver" class="net.sf.acegisecurity.util.PortResolverImpl"&gt;
&lt;property name="alwaysHttpPort"&gt;&lt;value&gt;8080&lt;/value&gt;&lt;/property&gt;
&lt;property name="alwaysHttpsPort"&gt;&lt;value&gt;8443&lt;/value&gt;&lt;/property&gt;
&lt;/bean&gt;</programlisting>
<para>The <literal>AuthenticationEntryPoint</literal> will be called
@ -577,6 +573,21 @@
properties related to forcing the use of HTTPS, so please refer to the
JavaDocs if you require this.</para>
<para>The <literal>PortResolver</literal> is used to inspect a HTTP
request and determine the server port it was received on. Generally
this means using <literal>ServletRequest.getServerPort()</literal>,
although implementations can be forced to always return particular
ports (based on the transport protocol), as shown in the example
above. </para>
<para>The <literal>PortMapper</literal> provides information on which
HTTPS ports correspond to which HTTP ports. This is used by the
<literal>AuthenticationProcessingFilterEntryPoint</literal> and
several other beans. The default implementation,
<literal>PortMapperImpl</literal>, knows the common HTTP ports 80 and
8080 map to HTTPS ports 443 and 8443 respectively. You can customise
this mapping if desired.</para>
<para>The <literal>SecurityEnforcementFilter</literal> primarily
provides session management support and initiates authentication when
required. It delegates actual <literal>FilterInvocation</literal>
@ -1585,9 +1596,8 @@ public boolean supports(Class clazz);</programlisting></para>
&lt;/filter-mapping&gt;</programlisting></para>
<para>For a discussion of <literal>FilterToBeanProxy</literal>, please
refer to the FilterInvocation Security Interceptor section. The
application context will need to define the
<literal>AuthenticationProcessingFilter</literal>:</para>
refer to the Filters section. The application context will need to
define the <literal>AuthenticationProcessingFilter</literal>:</para>
<para><programlisting>&lt;bean id="authenticationProcessingFilter" class="net.sf.acegisecurity.ui.webapp.AuthenticationProcessingFilter"&gt;
&lt;property name="authenticationManager"&gt;&lt;ref bean="authenticationManager"/&gt;&lt;/property&gt;
@ -1661,9 +1671,8 @@ public boolean supports(Class clazz);</programlisting></para>
&lt;/filter-mapping&gt;</programlisting></para>
<para>For a discussion of <literal>FilterToBeanProxy</literal>, please
refer to the FilterInvocation Security Interceptor section. The
application context will need to define the
<literal>BasicProcessingFilter</literal> and its required
refer to the Filters section. The application context will need to
define the <literal>BasicProcessingFilter</literal> and its required
collaborator:</para>
<para><programlisting>&lt;bean id="basicProcessingFilter" class="net.sf.acegisecurity.ui.basicauth.BasicProcessingFilter"&gt;
@ -2739,6 +2748,245 @@ $CATALINA_HOME/bin/startup.sh</programlisting></para>
</sect2>
</sect1>
<sect1 id="security-channels">
<title>Channel Security</title>
<sect2 id="security-channels-overview">
<title>Overview</title>
<para>In addition to coordinating the authentication and authorization
requirements of your application, the Acegi Security System for Spring
is also able to ensure web requests are received using an appropriate
transport. If your application has many security requirements, you'll
probably want to use HTTPS as the transport, whilst less secure pages
can use the unencrypted HTTP transport.</para>
<para>An important issue in considering transport security is that of
session hijacking. Your web container manages a
<literal>HttpSession</literal> by reference to a
<literal>jsessionid</literal> that is sent to user agents either via a
cookie or URL rewriting. If the <literal>jsessionid</literal> is ever
sent over HTTP, there is a possibility that session identifier can be
intercepted and used to impersonate the user after they complete the
authentication process. This is because most web containers maintain
the same session identifier for a given user, even after they switch
from HTTP to HTTPS pages.</para>
<para>If session hijacking is considered too significant a risk for
your particular application, the only option is to use HTTPS for every
request. This means the <literal>jsessionid</literal> is never sent
across an insecure channel. You will need to ensure your
<literal>web.xml</literal>-defined
<literal>&lt;welcome-file&gt;</literal> points to a HTTPS location,
and the application never directs the user to a HTTP location. The
Acegi Security System for Spring provides a solution to assist with
the latter.</para>
</sect2>
<sect2 id="security-channels-installation">
<title>Configuration</title>
<para>To utilise Acegi Security's channel security services, add the
following lines to <literal>web.xml</literal>:</para>
<para><programlisting>&lt;filter&gt;
&lt;filter-name&gt;Acegi Channel Processing Filter&lt;/filter-name&gt;
&lt;filter-class&gt;net.sf.acegisecurity.util.FilterToBeanProxy&lt;/filter-class&gt;
&lt;init-param&gt;
&lt;param-name&gt;targetClass&lt;/param-name&gt;
&lt;param-value&gt;net.sf.acegisecurity.securechannel.ChannelProcessingFilter&lt;/param-value&gt;
&lt;/init-param&gt;
&lt;/filter&gt;
&lt;filter-mapping&gt;
&lt;filter-name&gt;Acegi Channel Processing Filter&lt;/filter-name&gt;
&lt;url-pattern&gt;/*&lt;/url-pattern&gt;
&lt;/filter-mapping&gt;</programlisting></para>
<para>As usual when running <literal>FilterToBeanProxy</literal>, you
will also need to configure the filter in your application
context:</para>
<para><programlisting>&lt;bean id="channelProcessingFilter" class="net.sf.acegisecurity.securechannel.ChannelProcessingFilter"&gt;
&lt;property name="channelDecisionManager"&gt;&lt;ref bean="channelDecisionManager"/&gt;&lt;/property&gt;
&lt;property name="secureChannelEntryPoint"&gt;&lt;ref bean="secureChannelEntryPoint"/&gt;&lt;/property&gt;
&lt;property name="insecureChannelEntryPoint"&gt;&lt;ref bean="insecureChannelEntryPoint"/&gt;&lt;/property&gt;
&lt;property name="filterInvocationDefinitionSource"&gt;
&lt;value&gt;
CONVERT_URL_TO_LOWERCASE_BEFORE_COMPARISON
\A/secure/.*\Z=REQUIRES_SECURE_CHANNEL
\A/acegilogin.jsp.*\Z=REQUIRES_SECURE_CHANNEL
\A/j_acegi_security_check.*\Z=REQUIRES_SECURE_CHANNEL
\A.*\Z=REQUIRES_INSECURE_CHANNEL
&lt;/value&gt;
&lt;/property&gt;
&lt;/bean&gt;
&lt;bean id="channelDecisionManager" class="net.sf.acegisecurity.securechannel.ChannelDecisionManagerImpl"/&gt;
&lt;bean id="secureChannelEntryPoint" class="net.sf.acegisecurity.securechannel.RetryWithHttpsEntryPoint"&gt;
&lt;property name="portMapper"&gt;&lt;ref bean="portMapper"/&gt;&lt;/property&gt;
&lt;property name="portResolver"&gt;&lt;ref bean="portResolver"/&gt;&lt;/property&gt;
&lt;/bean&gt;
&lt;bean id="insecureChannelEntryPoint" class="net.sf.acegisecurity.securechannel.RetryWithHttpEntryPoint"&gt;
&lt;property name="portMapper"&gt;&lt;ref bean="portMapper"/&gt;&lt;/property&gt;
&lt;property name="portResolver"&gt;&lt;ref bean="portResolver"/&gt;&lt;/property&gt;
&lt;/bean&gt;</programlisting></para>
<para>Like <literal>FilterSecurityInterceptor</literal>, Apache Ant
style paths are also supported by the
<literal>ChannelProcessingFilter</literal>.</para>
<para>The <literal>ChannelProcessingFilter</literal> operates by
filtering all web requests and determining the configuration
attributes that apply. It then delegates to the
<literal>ChannelDecisionManager</literal>. The default implementation,
<literal>ChannelDecisionManagerImpl</literal>, should suffice in most
cases. It simply throws a
<literal>SecureChannelRequiredException</literal> or
<literal>InsecureChannelRequiredException</literal> if the request's
transport channel carries too little or too much security
respectively. </para>
<para>The <literal>ChannelProcessingFilter</literal> will detect the
<literal>SecureChannelRequiredException</literal> or
<literal>InsecureChannelRequiredException</literal> and delegate to
the <literal>secureChannelEntryPoint</literal> or
<literal>insecureChannelEntryPoint</literal> respectively. These entry
points implement the <literal>ChannelEntryPoint</literal> interface,
which allows the implementation to perform a redirect or take similar
action. The included <literal>RetryWithHttpsEntryPoint</literal> and
<literal>RetryWithHttpEntryPoint</literal> implementations simply
perform a redirect.</para>
<para>Note that the redirections are absolute (eg
http://www.company.com:8080/app/page), not relative (eg /app/page).
During testing it was discovered that Internet Explorer 6 Service Pack
1 appears to have a bug whereby it does not respond correctly to a
redirection instruction which also changes the port to use.
Accordingly, absolute URLs are used in conjunction with the
<literal>PortResolver</literal> interface to overcome this issue. The
<literal>PortResolverImpl</literal> is the included implementation,
and is capable of determining the port a request was received on
either from the <literal>ServletRequest.getServerPort()</literal>
method or from properties defined in the application context. Please
refer to the JavaDocs for <literal>PortResolverImpl</literal> for
further details.</para>
</sect2>
<sect2 id="security-channels-usage">
<title>Usage</title>
<para>Once configured, using the channel security filter is very easy.
Simply request pages without regard to the protocol (ie HTTP or HTTPS)
or port (eg 80, 8080, 443, 8443 etc). Obviously you'll still need a
way of making the initial request (probably via the
<literal>web.xml</literal> <literal>&lt;welcome-file&gt;</literal> or
a well-known home page URL), but once this is done the filter will
perform redirects as defined by your application context.</para>
</sect2>
</sect1>
<sect1 id="security-filters">
<title>Filters</title>
<sect2 id="security-filters-overview">
<title>Overview</title>
<para>The Acegi Security System for Spring uses filters extensively.
Each filter is covered in detail in a respective section of this
document. This section includes information that applies to all
filters.</para>
</sect2>
<sect2 id="security-filters-filtertobeanproxy">
<title>FilterToBeanProxy</title>
<para>Most filters are configured using the
<literal>FilterToBeanProxy</literal>. An example configuration from
<literal>web.xml</literal> follows:</para>
<para><programlisting>&lt;filter&gt;
&lt;filter-name&gt;Acegi HTTP Request Security Filter&lt;/filter-name&gt;
&lt;filter-class&gt;net.sf.acegisecurity.util.FilterToBeanProxy&lt;/filter-class&gt;
&lt;init-param&gt;
&lt;param-name&gt;targetClass&lt;/param-name&gt;
&lt;param-value&gt;net.sf.acegisecurity.ClassThatImplementsFilter&lt;/param-value&gt;
&lt;/init-param&gt;
&lt;/filter&gt;</programlisting></para>
<para>Notice that the filter in <literal>web.xml</literal> is actually
a <literal>FilterToBeanProxy</literal>, and not the filter that will
actually implements the logic of the filter. What
<literal>FilterToBeanProxy</literal> does is delegate the
<literal>Filter</literal>'s methods through to a bean which is
obtained from the Spring application context. This enables the bean to
benefit from the Spring application context lifecycle support and
configuration flexibility. The bean must implement
<literal>javax.servlet.Filter</literal>.</para>
<para>The <literal>FilterToBeanProxy</literal> only requires a single
initialization parameter, <literal>targetClass</literal> or
<literal>targetBean</literal>. The <literal>targetClass</literal>
parameter locates the first object in the application context of the
specified class, whilst <literal>targetBean</literal> locates the
object by bean name. Like standard Spring web applications, the
<literal>FilterToBeanProxy</literal> accesses the application context
via<literal>
WebApplicationContextUtils.getWebApplicationContext(ServletContext)</literal>,
so you should configure a <literal>ContextLoaderListener</literal> in
<literal>web.xml</literal>.</para>
</sect2>
<sect2 id="security-filters-order">
<title>Filter Ordering</title>
<para>The order that filters are defined in <literal>web.xml</literal>
is important.</para>
<para>Irrespective of which filters you are actually using, the order
of the <literal>&lt;filter-mapping&gt;</literal>s should be as
follows:</para>
<orderedlist>
<listitem>
<para>Acegi Channel Processing Filter
(<literal>ChannelProcessingFilter</literal>)</para>
</listitem>
<listitem>
<para>Acegi Authentication Processing Filter
(<literal>AuthenticationProcessingFilter</literal>)</para>
</listitem>
<listitem>
<para>Acegi CAS Processing Filter
(<literal>CasProcessingFilter</literal>)</para>
</listitem>
<listitem>
<para>Acegi HTTP BASIC Authorization Filter
(<literal>BasicProcessingFilter</literal>)</para>
</listitem>
<listitem>
<para>Acegi Security System for Spring Auto Integration Filter
(<literal>AutoIntegrationFilter</literal>)</para>
</listitem>
<listitem>
<para>Acegi HTTP Request Security Filter
(<literal>SecurityEnforcementFilter</literal>)</para>
</listitem>
</orderedlist>
<para>All of the above filters use
<literal>FilterToBeanProxy</literal>, which is discussed in the
previous section.</para>
</sect2>
</sect1>
<sect1 id="security-sample">
<title>Contacts Sample Application</title>

View File

@ -159,6 +159,34 @@
<bean id="backendContactManagerTarget" class="sample.contact.ContactManagerBackend"/>
<!-- ===================== HTTP CHANNEL REQUIREMENTS ==================== -->
<bean id="channelProcessingFilter" class="net.sf.acegisecurity.securechannel.ChannelProcessingFilter">
<property name="channelDecisionManager"><ref bean="channelDecisionManager"/></property>
<property name="secureChannelEntryPoint"><ref bean="secureChannelEntryPoint"/></property>
<property name="insecureChannelEntryPoint"><ref bean="insecureChannelEntryPoint"/></property>
<property name="filterInvocationDefinitionSource">
<value>
CONVERT_URL_TO_LOWERCASE_BEFORE_COMPARISON
\A/secure/.*\Z=REQUIRES_SECURE_CHANNEL
\A/j_acegi_cas_security_check.*\Z=REQUIRES_SECURE_CHANNEL
\A.*\Z=REQUIRES_INSECURE_CHANNEL
</value>
</property>
</bean>
<bean id="channelDecisionManager" class="net.sf.acegisecurity.securechannel.ChannelDecisionManagerImpl"/>
<bean id="secureChannelEntryPoint" class="net.sf.acegisecurity.securechannel.RetryWithHttpsEntryPoint">
<property name="portMapper"><ref bean="portMapper"/></property>
<property name="portResolver"><ref bean="portResolver"/></property>
</bean>
<bean id="insecureChannelEntryPoint" class="net.sf.acegisecurity.securechannel.RetryWithHttpEntryPoint">
<property name="portMapper"><ref bean="portMapper"/></property>
<property name="portResolver"><ref bean="portResolver"/></property>
</bean>
<!-- ===================== HTTP REQUEST SECURITY ==================== -->
<bean id="casProcessingFilter" class="net.sf.acegisecurity.ui.cas.CasProcessingFilter">
@ -171,6 +199,7 @@
<bean id="securityEnforcementFilter" class="net.sf.acegisecurity.intercept.web.SecurityEnforcementFilter">
<property name="filterSecurityInterceptor"><ref bean="filterInvocationInterceptor"/></property>
<property name="authenticationEntryPoint"><ref bean="casProcessingFilterEntryPoint"/></property>
<property name="portResolver"><ref bean="portResolver"/></property>
</bean>
<bean id="casProcessingFilterEntryPoint" class="net.sf.acegisecurity.ui.cas.CasProcessingFilterEntryPoint">
@ -178,6 +207,14 @@
<property name="serviceProperties"><ref bean="serviceProperties"/></property>
</bean>
<bean id="portMapper" class="net.sf.acegisecurity.util.PortMapperImpl"/>
<!-- Comment the always[Scheme]Port properties to use ServletRequest.getServerPort() -->
<bean id="portResolver" class="net.sf.acegisecurity.util.PortResolverImpl">
<property name="alwaysHttpPort"><value>8080</value></property>
<property name="alwaysHttpsPort"><value>8443</value></property>
</bean>
<bean id="httpRequestAccessDecisionManager" class="net.sf.acegisecurity.vote.AffirmativeBased">
<property name="allowIfAllAbstainDecisions"><value>false</value></property>
<property name="decisionVoters">

View File

@ -33,6 +33,15 @@
<param-value>/WEB-INF/applicationContext.xml</param-value>
</context-param>
<filter>
<filter-name>Acegi Channel Processing Filter</filter-name>
<filter-class>net.sf.acegisecurity.util.FilterToBeanProxy</filter-class>
<init-param>
<param-name>targetClass</param-name>
<param-value>net.sf.acegisecurity.securechannel.ChannelProcessingFilter</param-value>
</init-param>
</filter>
<filter>
<filter-name>Acegi CAS Processing Filter</filter-name>
<filter-class>net.sf.acegisecurity.util.FilterToBeanProxy</filter-class>
@ -65,6 +74,11 @@
</init-param>
</filter>
<filter-mapping>
<filter-name>Acegi Channel Processing Filter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
<filter-mapping>
<filter-name>Acegi CAS Processing Filter</filter-name>
<url-pattern>/*</url-pattern>

View File

@ -135,22 +135,35 @@
<!-- ===================== HTTP CHANNEL REQUIREMENTS ==================== -->
<!-- You will need to uncomment the "Acegi Channel Processing Filter"
<filter-mapping> in web.xml for the following beans to be used -->
<bean id="channelProcessingFilter" class="net.sf.acegisecurity.securechannel.ChannelProcessingFilter">
<property name="channelDecisionManager"><ref bean="channelDecisionManager"/></property>
<property name="channelEntryPoint"><ref bean="channelEntryPoint"/></property>
<property name="secureChannelEntryPoint"><ref bean="secureChannelEntryPoint"/></property>
<property name="insecureChannelEntryPoint"><ref bean="insecureChannelEntryPoint"/></property>
<property name="filterInvocationDefinitionSource">
<value>
CONVERT_URL_TO_LOWERCASE_BEFORE_COMPARISON
\A/secure/.*\Z=REQUIRES_SECURE_CHANNEL
\A/info/.*\Z=REQUIRES_SECURE_CHANNEL
\A/acegilogin.jsp.*\Z=REQUIRES_SECURE_CHANNEL
\A/j_acegi_security_check.*\Z=REQUIRES_SECURE_CHANNEL
\A.*\Z=REQUIRES_INSECURE_CHANNEL
</value>
</property>
</bean>
<bean id="channelDecisionManager" class="net.sf.acegisecurity.securechannel.ChannelDecisionManagerImpl"/>
<bean id="channelEntryPoint" class="net.sf.acegisecurity.securechannel.RetryWithHttpsEntryPoint"/>
<bean id="secureChannelEntryPoint" class="net.sf.acegisecurity.securechannel.RetryWithHttpsEntryPoint">
<property name="portMapper"><ref bean="portMapper"/></property>
<property name="portResolver"><ref bean="portResolver"/></property>
</bean>
<bean id="insecureChannelEntryPoint" class="net.sf.acegisecurity.securechannel.RetryWithHttpEntryPoint">
<property name="portMapper"><ref bean="portMapper"/></property>
<property name="portResolver"><ref bean="portResolver"/></property>
</bean>
<!-- ===================== HTTP REQUEST SECURITY ==================== -->
@ -164,10 +177,22 @@
<bean id="securityEnforcementFilter" class="net.sf.acegisecurity.intercept.web.SecurityEnforcementFilter">
<property name="filterSecurityInterceptor"><ref bean="filterInvocationInterceptor"/></property>
<property name="authenticationEntryPoint"><ref bean="authenticationProcessingFilterEntryPoint"/></property>
<property name="portResolver"><ref bean="portResolver"/></property>
</bean>
<bean id="authenticationProcessingFilterEntryPoint" class="net.sf.acegisecurity.ui.webapp.AuthenticationProcessingFilterEntryPoint">
<property name="loginFormUrl"><value>/acegilogin.jsp</value></property>
<property name="forceHttps"><value>false</value></property>
<property name="portMapper"><ref bean="portMapper"/></property>
<property name="portResolver"><ref bean="portResolver"/></property>
</bean>
<bean id="portMapper" class="net.sf.acegisecurity.util.PortMapperImpl"/>
<!-- Comment the always[Scheme]Port properties to use ServletRequest.getServerPort() -->
<bean id="portResolver" class="net.sf.acegisecurity.util.PortResolverImpl">
<property name="alwaysHttpPort"><value>8080</value></property>
<property name="alwaysHttpsPort"><value>8443</value></property>
</bean>
<bean id="httpRequestAccessDecisionManager" class="net.sf.acegisecurity.vote.AffirmativeBased">

View File

@ -65,10 +65,14 @@
</init-param>
</filter>
<!-- Remove the comments from the following <filter-mapping> if you'd
like to ensure secure URLs are only available over HTTPS -->
<!--
<filter-mapping>
<filter-name>Acegi Channel Processing Filter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
-->
<filter-mapping>
<filter-name>Acegi Authentication Processing Filter</filter-name>