Add support for HTTP Basic Authentication.

This commit is contained in:
Ben Alex 2004-04-11 12:09:08 +00:00
parent 670d007630
commit f1abf780b5
9 changed files with 858 additions and 34 deletions

View File

@ -1,6 +1,8 @@
Changes in version 0.5 (2004-xx-xx) Changes in version 0.5 (2004-xx-xx)
----------------------------------- -----------------------------------
* Added support for HTTP Basic Authentication
* Added Burlap and Hessian remoting to Contacts sample application
* AuthenticationProcessingFilter by default finds configuration context using Spring's WebApplicationContextUtils.getWebApplicationContext() * AuthenticationProcessingFilter by default finds configuration context using Spring's WebApplicationContextUtils.getWebApplicationContext()
* AuthenticationProcessingFilter context may optionally be specified with 'contextConfigLocation' param (was previously 'appContextLocation') * AuthenticationProcessingFilter context may optionally be specified with 'contextConfigLocation' param (was previously 'appContextLocation')
* SecurityEnforcementFilter by default finds configuration context using Spring's WebApplicationContextUtils.getWebApplicationContext() * SecurityEnforcementFilter by default finds configuration context using Spring's WebApplicationContextUtils.getWebApplicationContext()

View File

@ -0,0 +1,249 @@
/* 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.ui.basicauth;
import net.sf.acegisecurity.Authentication;
import net.sf.acegisecurity.AuthenticationException;
import net.sf.acegisecurity.AuthenticationManager;
import net.sf.acegisecurity.providers.UsernamePasswordAuthenticationToken;
import net.sf.acegisecurity.ui.webapp.HttpSessionIntegrationFilter;
import org.apache.commons.codec.binary.Base64;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.web.context.support.WebApplicationContextUtils;
import java.io.IOException;
import java.util.Map;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/**
* Processes a HTTP request's BASIC authorization headers, putting the result
* into the <code>HttpSession</code>.
*
* <P>
* For a detailed background on what this filter is designed to process, refer
* to <A HREF="http://www.faqs.org/rfcs/rfc1945.html">RFC 1945, Section
* 11.1</A>. Any realm name presented in the HTTP request is ignored.
* </p>
*
* <p>
* In summary, this filter is responsible for processing any request that has a
* HTTP request header of <code>Authorization</code> with an authentication
* scheme of <code>Basic</code> and a Base64-encoded
* <code>username:password</code> token. For example, to authenticate user
* "Aladdin" with password "open sesame" the following header would be
* presented:
* </p>
*
* <p>
* <code>Authorization: Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ==</code>.
* </p>
*
* <p>
* Requests containing BASIC authentication headers are generally created by
* remoting protocol libraries. This filter is intended to process requests
* made by such libraries.
* </p>
*
* <P>
* If authentication is successful, the resulting {@link Authentication} object
* will be placed into the <code>HttpSession</code> with the attribute defined
* by {@link HttpSessionIntegrationFilter#ACEGI_SECURITY_AUTHENTICATION_KEY}.
* </p>
*
* <p>
* If authentication fails, a <code>HttpServletResponse.SC_FORBIDDEN</code>
* (403 error) response is sent. This is consistent with RFC 1945, Section 11,
* which states, "<I>If the server does not wish to accept the credentials
* sent with a request, it should return a 403 (forbidden) response.</I>".
* </p>
*
* <p>
* This filter works with an {@link AuthenticationManager} which is used to
* process each authentication request. By default, at init time, the filter
* will use Spring's {@link
* WebApplicationContextUtils#getWebApplicationContext(ServletContext sc)}
* method to obtain an ApplicationContext instance, inside which must be a
* configured AuthenticationManager instance. In the case where it is
* desirable for this filter to instantiate its own ApplicationContext
* instance from which to obtain the AuthenticationManager, the location of
* the config for this context may be specified with the optional
* <code>contextConfigLocation</code> init param.
* </p>
*
* <p>
* To use this filter, it is necessary to specify the following filter
* initialization parameters:
* </p>
*
* <ul>
* <li>
* <code>contextConfigLocation</code> (optional, normally not used), indicates
* the path to an application context that contains an {@link
* AuthenticationManager} which should be used to process each authentication
* request. If not specified, {@link
* WebApplicationContextUtils#getWebApplicationContext(ServletContext sc)}
* will be used to obtain the context.
* </li>
* </ul>
*
*
* @author Ben Alex
* @version $Id$
*/
public class BasicProcessingFilter implements Filter {
//~ Static fields/initializers =============================================
/**
* Name of (optional) servlet filter parameter that can specify the config
* location for a new ApplicationContext used to config this filter.
*/
public static final String CONFIG_LOCATION_PARAM = "contextConfigLocation";
private static final Log logger = LogFactory.getLog(BasicProcessingFilter.class);
//~ Instance fields ========================================================
private ApplicationContext ctx;
private AuthenticationManager authenticationManager;
private boolean ourContext = false;
//~ Methods ================================================================
public void destroy() {
if (ourContext && ctx instanceof ConfigurableApplicationContext) {
((ConfigurableApplicationContext) ctx).close();
}
}
public void doFilter(ServletRequest request, ServletResponse response,
FilterChain chain) throws IOException, ServletException {
if (!(request instanceof HttpServletRequest)) {
throw new ServletException("Can only process HttpServletRequest");
}
if (!(response instanceof HttpServletResponse)) {
throw new ServletException("Can only process HttpServletResponse");
}
HttpServletRequest httpRequest = (HttpServletRequest) request;
HttpServletResponse httpResponse = (HttpServletResponse) response;
String header = httpRequest.getHeader("Authorization");
if (logger.isDebugEnabled()) {
logger.debug("Authorization header: " + header);
}
if ((header != null) && header.startsWith("Basic ")) {
String base64Token = header.substring(6);
String token = new String(Base64.decodeBase64(
base64Token.getBytes()));
String username = "";
String password = "";
int delim = token.indexOf(":");
if (delim != -1) {
username = token.substring(0, delim);
password = token.substring(delim + 1);
}
UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(username,
password);
Authentication authResult;
try {
authResult = authenticationManager.authenticate(authRequest);
} catch (AuthenticationException failed) {
// Authentication failed
if (logger.isDebugEnabled()) {
logger.debug("Authentication request for user: " + username
+ " failed: " + failed.toString());
}
((HttpServletResponse) response).sendError(HttpServletResponse.SC_FORBIDDEN); // 403
return;
}
// Authentication success
if (logger.isDebugEnabled()) {
logger.debug("Authentication success: " + authResult.toString());
}
httpRequest.getSession().setAttribute(HttpSessionIntegrationFilter.ACEGI_SECURITY_AUTHENTICATION_KEY,
authResult);
}
chain.doFilter(request, response);
}
public void init(FilterConfig filterConfig) throws ServletException {
String appContextLocation = filterConfig.getInitParameter(CONFIG_LOCATION_PARAM);
if ((appContextLocation != null) && (appContextLocation.length() > 0)) {
ourContext = true;
if (Thread.currentThread().getContextClassLoader().getResource(appContextLocation) == null) {
throw new ServletException("Cannot locate "
+ appContextLocation);
}
}
try {
if (!ourContext) {
ctx = WebApplicationContextUtils
.getRequiredWebApplicationContext(filterConfig
.getServletContext());
} else {
ctx = new ClassPathXmlApplicationContext(appContextLocation);
}
} catch (RuntimeException e) {
throw new ServletException(
"Error obtaining/creating ApplicationContext for config. Must be stored in ServletContext, or optionally '"
+ CONFIG_LOCATION_PARAM
+ "' param may be used to allow creation of new context by this filter. See root error for additional details",
e);
}
Map beans = ctx.getBeansOfType(AuthenticationManager.class, true, true);
if (beans.size() == 0) {
throw new ServletException(
"Bean context must contain at least one bean of type AuthenticationManager");
}
String beanName = (String) beans.keySet().iterator().next();
authenticationManager = (AuthenticationManager) beans.get(beanName);
}
}

View File

@ -0,0 +1,5 @@
<html>
<body>
Authenticates HTTP BASIC authentication requests.
</body>
</html>

View File

@ -48,7 +48,8 @@ public class MockHttpServletRequest implements HttpServletRequest {
//~ Instance fields ======================================================== //~ Instance fields ========================================================
private HttpSession session; private HttpSession session;
private Map map = new HashMap(); private Map paramMap = new HashMap();
private Map headersMap = new HashMap();
private Principal principal; private Principal principal;
private String contextPath = ""; private String contextPath = "";
private String queryString = null; private String queryString = null;
@ -69,6 +70,12 @@ public class MockHttpServletRequest implements HttpServletRequest {
super(); super();
} }
public MockHttpServletRequest(Map headers, Principal principal, HttpSession session) {
this.headersMap = headers;
this.principal = principal;
this.session = session;
}
//~ Methods ================================================================ //~ Methods ================================================================
public void setAttribute(String arg0, Object arg1) { public void setAttribute(String arg0, Object arg1) {
@ -120,7 +127,13 @@ public class MockHttpServletRequest implements HttpServletRequest {
} }
public String getHeader(String arg0) { public String getHeader(String arg0) {
throw new UnsupportedOperationException("mock method not implemented"); Object result = headersMap.get(arg0);
if (result != null) {
return (String) headersMap.get(arg0);
} else {
return null;
}
} }
public Enumeration getHeaderNames() { public Enumeration getHeaderNames() {
@ -152,14 +165,14 @@ public class MockHttpServletRequest implements HttpServletRequest {
} }
public void setParameter(String arg0, String value) { public void setParameter(String arg0, String value) {
map.put(arg0, value); paramMap.put(arg0, value);
} }
public String getParameter(String arg0) { public String getParameter(String arg0) {
Object result = map.get(arg0); Object result = paramMap.get(arg0);
if (result != null) { if (result != null) {
return (String) map.get(arg0); return (String) paramMap.get(arg0);
} else { } else {
return null; return null;
} }

View File

@ -0,0 +1,376 @@
/* 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.ui.basicauth;
import junit.framework.TestCase;
import net.sf.acegisecurity.Authentication;
import net.sf.acegisecurity.MockFilterConfig;
import net.sf.acegisecurity.MockHttpServletRequest;
import net.sf.acegisecurity.MockHttpServletResponse;
import net.sf.acegisecurity.MockHttpSession;
import net.sf.acegisecurity.ui.webapp.HttpSessionIntegrationFilter;
import org.apache.commons.codec.binary.Base64;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
/**
* Tests {@link BasicProcessingFilter}.
*
* @author Ben Alex
* @version $Id$
*/
public class BasicProcessingFilterTests extends TestCase {
//~ Constructors ===========================================================
public BasicProcessingFilterTests() {
super();
}
public BasicProcessingFilterTests(String arg0) {
super(arg0);
}
//~ Methods ================================================================
public final void setUp() throws Exception {
super.setUp();
}
public static void main(String[] args) {
junit.textui.TestRunner.run(BasicProcessingFilterTests.class);
}
public void testDoFilterWithNonHttpServletRequestDetected()
throws Exception {
BasicProcessingFilter filter = new BasicProcessingFilter();
try {
filter.doFilter(null, new MockHttpServletResponse(),
new MockFilterChain());
fail("Should have thrown ServletException");
} catch (ServletException expected) {
assertEquals("Can only process HttpServletRequest",
expected.getMessage());
}
}
public void testDoFilterWithNonHttpServletResponseDetected()
throws Exception {
BasicProcessingFilter filter = new BasicProcessingFilter();
try {
filter.doFilter(new MockHttpServletRequest(null, null), null,
new MockFilterChain());
fail("Should have thrown ServletException");
} catch (ServletException expected) {
assertEquals("Can only process HttpServletResponse",
expected.getMessage());
}
}
public void testFilterIgnoresRequestsContainingNoAuthorizationHeader()
throws Exception {
// Setup our HTTP request
Map headers = new HashMap();
MockHttpServletRequest request = new MockHttpServletRequest(headers,
null, new MockHttpSession());
request.setServletPath("/some_file.html");
// Setup our filter configuration
MockFilterConfig config = new MockFilterConfig();
config.setInitParmeter("contextConfigLocation",
"net/sf/acegisecurity/ui/webapp/filtertest-valid.xml");
// Setup our expectation that the filter chain will be invoked
MockFilterChain chain = new MockFilterChain(true);
MockHttpServletResponse response = new MockHttpServletResponse();
// Test
BasicProcessingFilter filter = new BasicProcessingFilter();
executeFilterInContainerSimulator(config, filter, request, response,
chain);
assertTrue(request.getSession().getAttribute(HttpSessionIntegrationFilter.ACEGI_SECURITY_AUTHENTICATION_KEY) == null);
}
public void testInvalidBasicAuthorizationTokenIsIgnored()
throws Exception {
// Setup our HTTP request
Map headers = new HashMap();
String token = "NOT_A_VALID_TOKEN_AS_MISSING_COLON";
headers.put("Authorization",
"Basic " + new String(Base64.encodeBase64(token.getBytes())));
MockHttpServletRequest request = new MockHttpServletRequest(headers,
null, new MockHttpSession());
request.setServletPath("/some_file.html");
// Setup our filter configuration
MockFilterConfig config = new MockFilterConfig();
config.setInitParmeter("contextConfigLocation",
"net/sf/acegisecurity/ui/webapp/filtertest-valid.xml");
// Setup our expectation that the filter chain will be invoked
MockFilterChain chain = new MockFilterChain(true);
MockHttpServletResponse response = new MockHttpServletResponse();
// Test
BasicProcessingFilter filter = new BasicProcessingFilter();
executeFilterInContainerSimulator(config, filter, request, response,
chain);
assertTrue(request.getSession().getAttribute(HttpSessionIntegrationFilter.ACEGI_SECURITY_AUTHENTICATION_KEY) == null);
}
public void testNormalOperation() throws Exception {
// Setup our HTTP request
Map headers = new HashMap();
String token = "marissa:koala";
headers.put("Authorization",
"Basic " + new String(Base64.encodeBase64(token.getBytes())));
MockHttpServletRequest request = new MockHttpServletRequest(headers,
null, new MockHttpSession());
request.setServletPath("/some_file.html");
// Setup our filter configuration
MockFilterConfig config = new MockFilterConfig();
config.setInitParmeter("contextConfigLocation",
"net/sf/acegisecurity/ui/webapp/filtertest-valid.xml");
// Setup our expectation that the filter chain will be invoked
MockFilterChain chain = new MockFilterChain(true);
MockHttpServletResponse response = new MockHttpServletResponse();
// Test
BasicProcessingFilter filter = new BasicProcessingFilter();
executeFilterInContainerSimulator(config, filter, request, response,
chain);
assertTrue(request.getSession().getAttribute(HttpSessionIntegrationFilter.ACEGI_SECURITY_AUTHENTICATION_KEY) != null);
assertEquals("marissa",
((Authentication) request.getSession().getAttribute(HttpSessionIntegrationFilter.ACEGI_SECURITY_AUTHENTICATION_KEY)).getPrincipal()
.toString());
}
public void testOtherAuthorizationSchemeIsIgnored()
throws Exception {
// Setup our HTTP request
Map headers = new HashMap();
headers.put("Authorization", "SOME_OTHER_AUTHENTICATION_SCHEME");
MockHttpServletRequest request = new MockHttpServletRequest(headers,
null, new MockHttpSession());
request.setServletPath("/some_file.html");
// Setup our filter configuration
MockFilterConfig config = new MockFilterConfig();
config.setInitParmeter("contextConfigLocation",
"net/sf/acegisecurity/ui/webapp/filtertest-valid.xml");
// Setup our expectation that the filter chain will be invoked
MockFilterChain chain = new MockFilterChain(true);
MockHttpServletResponse response = new MockHttpServletResponse();
// Test
BasicProcessingFilter filter = new BasicProcessingFilter();
executeFilterInContainerSimulator(config, filter, request, response,
chain);
assertTrue(request.getSession().getAttribute(HttpSessionIntegrationFilter.ACEGI_SECURITY_AUTHENTICATION_KEY) == null);
}
public void testStartupDetectsInvalidContextConfigLocation()
throws Exception {
MockFilterConfig config = new MockFilterConfig();
config.setInitParmeter("contextConfigLocation",
"net/sf/acegisecurity/ui/webapp/filtertest-invalid.xml");
BasicProcessingFilter filter = new BasicProcessingFilter();
try {
filter.init(config);
fail("Should have thrown ServletException");
} catch (ServletException expected) {
assertEquals("Bean context must contain at least one bean of type AuthenticationManager",
expected.getMessage());
}
}
public void testStartupDetectsMissingAppContext() throws Exception {
MockFilterConfig config = new MockFilterConfig();
BasicProcessingFilter filter = new BasicProcessingFilter();
try {
filter.init(config);
fail("Should have thrown ServletException");
} catch (ServletException expected) {
assertTrue(expected.getMessage().startsWith("Error obtaining/creating ApplicationContext for config."));
}
config.setInitParmeter("contextConfigLocation", "");
try {
filter.init(config);
fail("Should have thrown ServletException");
} catch (ServletException expected) {
assertTrue(expected.getMessage().startsWith("Error obtaining/creating ApplicationContext for config."));
}
}
public void testStartupDetectsMissingInvalidContextConfigLocation()
throws Exception {
MockFilterConfig config = new MockFilterConfig();
config.setInitParmeter("contextConfigLocation", "DOES_NOT_EXIST");
BasicProcessingFilter filter = new BasicProcessingFilter();
try {
filter.init(config);
fail("Should have thrown ServletException");
} catch (ServletException expected) {
assertTrue(expected.getMessage().startsWith("Cannot locate"));
}
}
public void testSuccessLoginThenFailureLoginResultsInSessionLoosingToken()
throws Exception {
// Setup our HTTP request
Map headers = new HashMap();
String token = "marissa:koala";
headers.put("Authorization",
"Basic " + new String(Base64.encodeBase64(token.getBytes())));
MockHttpServletRequest request = new MockHttpServletRequest(headers,
null, new MockHttpSession());
request.setServletPath("/some_file.html");
// Setup our filter configuration
MockFilterConfig config = new MockFilterConfig();
config.setInitParmeter("contextConfigLocation",
"net/sf/acegisecurity/ui/webapp/filtertest-valid.xml");
// Setup our expectation that the filter chain will be invoked
MockFilterChain chain = new MockFilterChain(true);
MockHttpServletResponse response = new MockHttpServletResponse();
// Test
BasicProcessingFilter filter = new BasicProcessingFilter();
executeFilterInContainerSimulator(config, filter, request, response,
chain);
assertTrue(request.getSession().getAttribute(HttpSessionIntegrationFilter.ACEGI_SECURITY_AUTHENTICATION_KEY) != null);
assertEquals("marissa",
((Authentication) request.getSession().getAttribute(HttpSessionIntegrationFilter.ACEGI_SECURITY_AUTHENTICATION_KEY)).getPrincipal()
.toString());
// NOW PERFORM FAILED AUTHENTICATION
// Setup our HTTP request
headers = new HashMap();
token = "marissa:WRONG_PASSWORD";
headers.put("Authorization",
"Basic " + new String(Base64.encodeBase64(token.getBytes())));
request = new MockHttpServletRequest(headers, null,
new MockHttpSession());
request.setServletPath("/some_file.html");
// Setup our expectation that the filter chain will not be invoked, as we get a 403 forbidden response
chain = new MockFilterChain(false);
response = new MockHttpServletResponse();
// Test
filter = new BasicProcessingFilter();
executeFilterInContainerSimulator(config, filter, request, response,
chain);
assertTrue(request.getSession().getAttribute(HttpSessionIntegrationFilter.ACEGI_SECURITY_AUTHENTICATION_KEY) == null);
assertEquals(403, response.getError());
}
public void testWrongPasswordReturnsForbidden() throws Exception {
// Setup our HTTP request
Map headers = new HashMap();
String token = "marissa:WRONG_PASSWORD";
headers.put("Authorization",
"Basic " + new String(Base64.encodeBase64(token.getBytes())));
MockHttpServletRequest request = new MockHttpServletRequest(headers,
null, new MockHttpSession());
request.setServletPath("/some_file.html");
// Setup our filter configuration
MockFilterConfig config = new MockFilterConfig();
config.setInitParmeter("contextConfigLocation",
"net/sf/acegisecurity/ui/webapp/filtertest-valid.xml");
// Setup our expectation that the filter chain will not be invoked, as we get a 403 forbidden response
MockFilterChain chain = new MockFilterChain(false);
MockHttpServletResponse response = new MockHttpServletResponse();
// Test
BasicProcessingFilter filter = new BasicProcessingFilter();
executeFilterInContainerSimulator(config, filter, request, response,
chain);
assertTrue(request.getSession().getAttribute(HttpSessionIntegrationFilter.ACEGI_SECURITY_AUTHENTICATION_KEY) == null);
assertEquals(403, response.getError());
}
private void executeFilterInContainerSimulator(FilterConfig filterConfig,
Filter filter, ServletRequest request, ServletResponse response,
FilterChain filterChain) throws ServletException, IOException {
filter.init(filterConfig);
filter.doFilter(request, response, filterChain);
filter.destroy();
}
//~ Inner Classes ==========================================================
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");
}
}
}
}

View File

@ -281,15 +281,6 @@
custom request contexts implement the <literal>SecureContext</literal> custom request contexts implement the <literal>SecureContext</literal>
interface.</para> interface.</para>
</sect2> </sect2>
<sect2 id="security-contexts-future-work">
<title>Future Work</title>
<para>Over time it is hoped that the Spring remoting classes can be
extended to support propagation of the <literal>Context</literal>
between <literal>ContextHolder</literal>s on the client and
server.</para>
</sect2>
</sect1> </sect1>
<sect1 id="security-interception"> <sect1 id="security-interception">
@ -618,7 +609,7 @@
expressions to be treated as Apache Ant paths. It is not possible to expressions to be treated as Apache Ant paths. It is not possible to
mix expression syntaxes within the same definition. For example, the mix expression syntaxes within the same definition. For example, the
earlier configuration could be generated using Apache Ant paths as earlier configuration could be generated using Apache Ant paths as
follows: </para> follows:</para>
<para><programlisting>&lt;bean id="filterInvocationInterceptor" class="net.sf.acegisecurity.intercept.web.FilterSecurityInterceptor"&gt; <para><programlisting>&lt;bean id="filterInvocationInterceptor" class="net.sf.acegisecurity.intercept.web.FilterSecurityInterceptor"&gt;
&lt;property name="authenticationManager"&gt;&lt;ref bean="authenticationManager"/&gt;&lt;/property&gt; &lt;property name="authenticationManager"&gt;&lt;ref bean="authenticationManager"/&gt;&lt;/property&gt;
@ -642,8 +633,8 @@
pattern appears higher than the less specific pattern appears higher than the less specific
<literal>/super/</literal> pattern. If they were reversed, the <literal>/super/</literal> pattern. If they were reversed, the
<literal>/super/</literal> pattern would always match and the <literal>/super/</literal> pattern would always match and the
<literal>/secure/super/</literal> pattern would never be evaluated. <literal>/secure/super/</literal> pattern would never be
</para> evaluated.</para>
<para>The special keyword <para>The special keyword
<literal>CONVERT_URL_TO_LOWERCASE_BEFORE_COMPARISON</literal> causes <literal>CONVERT_URL_TO_LOWERCASE_BEFORE_COMPARISON</literal> causes
@ -1477,13 +1468,17 @@ public boolean supports(Class clazz);</programlisting></para>
</listitem> </listitem>
</itemizedlist></para> </itemizedlist></para>
<para>Several alternatives are available for the first step. The two <para>There are several alternatives are available for the first step,
most supported approaches are HTTP Session Authentication, which uses which will be briefly discussed in this chapter. The most popular
the <literal>HttpSession</literal> object and filters to authenticate approach is HTTP Session Authentication, which uses the
the user. The other is via Container Adapters, which allow supported <literal>HttpSession</literal> object and filters to authenticate the
web containers to perform the authentication themselves. HTTP Session user. Another approach is HTTP Basic Authentication, which allows
Authentication is discussed below, whilst Container Adapters are clients to use HTTP headers to present authentication information to
discussed in a separate section.</para> the Acegi Security System for Spring. The final approach is via
Container Adapters, which allow supported web containers to perform
the authentication themselves. HTTP Session Authentication is
discussed below, whilst Container Adapters are discussed in a separate
section.</para>
</sect2> </sect2>
<sect2 id="security-ui-http-session"> <sect2 id="security-ui-http-session">
@ -1569,11 +1564,74 @@ public boolean supports(Class clazz);</programlisting></para>
be used instead of Container Adapters.</para> be used instead of Container Adapters.</para>
</sect2> </sect2>
<sect2 id="security-ui-http-session">
<title>HTTP Basic Authentication</title>
<para>Primarily to cater for the needs of remoting protocols such as
Hessian and Burlap, the Acegi Security System for Spring provides a
<literal>BasicProcessingFilter</literal> which is capable of
processing authentication credentials presented in HTTP headers (for
standard authentication of web browser users, we recommend HTTP
Session Authentication). The standard governing HTTP Basic
Authentication is defined by RFC 1945, Section 11, and the
<literal>BasicProcessingFilter</literal> conforms with this RFC. To
implement HTTP Basic Authentication, it is necessary to add the
following filter to <literal>web.xml</literal>:</para>
<para><programlisting>&lt;filter&gt;
&lt;filter-name&gt;Acegi HTTP BASIC Authorization Filter&lt;/filter-name&gt;
&lt;filter-class&gt;net.sf.acegisecurity.ui.basicauth.BasicProcessingFilter&lt;/filter-class&gt;
&lt;/filter&gt;
&lt;filter-mapping&gt;
&lt;filter-name&gt;Acegi HTTP BASIC Authorization Filter&lt;/filter-name&gt;
&lt;url-pattern&gt;/*&lt;/url-pattern&gt;
&lt;/filter-mapping&gt;</programlisting></para>
<para>Like the <literal>AuthenticationProcessingFilter</literal>
discussed above, the <literal>BasicProcessingFilter</literal> will
need to delegate to a properly configured
<literal>AuthenticationManager</literal>. To do this it requires
access to a Spring application context, which is usually obtained from
<literal>WebApplicationContextUtils.getWebApplicationContext(ServletContext)</literal>.
This is usually made available by using Spring's
<literal>ContextLoaderListener</literal> in
<literal>web.xml</literal>. Alternatively, the
<literal>web.xml</literal> can be used to define a filter
<literal>&lt;init-param&gt;</literal> named
<literal>contextConfigLocation</literal>. This initialization
parameter will represent a path to a Spring XML application context
that the <literal>AuthenticationProcessingFilter</literal> will load
during startup.</para>
<para>The <literal>AuthenticationManager</literal> processes each
authentication request. If authentication fails, a 403 (forbidden)
response will be returned in response to the HTTP request. If
authentication is successful, the resulting
<literal>Authentication</literal> object will be placed into the
<literal>HttpSession</literal> attribute indicated by
<literal>HttpSessionIntegrationFilter.ACEGI_SECURITY_AUTHENTICATION_KEY</literal>.
This becomes the "well-known location" from which the
<literal>Authentication</literal> object is later extracted.</para>
<para>If the authentication event was successful, or authentication
was not attempted because the HTTP header did not contain a supported
authentication request, the filter chain will continue as normal. The
only time the filter chain will be interrupted is if authentication
fails and a 403 response is returned, as discussed in the previous
paragraph.</para>
<para>HTTP Basic Authentication is recommended to be used instead of
Container Adapters. It can be used in conjunction with HTTP Session
Authentication, as demonstrated in the Contacts sample
application.</para>
</sect2>
<sect2 id="security-ui-well-known"> <sect2 id="security-ui-well-known">
<title>Well-Known Location Integration</title> <title>Well-Known Location Integration</title>
<para>Once a web application has used either HTTP Session <para>Once a web application has used either HTTP Session
Authentication or a Container Adapter, an Authentication, HTTP Basic Authentication, or a Container Adapter, an
<literal>Authentication</literal> object will exist in a well-known <literal>Authentication</literal> object will exist in a well-known
location. The final step in automatically integrating the user location. The final step in automatically integrating the user
interface with the backend security interceptor is to extract this interface with the backend security interceptor is to extract this
@ -1597,9 +1655,10 @@ public boolean supports(Class clazz);</programlisting></para>
<para><itemizedlist> <para><itemizedlist>
<listitem> <listitem>
<para><literal>HttpSessionIntegrationFilter</literal> is used <para><literal>HttpSessionIntegrationFilter</literal> is used
with HTTP Session Authentication, or any other approach that with HTTP Session Authentication, HTTP Basic Authentication, or
populates the <literal>HttpSession</literal> accordingly. It any other approach that populates the
extracts the <literal>Authentication</literal> object from the <literal>HttpSession</literal> accordingly. It extracts the
<literal>Authentication</literal> object from the
<literal>HttpSession</literal> attribute indicated by <literal>HttpSession</literal> attribute indicated by
<literal>HttpSessionIntegrationFilter.ACEGI_SECURITY_AUTHENTICATION_KEY</literal>.</para> <literal>HttpSessionIntegrationFilter.ACEGI_SECURITY_AUTHENTICATION_KEY</literal>.</para>
</listitem> </listitem>
@ -1663,8 +1722,8 @@ public boolean supports(Class clazz);</programlisting></para>
with end users. Whilst this worked well, it required considerable time with end users. Whilst this worked well, it required considerable time
to support multiple container versions and the configuration itself to support multiple container versions and the configuration itself
was relatively time-consuming for developers. For this reason the HTTP was relatively time-consuming for developers. For this reason the HTTP
Session Authentication approach was developed, and is today Session Authentication and HTTP Basic Authentication approaches were
recommended for most applications.</para> developed, and are today recommended for most applications.</para>
<para>Container Adapters enable the Acegi Security System for Spring <para>Container Adapters enable the Acegi Security System for Spring
to integrate directly with the containers used to host end user to integrate directly with the containers used to host end user
@ -1789,6 +1848,18 @@ public boolean supports(Class clazz);</programlisting></para>
<listitem> <listitem>
<para><literal>acegi-security-catalina-common.jar</literal></para> <para><literal>acegi-security-catalina-common.jar</literal></para>
</listitem> </listitem>
<listitem>
<para><literal>commons-codec.jar</literal></para>
</listitem>
<listitem>
<para><literal>burlap.jar</literal></para>
</listitem>
<listitem>
<para><literal>hessian.jar</literal></para>
</listitem>
</itemizedlist> </itemizedlist>
<para>None of the above JAR files (or <para>None of the above JAR files (or
@ -1851,6 +1922,18 @@ $CATALINA_HOME/bin/startup.sh</programlisting></para>
<listitem> <listitem>
<para><literal>acegi-security-jetty-ext.jar</literal></para> <para><literal>acegi-security-jetty-ext.jar</literal></para>
</listitem> </listitem>
<listitem>
<para><literal>commons-codec.jar</literal></para>
</listitem>
<listitem>
<para><literal>burlap.jar</literal></para>
</listitem>
<listitem>
<para><literal>hessian.jar</literal></para>
</listitem>
</itemizedlist></para> </itemizedlist></para>
<para>None of the above JAR files (or <para>None of the above JAR files (or
@ -1904,6 +1987,18 @@ $CATALINA_HOME/bin/startup.sh</programlisting></para>
<listitem> <listitem>
<para><literal>acegi-security-jboss-lib.jar</literal></para> <para><literal>acegi-security-jboss-lib.jar</literal></para>
</listitem> </listitem>
<listitem>
<para><literal>commons-codec.jar</literal></para>
</listitem>
<listitem>
<para><literal>burlap.jar</literal></para>
</listitem>
<listitem>
<para><literal>hessian.jar</literal></para>
</listitem>
</itemizedlist></para> </itemizedlist></para>
<para>None of the above JAR files (or <para>None of the above JAR files (or
@ -1954,6 +2049,18 @@ $CATALINA_HOME/bin/startup.sh</programlisting></para>
<listitem> <listitem>
<para><literal>acegi-security-resin-lib.jar</literal></para> <para><literal>acegi-security-resin-lib.jar</literal></para>
</listitem> </listitem>
<listitem>
<para><literal>commons-codec.jar</literal></para>
</listitem>
<listitem>
<para><literal>burlap.jar</literal></para>
</listitem>
<listitem>
<para><literal>hessian.jar</literal></para>
</listitem>
</itemizedlist></para> </itemizedlist></para>
<para>Unlike the container-wide <literal>acegisecurity.xml</literal> <para>Unlike the container-wide <literal>acegisecurity.xml</literal>
@ -2066,6 +2173,22 @@ $CATALINA_HOME/bin/startup.sh</programlisting></para>
visiting <literal>http://localhost:8080/contacts/secure/super</literal>, visiting <literal>http://localhost:8080/contacts/secure/super</literal>,
which will demonstrate access being denied by the which will demonstrate access being denied by the
<literal>SecurityEnforcementFilter</literal>.</para> <literal>SecurityEnforcementFilter</literal>.</para>
<para>The Contacts sample application also include a
<literal>client</literal> directory. Inside you will find a small
application that queries the backend business objects using the Hessian
and Burlap protocols. This demonstrates how to use the Acegi Security
System for Spring for authentication with Spring remoting protocols. To
try this client, ensure your servlet container is still running the
Contacts sample application, and then execute <literal>client
marissa</literal>. This will use the remoting protocols to obtain the
list of contacts with the owner specified (in this case
<literal>marissa</literal>). Note you will be need to edit
<literal>client.properties</literal> to use a different username,
password, or target URL. To see that security does indeed work, try
running <literal>client scott</literal> before changing
<literal>client.properties</literal> to use <literal>scott</literal>'s
authentication details.</para>
</sect1> </sect1>
<sect1 id="security-become-involved"> <sect1 id="security-become-involved">

View File

@ -0,0 +1,56 @@
/*
* ====================================================================
*
* The Apache Software License, Version 1.1
*
* Copyright (c) 2001-2003 The Apache Software Foundation. All rights
* reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in
* the documentation and/or other materials provided with the
* distribution.
*
* 3. The end-user documentation included with the redistribution,
* if any, must include the following acknowledgement:
* "This product includes software developed by the
* Apache Software Foundation (http://www.apache.org/)."
* Alternately, this acknowledgement may appear in the software itself,
* if and wherever such third-party acknowledgements normally appear.
*
* 4. The names "Apache", "The Jakarta Project", "Commons", and "Apache Software
* Foundation" must not be used to endorse or promote products derived
* from this software without prior written permission. For written
* permission, please contact apache@apache.org.
*
* 5. Products derived from this software may not be called "Apache",
* "Apache" nor may "Apache" appear in their name without prior
* written permission of the Apache Software Foundation.
*
* THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
* WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR
* ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
* USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
* ====================================================================
*
* This software consists of voluntary contributions made by many
* individuals on behalf of the Apache Software Foundation. For more
* information on the Apache Software Foundation, please see
* <http://www.apache.org/>.
*
*/

View File

@ -9,8 +9,6 @@ to the project have some ideas on where they could potentially add value.
- Extend integration tests to support Resin (Ant startup/shutdown approach - Extend integration tests to support Resin (Ant startup/shutdown approach
needed) needed)
- Extend Spring remoting classes to transparently transport the Context
- Sample application that demonstrates EJB remote method invocation with Acegi - Sample application that demonstrates EJB remote method invocation with Acegi
security system as login module on server side security system as login module on server side

View File

@ -2,8 +2,7 @@
ACEGI SECURITY SYSTEM FOR SPRING - UPGRADING FROM 0.4 TO 0.5 ACEGI SECURITY SYSTEM FOR SPRING - UPGRADING FROM 0.4 TO 0.5
=============================================================================== ===============================================================================
Unfortunately, changes to the API and package locations were required. The The following should help most casual users of the project update their
following should help most casual users of the project update their
applications: applications:
- By default, AuthenticationProcessingFilter and SecurityEnforcementFilter now - By default, AuthenticationProcessingFilter and SecurityEnforcementFilter now
@ -17,6 +16,9 @@ applications:
If you do still want to use this approach, just rename your param from If you do still want to use this approach, just rename your param from
'appContextLocation' to 'contextConfigLocation'. 'appContextLocation' to 'contextConfigLocation'.
- If you're using container adapters, please refer to the reference
documentation as additional JARs are now required in your container
classloader.
We hope you find the new features useful in your projects. We hope you find the new features useful in your projects.