Make SecurityEnforcementFilter support pluggable authentication entry points. Enhance BASIC authentication so it's a viable alternative to form-based authentication for user agents like IE and Netscape.

This commit is contained in:
Ben Alex 2004-04-16 14:22:15 +00:00
parent 7e85bbc054
commit 6815e693a7
15 changed files with 586 additions and 79 deletions

View File

@ -0,0 +1,56 @@
/* 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.intercept.web;
import java.io.IOException;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
/**
* Used by {@link SecurityEnforcementFilter} to commence an authentication
* scheme.
*
* @author Ben Alex
* @version $Id$
*/
public interface AuthenticationEntryPoint {
//~ Methods ================================================================
/**
* Commences an authentication scheme.
*
* <P>
* <code>SecurityEnforcementFilter</code> will populate the
* <code>HttpSession</code> attribute named
* <code>AuthenticationProcessingFilter.ACEGI_SECURITY_TARGET_URL_KEY</code>
* with the requested target URL before calling this method.
* </p>
*
* <P>
* Implementations should modify the headers on the
* <code>ServletResponse</code> to as necessary to commence the
* authentication process.
* </p>
*
* @param request that resulted in an <code>AuthenticationException</code>
* @param response so that the user agent can begin authentication
*/
public void commence(ServletRequest request, ServletResponse response)
throws IOException, ServletException;
}

View File

@ -59,8 +59,7 @@ import javax.servlet.http.HttpServletResponse;
* </p>
*
* <p>
* To use this filter, it is necessary to specify the following filter
* initialization parameters:
* To use this filter, it is necessary to specify the following properties:
* </p>
*
* <ul>
@ -70,8 +69,9 @@ import javax.servlet.http.HttpServletResponse;
* to.
* </li>
* <li>
* <code>loginFormUrl</code> indicates the URL that should be used for
* redirection if an <code>AuthenticationException</code> is detected.
* <code>authenticationEntryPoint</code> indicates the handler that should
* commence the authentication process if an
* <code>AuthenticationException</code> is detected.
* </li>
* </ul>
*
@ -92,16 +92,20 @@ public class SecurityEnforcementFilter implements Filter, InitializingBean {
//~ Instance fields ========================================================
protected FilterSecurityInterceptor filterSecurityInterceptor;
/**
* The URL that should be used for redirection if an
* <code>AuthenticationException</code> is detected.
*/
protected String loginFormUrl;
private AuthenticationEntryPoint authenticationEntryPoint;
private FilterSecurityInterceptor filterSecurityInterceptor;
//~ Methods ================================================================
public void setAuthenticationEntryPoint(
AuthenticationEntryPoint authenticationEntryPoint) {
this.authenticationEntryPoint = authenticationEntryPoint;
}
public AuthenticationEntryPoint getAuthenticationEntryPoint() {
return authenticationEntryPoint;
}
public void setFilterSecurityInterceptor(
FilterSecurityInterceptor filterSecurityInterceptor) {
this.filterSecurityInterceptor = filterSecurityInterceptor;
@ -111,17 +115,10 @@ public class SecurityEnforcementFilter implements Filter, InitializingBean {
return filterSecurityInterceptor;
}
public void setLoginFormUrl(String loginFormUrl) {
this.loginFormUrl = loginFormUrl;
}
public String getLoginFormUrl() {
return loginFormUrl;
}
public void afterPropertiesSet() throws Exception {
if ((loginFormUrl == null) || "".equals(loginFormUrl)) {
throw new IllegalArgumentException("loginFormUrl must be specified");
if (authenticationEntryPoint == null) {
throw new IllegalArgumentException(
"authenticationEntryPoint must be specified");
}
if (filterSecurityInterceptor == null) {
@ -161,8 +158,7 @@ public class SecurityEnforcementFilter implements Filter, InitializingBean {
((HttpServletRequest) request).getSession().setAttribute(AuthenticationProcessingFilter.ACEGI_SECURITY_TARGET_URL_KEY,
fi.getRequestUrl());
((HttpServletResponse) response).sendRedirect(((HttpServletRequest) request)
.getContextPath() + loginFormUrl);
authenticationEntryPoint.commence(request, response);
} catch (AccessDeniedException accessDenied) {
if (logger.isDebugEnabled()) {
logger.debug(

View File

@ -18,6 +18,7 @@ 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.intercept.web.AuthenticationEntryPoint;
import net.sf.acegisecurity.providers.UsernamePasswordAuthenticationToken;
import net.sf.acegisecurity.ui.webapp.HttpSessionIntegrationFilter;
@ -63,9 +64,9 @@ import javax.servlet.http.HttpServletResponse;
* </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.
* This filter can be used to provide BASIC authentication services to both
* remoting protocol clients (such as Hessian and SOAP) as well as standard
* user agents (such as Internet Explorer and Netscape).
* </p>
*
* <P>
@ -75,10 +76,9 @@ import javax.servlet.http.HttpServletResponse;
* </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>".
* If authentication fails, an {@link AuthenticationEntryPoint} implementation
* is called. Usually this should be {@link BasicProcessingFilterEntryPoint},
* which will prompt the user to authenticate again via BASIC authentication.
* </p>
*
* <P>
@ -97,10 +97,20 @@ public class BasicProcessingFilter implements Filter, InitializingBean {
//~ Instance fields ========================================================
private AuthenticationEntryPoint authenticationEntryPoint;
private AuthenticationManager authenticationManager;
//~ Methods ================================================================
public void setAuthenticationEntryPoint(
AuthenticationEntryPoint authenticationEntryPoint) {
this.authenticationEntryPoint = authenticationEntryPoint;
}
public AuthenticationEntryPoint getAuthenticationEntryPoint() {
return authenticationEntryPoint;
}
public void setAuthenticationManager(
AuthenticationManager authenticationManager) {
this.authenticationManager = authenticationManager;
@ -115,6 +125,11 @@ public class BasicProcessingFilter implements Filter, InitializingBean {
throw new IllegalArgumentException(
"An AuthenticationManager is required");
}
if (this.authenticationEntryPoint == null) {
throw new IllegalArgumentException(
"An AuthenticationEntryPoint is required");
}
}
public void destroy() {}
@ -166,7 +181,7 @@ public class BasicProcessingFilter implements Filter, InitializingBean {
+ " failed: " + failed.toString());
}
((HttpServletResponse) response).sendError(HttpServletResponse.SC_FORBIDDEN); // 403
authenticationEntryPoint.commence(request, response);
return;
}

View File

@ -0,0 +1,75 @@
/* 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.intercept.web.AuthenticationEntryPoint;
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.HttpServletResponse;
/**
* Used by the <code>SecurityEnforcementFilter</code> to commence
* authentication via the {@link BasicProcessingFilter}.
*
* <P>
* Once a user agent is authenticated using BASIC authentication, logout
* requires that the browser be closed or an unauthorized (401) header be
* sent. The simplest way of achieving the latter is to call the {@link
* #commence(ServletRequest, ServletResponse)} method below. This will
* indicate to the browser its credentials are no longer authorized, causing
* it to prompt the user to login again.
* </p>
*
* @author Ben Alex
* @version $Id$
*/
public class BasicProcessingFilterEntryPoint implements AuthenticationEntryPoint,
InitializingBean {
//~ Instance fields ========================================================
private String realmName;
//~ Methods ================================================================
public void setRealmName(String realmName) {
this.realmName = realmName;
}
public String getRealmName() {
return realmName;
}
public void afterPropertiesSet() throws Exception {
if ((realmName == null) || "".equals(realmName)) {
throw new IllegalArgumentException("realmName must be specified");
}
}
public void commence(ServletRequest request, ServletResponse response)
throws IOException, ServletException {
HttpServletResponse httpResponse = (HttpServletResponse) response;
httpResponse.addHeader("WWW-Authenticate",
"Basic realm=\"" + realmName + "\"");
httpResponse.sendError(401);
}
}

View File

@ -0,0 +1,69 @@
/* 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.webapp;
import net.sf.acegisecurity.intercept.web.AuthenticationEntryPoint;
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;
/**
* Used by the <code>SecurityEnforcementFilter</code> to commence
* authentication via the {@link AuthenticationProcessingFilter}.
*
* @author Ben Alex
* @version $Id$
*/
public class AuthenticationProcessingFilterEntryPoint
implements AuthenticationEntryPoint, InitializingBean {
//~ Instance fields ========================================================
/**
* The URL where the <code>AuthenticationProcessingFilter</code> login page
* can be found.
*/
private String loginFormUrl;
//~ Methods ================================================================
public void setLoginFormUrl(String loginFormUrl) {
this.loginFormUrl = loginFormUrl;
}
public String getLoginFormUrl() {
return loginFormUrl;
}
public void afterPropertiesSet() throws Exception {
if ((loginFormUrl == null) || "".equals(loginFormUrl)) {
throw new IllegalArgumentException("loginFormUrl must be specified");
}
}
public void commence(ServletRequest request, ServletResponse response)
throws IOException, ServletException {
((HttpServletResponse) response).sendRedirect(((HttpServletRequest) request)
.getContextPath() + loginFormUrl);
}
}

View File

@ -0,0 +1,57 @@
/* 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;
import net.sf.acegisecurity.intercept.web.AuthenticationEntryPoint;
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;
/**
* Performs a HTTP redirect to the constructor-indicated URL.
*
* @author Ben Alex
* @version $Id$
*/
public class MockAuthenticationEntryPoint implements AuthenticationEntryPoint {
//~ Instance fields ========================================================
private String url;
//~ Constructors ===========================================================
public MockAuthenticationEntryPoint(String url) {
this.url = url;
}
private MockAuthenticationEntryPoint() {
super();
}
//~ Methods ================================================================
public void commence(ServletRequest request, ServletResponse response)
throws IOException, ServletException {
((HttpServletResponse) response).sendRedirect(((HttpServletRequest) request)
.getContextPath() + url);
}
}

View File

@ -18,7 +18,9 @@ package net.sf.acegisecurity;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.HashMap;
import java.util.Locale;
import java.util.Map;
import javax.servlet.ServletOutputStream;
import javax.servlet.http.Cookie;
@ -35,6 +37,7 @@ import javax.servlet.http.HttpServletResponse;
public class MockHttpServletResponse implements HttpServletResponse {
//~ Instance fields ========================================================
private Map headersMap = new HashMap();
private String redirect;
private int error;
@ -76,6 +79,16 @@ public class MockHttpServletResponse implements HttpServletResponse {
throw new UnsupportedOperationException("mock method not implemented");
}
public String getHeader(String arg0) {
Object result = headersMap.get(arg0);
if (result != null) {
return (String) headersMap.get(arg0);
} else {
return null;
}
}
public void setIntHeader(String arg0, int arg1) {
throw new UnsupportedOperationException("mock method not implemented");
}
@ -117,7 +130,7 @@ public class MockHttpServletResponse implements HttpServletResponse {
}
public void addHeader(String arg0, String arg1) {
throw new UnsupportedOperationException("mock method not implemented");
headersMap.put(arg0, arg1);
}
public void addIntHeader(String arg0, int arg1) {

View File

@ -19,6 +19,7 @@ import junit.framework.TestCase;
import net.sf.acegisecurity.AccessDeniedException;
import net.sf.acegisecurity.BadCredentialsException;
import net.sf.acegisecurity.MockAuthenticationEntryPoint;
import net.sf.acegisecurity.MockHttpServletRequest;
import net.sf.acegisecurity.MockHttpServletResponse;
import net.sf.acegisecurity.MockHttpSession;
@ -76,7 +77,8 @@ public class SecurityEnforcementFilterTests extends TestCase {
// Test
SecurityEnforcementFilter filter = new SecurityEnforcementFilter();
filter.setFilterSecurityInterceptor(interceptor);
filter.setLoginFormUrl("/login.jsp");
filter.setAuthenticationEntryPoint(new MockAuthenticationEntryPoint(
"/login.jsp"));
MockHttpServletResponse response = new MockHttpServletResponse();
filter.doFilter(request, response, chain);
@ -115,8 +117,9 @@ public class SecurityEnforcementFilterTests extends TestCase {
false, false));
assertTrue(filter.getFilterSecurityInterceptor() != null);
filter.setLoginFormUrl("/u");
assertEquals("/u", filter.getLoginFormUrl());
filter.setAuthenticationEntryPoint(new MockAuthenticationEntryPoint(
"/login.jsp"));
assertTrue(filter.getAuthenticationEntryPoint() != null);
}
public void testRedirectedToLoginFormAndSessionShowsOriginalTargetWhenAuthenticationException()
@ -136,7 +139,8 @@ public class SecurityEnforcementFilterTests extends TestCase {
// Test
SecurityEnforcementFilter filter = new SecurityEnforcementFilter();
filter.setFilterSecurityInterceptor(interceptor);
filter.setLoginFormUrl("/login.jsp");
filter.setAuthenticationEntryPoint(new MockAuthenticationEntryPoint(
"/login.jsp"));
filter.afterPropertiesSet();
MockHttpServletResponse response = new MockHttpServletResponse();
@ -146,21 +150,7 @@ public class SecurityEnforcementFilterTests extends TestCase {
request.getSession().getAttribute(AuthenticationProcessingFilter.ACEGI_SECURITY_TARGET_URL_KEY));
}
public void testStartupDetectsMissingFilterSecurityInterceptor()
throws Exception {
SecurityEnforcementFilter filter = new SecurityEnforcementFilter();
filter.setLoginFormUrl("/login.jsp");
try {
filter.afterPropertiesSet();
fail("Should have thrown IllegalArgumentException");
} catch (IllegalArgumentException expected) {
assertEquals("filterSecurityInterceptor must be specified",
expected.getMessage());
}
}
public void testStartupDetectsMissingLoginFormUrl()
public void testStartupDetectsMissingAuthenticationEntryPoint()
throws Exception {
SecurityEnforcementFilter filter = new SecurityEnforcementFilter();
filter.setFilterSecurityInterceptor(new MockFilterSecurityInterceptor(
@ -170,7 +160,23 @@ public class SecurityEnforcementFilterTests extends TestCase {
filter.afterPropertiesSet();
fail("Should have thrown IllegalArgumentException");
} catch (IllegalArgumentException expected) {
assertEquals("loginFormUrl must be specified", expected.getMessage());
assertEquals("authenticationEntryPoint must be specified",
expected.getMessage());
}
}
public void testStartupDetectsMissingFilterSecurityInterceptor()
throws Exception {
SecurityEnforcementFilter filter = new SecurityEnforcementFilter();
filter.setAuthenticationEntryPoint(new MockAuthenticationEntryPoint(
"/login.jsp"));
try {
filter.afterPropertiesSet();
fail("Should have thrown IllegalArgumentException");
} catch (IllegalArgumentException expected) {
assertEquals("filterSecurityInterceptor must be specified",
expected.getMessage());
}
}
@ -190,7 +196,8 @@ public class SecurityEnforcementFilterTests extends TestCase {
// Test
SecurityEnforcementFilter filter = new SecurityEnforcementFilter();
filter.setFilterSecurityInterceptor(interceptor);
filter.setLoginFormUrl("/login.jsp");
filter.setAuthenticationEntryPoint(new MockAuthenticationEntryPoint(
"/login.jsp"));
MockHttpServletResponse response = new MockHttpServletResponse();
filter.doFilter(request, response, chain);

View File

@ -0,0 +1,82 @@
/* 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.MockHttpServletRequest;
import net.sf.acegisecurity.MockHttpServletResponse;
/**
* Tests {@link BasicProcessingFilterEntryPoint}.
*
* @author Ben Alex
* @version $Id$
*/
public class BasicProcessingFilterEntryPointTests extends TestCase {
//~ Constructors ===========================================================
public BasicProcessingFilterEntryPointTests() {
super();
}
public BasicProcessingFilterEntryPointTests(String arg0) {
super(arg0);
}
//~ Methods ================================================================
public final void setUp() throws Exception {
super.setUp();
}
public static void main(String[] args) {
junit.textui.TestRunner.run(BasicProcessingFilterEntryPointTests.class);
}
public void testDetectsMissingRealmName() throws Exception {
BasicProcessingFilterEntryPoint ep = new BasicProcessingFilterEntryPoint();
try {
ep.afterPropertiesSet();
fail("Should have thrown IllegalArgumentException");
} catch (IllegalArgumentException expected) {
assertEquals("realmName must be specified", expected.getMessage());
}
}
public void testGettersSetters() {
BasicProcessingFilterEntryPoint ep = new BasicProcessingFilterEntryPoint();
ep.setRealmName("realm");
assertEquals("realm", ep.getRealmName());
}
public void testNormalOperation() throws Exception {
BasicProcessingFilterEntryPoint ep = new BasicProcessingFilterEntryPoint();
ep.setRealmName("hello");
MockHttpServletRequest request = new MockHttpServletRequest(
"/some_path");
MockHttpServletResponse response = new MockHttpServletResponse();
ep.afterPropertiesSet();
ep.commence(request, response);
assertEquals(401, response.getError());
assertEquals("Basic realm=\"hello\"",
response.getHeader("WWW-Authenticate"));
}
}

View File

@ -18,6 +18,7 @@ package net.sf.acegisecurity.ui.basicauth;
import junit.framework.TestCase;
import net.sf.acegisecurity.Authentication;
import net.sf.acegisecurity.MockAuthenticationEntryPoint;
import net.sf.acegisecurity.MockAuthenticationManager;
import net.sf.acegisecurity.MockFilterConfig;
import net.sf.acegisecurity.MockHttpServletRequest;
@ -130,6 +131,10 @@ public class BasicProcessingFilterTests extends TestCase {
BasicProcessingFilter filter = new BasicProcessingFilter();
filter.setAuthenticationManager(new MockAuthenticationManager());
assertTrue(filter.getAuthenticationManager() != null);
filter.setAuthenticationEntryPoint(new MockAuthenticationEntryPoint(
"sx"));
assertTrue(filter.getAuthenticationEntryPoint() != null);
}
public void testInvalidBasicAuthorizationTokenIsIgnored()
@ -228,10 +233,25 @@ public class BasicProcessingFilterTests extends TestCase {
assertTrue(request.getSession().getAttribute(HttpSessionIntegrationFilter.ACEGI_SECURITY_AUTHENTICATION_KEY) == null);
}
public void testStartupDetectsMissingAuthenticationEntryPoint()
throws Exception {
try {
BasicProcessingFilter filter = new BasicProcessingFilter();
filter.setAuthenticationManager(new MockAuthenticationManager());
filter.afterPropertiesSet();
fail("Should have thrown IllegalArgumentException");
} catch (IllegalArgumentException expected) {
assertEquals("An AuthenticationEntryPoint is required",
expected.getMessage());
}
}
public void testStartupDetectsMissingAuthenticationManager()
throws Exception {
try {
BasicProcessingFilter filter = new BasicProcessingFilter();
filter.setAuthenticationEntryPoint(new MockAuthenticationEntryPoint(
"x"));
filter.afterPropertiesSet();
fail("Should have thrown IllegalArgumentException");
} catch (IllegalArgumentException expected) {
@ -293,7 +313,7 @@ public class BasicProcessingFilterTests extends TestCase {
chain);
assertTrue(request.getSession().getAttribute(HttpSessionIntegrationFilter.ACEGI_SECURITY_AUTHENTICATION_KEY) == null);
assertEquals(403, response.getError());
assertEquals(401, response.getError());
}
public void testWrongPasswordReturnsForbidden() throws Exception {
@ -325,7 +345,7 @@ public class BasicProcessingFilterTests extends TestCase {
chain);
assertTrue(request.getSession().getAttribute(HttpSessionIntegrationFilter.ACEGI_SECURITY_AUTHENTICATION_KEY) == null);
assertEquals(403, response.getError());
assertEquals(401, response.getError());
}
private void executeFilterInContainerSimulator(FilterConfig filterConfig,

View File

@ -49,6 +49,11 @@
<bean id="basicProcessingFilter" class="net.sf.acegisecurity.ui.basicauth.BasicProcessingFilter">
<property name="authenticationManager"><ref bean="authenticationManager"/></property>
<property name="authenticationEntryPoint"><ref bean="basicProcessingFilterEntryPoint"/></property>
</bean>
<bean id="basicProcessingFilterEntryPoint" class="net.sf.acegisecurity.ui.basicauth.BasicProcessingFilterEntryPoint">
<property name="realmName"><value>Test Suite Realm</value></property>
</bean>
</beans>

View File

@ -0,0 +1,80 @@
/* 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.webapp;
import junit.framework.TestCase;
import net.sf.acegisecurity.MockHttpServletRequest;
import net.sf.acegisecurity.MockHttpServletResponse;
/**
* Tests {@link AuthenticationProcessingFilterEntryPoint}.
*
* @author Ben Alex
* @version $Id$
*/
public class AuthenticationProcessingFilterEntryPointTests extends TestCase {
//~ Constructors ===========================================================
public AuthenticationProcessingFilterEntryPointTests() {
super();
}
public AuthenticationProcessingFilterEntryPointTests(String arg0) {
super(arg0);
}
//~ Methods ================================================================
public final void setUp() throws Exception {
super.setUp();
}
public static void main(String[] args) {
junit.textui.TestRunner.run(AuthenticationProcessingFilterEntryPointTests.class);
}
public void testDetectsMissingLoginFormUrl() throws Exception {
AuthenticationProcessingFilterEntryPoint ep = new AuthenticationProcessingFilterEntryPoint();
try {
ep.afterPropertiesSet();
fail("Should have thrown IllegalArgumentException");
} catch (IllegalArgumentException expected) {
assertEquals("loginFormUrl must be specified", expected.getMessage());
}
}
public void testGettersSetters() {
AuthenticationProcessingFilterEntryPoint ep = new AuthenticationProcessingFilterEntryPoint();
ep.setLoginFormUrl("/hello");
assertEquals("/hello", ep.getLoginFormUrl());
}
public void testNormalOperation() throws Exception {
AuthenticationProcessingFilterEntryPoint ep = new AuthenticationProcessingFilterEntryPoint();
ep.setLoginFormUrl("/hello");
MockHttpServletRequest request = new MockHttpServletRequest(
"/some_path");
MockHttpServletResponse response = new MockHttpServletResponse();
ep.afterPropertiesSet();
ep.commence(request, response);
assertEquals("/hello", response.getRedirect());
}
}

View File

@ -538,11 +538,15 @@
so you should configure a <literal>ContextLoaderListener</literal> in
<literal>web.xml</literal>.</para>
<para>In the application context you will need to configure two
<para>In the application context you will need to configure three
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;/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;/bean&gt;
@ -559,16 +563,21 @@
&lt;/property&gt;
&lt;/bean&gt;</programlisting>
<para>The <literal>loginFormUrl</literal> is where the filter will
redirect the user's browser if they request a secure HTTP resource but
they are not authenticated. If the user is authenticated, a "403
Forbidden" response will be returned to the browser. All paths are
relative to the web application root.</para>
<para>The <literal>AuthenticationEntryPoint</literal> will be called
if the user requests a secure HTTP resource but they are not
authenticated. The class handles presenting the appropriate response
to the user so that authentication can begin. Two concrete
implementations are provided with the Acegi Security System for
Spring: <literal>AuthenticationProcessingFilterEntryPoint</literal>
for commencing a form-based authentication, and
<literal>BasicProcessingFilterEntryPoint</literal> for commencing a
Http Basic authentication process.</para>
<para>The <literal>SecurityEnforcementFilter</literal> primarily
provides redirection and session management support. It delegates
actual <literal>FilterInvocation</literal> security decisions to the
configured <literal>FilterSecurityInterceptor</literal>.</para>
provides session management support and initiates authentication when
required. It delegates actual <literal>FilterInvocation</literal>
security decisions to the configured
<literal>FilterSecurityInterceptor</literal>.</para>
<para>Like any other security interceptor, the
<literal>FilterSecurityInterceptor</literal> requires a reference to
@ -1560,19 +1569,18 @@ public boolean supports(Class clazz);</programlisting></para>
<sect2 id="security-ui-http-basic">
<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
<para>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
processing authentication credentials presented in HTTP headers. This
can be used for authenticating calls made by Spring remoting protocols
(such as Hessian and Burlap), as well as normal user agents (such as
Internet Explorer and Navigator). The standard governing HTTP Basic
Authentication is defined by RFC 1945, Section 11, and the
<literal>BasicProcessingFilter</literal> conforms with this
RFC.</para>
<para>To implement HTTP Basic Authentication, it is necessary to add
the following filter to <literal>web.xml</literal>, behind a
<literal>FilterToBeanProxy</literal>:</para>
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;
@ -1591,16 +1599,25 @@ public boolean supports(Class clazz);</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>:</para>
<literal>BasicProcessingFilter</literal> and its required
collaborator:</para>
<para><programlisting>&lt;bean id="basicProcessingFilter" class="net.sf.acegisecurity.ui.basicauth.BasicProcessingFilter"&gt;
&lt;property name="authenticationManager"&gt;&lt;ref bean="authenticationManager"/&gt;&lt;/property&gt;
&lt;property name="authenticationEntryPoint"&gt;&lt;ref bean="authenticationEntryPoint"/&gt;&lt;/property&gt;
&lt;/bean&gt;
&lt;bean id="authenticationEntryPoint" class="net.sf.acegisecurity.ui.basicauth.BasicProcessingFilterEntryPoint"&gt;
&lt;property name="realmName"&gt;&lt;value&gt;Name Of Your Realm&lt;/value&gt;&lt;/property&gt;
&lt;/bean&gt;</programlisting></para>
<para>The configured <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
processes each authentication request. If authentication fails, the
configured <literal>AuthenticationEntryPoint</literal> will be used to
retry the authentication process. Usually you will use the
<literal>BasicProcessingFilterEntryPoint</literal>, which returns a
401 response with a suitable header to retry HTTP Basic
authentication. 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>.
@ -1611,13 +1628,14 @@ public boolean supports(Class clazz);</programlisting></para>
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>
fails and the <literal>AuthenticationEntryPoint</literal> is called,
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>
Authentication, as demonstrated in the Contacts sample application.
You can also use it instead of HTTP Session Authentication if you
wish.</para>
</sect2>
<sect2 id="security-ui-well-known">

View File

@ -52,6 +52,11 @@
<bean id="basicProcessingFilter" class="net.sf.acegisecurity.ui.basicauth.BasicProcessingFilter">
<property name="authenticationManager"><ref bean="authenticationManager"/></property>
<property name="authenticationEntryPoint"><ref bean="basicProcessingFilterEntryPoint"/></property>
</bean>
<bean id="basicProcessingFilterEntryPoint" class="net.sf.acegisecurity.ui.basicauth.BasicProcessingFilterEntryPoint">
<property name="realmName"><value>Contacts Realm</value></property>
</bean>
<!-- ~~~~~~~~~~~~~~~~~~~~ AUTHORIZATION DEFINITIONS ~~~~~~~~~~~~~~~~~~~ -->

View File

@ -47,6 +47,11 @@
<bean id="basicProcessingFilter" class="net.sf.acegisecurity.ui.basicauth.BasicProcessingFilter">
<property name="authenticationManager"><ref bean="authenticationManager"/></property>
<property name="authenticationEntryPoint"><ref bean="basicProcessingFilterEntryPoint"/></property>
</bean>
<bean id="basicProcessingFilterEntryPoint" class="net.sf.acegisecurity.ui.basicauth.BasicProcessingFilterEntryPoint">
<property name="realmName"><value>Contacts Realm</value></property>
</bean>
<!-- ~~~~~~~~~~~~~~~~~~~~ AUTHORIZATION DEFINITIONS ~~~~~~~~~~~~~~~~~~~ -->
@ -138,6 +143,10 @@
<bean id="securityEnforcementFilter" class="net.sf.acegisecurity.intercept.web.SecurityEnforcementFilter">
<property name="filterSecurityInterceptor"><ref bean="filterInvocationInterceptor"/></property>
<property name="authenticationEntryPoint"><ref bean="authenticationProcessingFilterEntryPoint"/></property>
</bean>
<bean id="authenticationProcessingFilterEntryPoint" class="net.sf.acegisecurity.ui.webapp.AuthenticationProcessingFilterEntryPoint">
<property name="loginFormUrl"><value>/acegilogin.jsp</value></property>
</bean>