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

View File

@ -18,6 +18,7 @@ package net.sf.acegisecurity.ui.basicauth;
import net.sf.acegisecurity.Authentication; import net.sf.acegisecurity.Authentication;
import net.sf.acegisecurity.AuthenticationException; import net.sf.acegisecurity.AuthenticationException;
import net.sf.acegisecurity.AuthenticationManager; import net.sf.acegisecurity.AuthenticationManager;
import net.sf.acegisecurity.intercept.web.AuthenticationEntryPoint;
import net.sf.acegisecurity.providers.UsernamePasswordAuthenticationToken; import net.sf.acegisecurity.providers.UsernamePasswordAuthenticationToken;
import net.sf.acegisecurity.ui.webapp.HttpSessionIntegrationFilter; import net.sf.acegisecurity.ui.webapp.HttpSessionIntegrationFilter;
@ -63,9 +64,9 @@ import javax.servlet.http.HttpServletResponse;
* </p> * </p>
* *
* <p> * <p>
* Requests containing BASIC authentication headers are generally created by * This filter can be used to provide BASIC authentication services to both
* remoting protocol libraries. This filter is intended to process requests * remoting protocol clients (such as Hessian and SOAP) as well as standard
* made by such libraries. * user agents (such as Internet Explorer and Netscape).
* </p> * </p>
* *
* <P> * <P>
@ -75,10 +76,9 @@ import javax.servlet.http.HttpServletResponse;
* </p> * </p>
* *
* <p> * <p>
* If authentication fails, a <code>HttpServletResponse.SC_FORBIDDEN</code> * If authentication fails, an {@link AuthenticationEntryPoint} implementation
* (403 error) response is sent. This is consistent with RFC 1945, Section 11, * is called. Usually this should be {@link BasicProcessingFilterEntryPoint},
* which states, "<I>If the server does not wish to accept the credentials * which will prompt the user to authenticate again via BASIC authentication.
* sent with a request, it should return a 403 (forbidden) response.</I>".
* </p> * </p>
* *
* <P> * <P>
@ -97,10 +97,20 @@ public class BasicProcessingFilter implements Filter, InitializingBean {
//~ Instance fields ======================================================== //~ Instance fields ========================================================
private AuthenticationEntryPoint authenticationEntryPoint;
private AuthenticationManager authenticationManager; private AuthenticationManager authenticationManager;
//~ Methods ================================================================ //~ Methods ================================================================
public void setAuthenticationEntryPoint(
AuthenticationEntryPoint authenticationEntryPoint) {
this.authenticationEntryPoint = authenticationEntryPoint;
}
public AuthenticationEntryPoint getAuthenticationEntryPoint() {
return authenticationEntryPoint;
}
public void setAuthenticationManager( public void setAuthenticationManager(
AuthenticationManager authenticationManager) { AuthenticationManager authenticationManager) {
this.authenticationManager = authenticationManager; this.authenticationManager = authenticationManager;
@ -115,6 +125,11 @@ public class BasicProcessingFilter implements Filter, InitializingBean {
throw new IllegalArgumentException( throw new IllegalArgumentException(
"An AuthenticationManager is required"); "An AuthenticationManager is required");
} }
if (this.authenticationEntryPoint == null) {
throw new IllegalArgumentException(
"An AuthenticationEntryPoint is required");
}
} }
public void destroy() {} public void destroy() {}
@ -166,7 +181,7 @@ public class BasicProcessingFilter implements Filter, InitializingBean {
+ " failed: " + failed.toString()); + " failed: " + failed.toString());
} }
((HttpServletResponse) response).sendError(HttpServletResponse.SC_FORBIDDEN); // 403 authenticationEntryPoint.commence(request, response);
return; 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.IOException;
import java.io.PrintWriter; import java.io.PrintWriter;
import java.util.HashMap;
import java.util.Locale; import java.util.Locale;
import java.util.Map;
import javax.servlet.ServletOutputStream; import javax.servlet.ServletOutputStream;
import javax.servlet.http.Cookie; import javax.servlet.http.Cookie;
@ -35,6 +37,7 @@ import javax.servlet.http.HttpServletResponse;
public class MockHttpServletResponse implements HttpServletResponse { public class MockHttpServletResponse implements HttpServletResponse {
//~ Instance fields ======================================================== //~ Instance fields ========================================================
private Map headersMap = new HashMap();
private String redirect; private String redirect;
private int error; private int error;
@ -76,6 +79,16 @@ public class MockHttpServletResponse implements HttpServletResponse {
throw new UnsupportedOperationException("mock method not implemented"); 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) { public void setIntHeader(String arg0, int arg1) {
throw new UnsupportedOperationException("mock method not implemented"); throw new UnsupportedOperationException("mock method not implemented");
} }
@ -117,7 +130,7 @@ public class MockHttpServletResponse implements HttpServletResponse {
} }
public void addHeader(String arg0, String arg1) { 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) { 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.AccessDeniedException;
import net.sf.acegisecurity.BadCredentialsException; import net.sf.acegisecurity.BadCredentialsException;
import net.sf.acegisecurity.MockAuthenticationEntryPoint;
import net.sf.acegisecurity.MockHttpServletRequest; import net.sf.acegisecurity.MockHttpServletRequest;
import net.sf.acegisecurity.MockHttpServletResponse; import net.sf.acegisecurity.MockHttpServletResponse;
import net.sf.acegisecurity.MockHttpSession; import net.sf.acegisecurity.MockHttpSession;
@ -76,7 +77,8 @@ public class SecurityEnforcementFilterTests extends TestCase {
// Test // Test
SecurityEnforcementFilter filter = new SecurityEnforcementFilter(); SecurityEnforcementFilter filter = new SecurityEnforcementFilter();
filter.setFilterSecurityInterceptor(interceptor); filter.setFilterSecurityInterceptor(interceptor);
filter.setLoginFormUrl("/login.jsp"); filter.setAuthenticationEntryPoint(new MockAuthenticationEntryPoint(
"/login.jsp"));
MockHttpServletResponse response = new MockHttpServletResponse(); MockHttpServletResponse response = new MockHttpServletResponse();
filter.doFilter(request, response, chain); filter.doFilter(request, response, chain);
@ -115,8 +117,9 @@ public class SecurityEnforcementFilterTests extends TestCase {
false, false)); false, false));
assertTrue(filter.getFilterSecurityInterceptor() != null); assertTrue(filter.getFilterSecurityInterceptor() != null);
filter.setLoginFormUrl("/u"); filter.setAuthenticationEntryPoint(new MockAuthenticationEntryPoint(
assertEquals("/u", filter.getLoginFormUrl()); "/login.jsp"));
assertTrue(filter.getAuthenticationEntryPoint() != null);
} }
public void testRedirectedToLoginFormAndSessionShowsOriginalTargetWhenAuthenticationException() public void testRedirectedToLoginFormAndSessionShowsOriginalTargetWhenAuthenticationException()
@ -136,7 +139,8 @@ public class SecurityEnforcementFilterTests extends TestCase {
// Test // Test
SecurityEnforcementFilter filter = new SecurityEnforcementFilter(); SecurityEnforcementFilter filter = new SecurityEnforcementFilter();
filter.setFilterSecurityInterceptor(interceptor); filter.setFilterSecurityInterceptor(interceptor);
filter.setLoginFormUrl("/login.jsp"); filter.setAuthenticationEntryPoint(new MockAuthenticationEntryPoint(
"/login.jsp"));
filter.afterPropertiesSet(); filter.afterPropertiesSet();
MockHttpServletResponse response = new MockHttpServletResponse(); MockHttpServletResponse response = new MockHttpServletResponse();
@ -146,21 +150,7 @@ public class SecurityEnforcementFilterTests extends TestCase {
request.getSession().getAttribute(AuthenticationProcessingFilter.ACEGI_SECURITY_TARGET_URL_KEY)); request.getSession().getAttribute(AuthenticationProcessingFilter.ACEGI_SECURITY_TARGET_URL_KEY));
} }
public void testStartupDetectsMissingFilterSecurityInterceptor() public void testStartupDetectsMissingAuthenticationEntryPoint()
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()
throws Exception { throws Exception {
SecurityEnforcementFilter filter = new SecurityEnforcementFilter(); SecurityEnforcementFilter filter = new SecurityEnforcementFilter();
filter.setFilterSecurityInterceptor(new MockFilterSecurityInterceptor( filter.setFilterSecurityInterceptor(new MockFilterSecurityInterceptor(
@ -170,7 +160,23 @@ public class SecurityEnforcementFilterTests extends TestCase {
filter.afterPropertiesSet(); filter.afterPropertiesSet();
fail("Should have thrown IllegalArgumentException"); fail("Should have thrown IllegalArgumentException");
} catch (IllegalArgumentException expected) { } 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 // Test
SecurityEnforcementFilter filter = new SecurityEnforcementFilter(); SecurityEnforcementFilter filter = new SecurityEnforcementFilter();
filter.setFilterSecurityInterceptor(interceptor); filter.setFilterSecurityInterceptor(interceptor);
filter.setLoginFormUrl("/login.jsp"); filter.setAuthenticationEntryPoint(new MockAuthenticationEntryPoint(
"/login.jsp"));
MockHttpServletResponse response = new MockHttpServletResponse(); MockHttpServletResponse response = new MockHttpServletResponse();
filter.doFilter(request, response, chain); 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 junit.framework.TestCase;
import net.sf.acegisecurity.Authentication; import net.sf.acegisecurity.Authentication;
import net.sf.acegisecurity.MockAuthenticationEntryPoint;
import net.sf.acegisecurity.MockAuthenticationManager; import net.sf.acegisecurity.MockAuthenticationManager;
import net.sf.acegisecurity.MockFilterConfig; import net.sf.acegisecurity.MockFilterConfig;
import net.sf.acegisecurity.MockHttpServletRequest; import net.sf.acegisecurity.MockHttpServletRequest;
@ -130,6 +131,10 @@ public class BasicProcessingFilterTests extends TestCase {
BasicProcessingFilter filter = new BasicProcessingFilter(); BasicProcessingFilter filter = new BasicProcessingFilter();
filter.setAuthenticationManager(new MockAuthenticationManager()); filter.setAuthenticationManager(new MockAuthenticationManager());
assertTrue(filter.getAuthenticationManager() != null); assertTrue(filter.getAuthenticationManager() != null);
filter.setAuthenticationEntryPoint(new MockAuthenticationEntryPoint(
"sx"));
assertTrue(filter.getAuthenticationEntryPoint() != null);
} }
public void testInvalidBasicAuthorizationTokenIsIgnored() public void testInvalidBasicAuthorizationTokenIsIgnored()
@ -228,10 +233,25 @@ public class BasicProcessingFilterTests extends TestCase {
assertTrue(request.getSession().getAttribute(HttpSessionIntegrationFilter.ACEGI_SECURITY_AUTHENTICATION_KEY) == null); 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() public void testStartupDetectsMissingAuthenticationManager()
throws Exception { throws Exception {
try { try {
BasicProcessingFilter filter = new BasicProcessingFilter(); BasicProcessingFilter filter = new BasicProcessingFilter();
filter.setAuthenticationEntryPoint(new MockAuthenticationEntryPoint(
"x"));
filter.afterPropertiesSet(); filter.afterPropertiesSet();
fail("Should have thrown IllegalArgumentException"); fail("Should have thrown IllegalArgumentException");
} catch (IllegalArgumentException expected) { } catch (IllegalArgumentException expected) {
@ -293,7 +313,7 @@ public class BasicProcessingFilterTests extends TestCase {
chain); chain);
assertTrue(request.getSession().getAttribute(HttpSessionIntegrationFilter.ACEGI_SECURITY_AUTHENTICATION_KEY) == null); assertTrue(request.getSession().getAttribute(HttpSessionIntegrationFilter.ACEGI_SECURITY_AUTHENTICATION_KEY) == null);
assertEquals(403, response.getError()); assertEquals(401, response.getError());
} }
public void testWrongPasswordReturnsForbidden() throws Exception { public void testWrongPasswordReturnsForbidden() throws Exception {
@ -325,7 +345,7 @@ public class BasicProcessingFilterTests extends TestCase {
chain); chain);
assertTrue(request.getSession().getAttribute(HttpSessionIntegrationFilter.ACEGI_SECURITY_AUTHENTICATION_KEY) == null); assertTrue(request.getSession().getAttribute(HttpSessionIntegrationFilter.ACEGI_SECURITY_AUTHENTICATION_KEY) == null);
assertEquals(403, response.getError()); assertEquals(401, response.getError());
} }
private void executeFilterInContainerSimulator(FilterConfig filterConfig, private void executeFilterInContainerSimulator(FilterConfig filterConfig,

View File

@ -49,6 +49,11 @@
<bean id="basicProcessingFilter" class="net.sf.acegisecurity.ui.basicauth.BasicProcessingFilter"> <bean id="basicProcessingFilter" class="net.sf.acegisecurity.ui.basicauth.BasicProcessingFilter">
<property name="authenticationManager"><ref bean="authenticationManager"/></property> <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> </bean>
</beans> </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 so you should configure a <literal>ContextLoaderListener</literal> in
<literal>web.xml</literal>.</para> <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> beans:</para>
<programlisting>&lt;bean id="securityEnforcementFilter" class="net.sf.acegisecurity.intercept.web.SecurityEnforcementFilter"&gt; <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="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;property name="loginFormUrl"&gt;&lt;value&gt;/acegilogin.jsp&lt;/value&gt;&lt;/property&gt;
&lt;/bean&gt; &lt;/bean&gt;
@ -559,16 +563,21 @@
&lt;/property&gt; &lt;/property&gt;
&lt;/bean&gt;</programlisting> &lt;/bean&gt;</programlisting>
<para>The <literal>loginFormUrl</literal> is where the filter will <para>The <literal>AuthenticationEntryPoint</literal> will be called
redirect the user's browser if they request a secure HTTP resource but if the user requests a secure HTTP resource but they are not
they are not authenticated. If the user is authenticated, a "403 authenticated. The class handles presenting the appropriate response
Forbidden" response will be returned to the browser. All paths are to the user so that authentication can begin. Two concrete
relative to the web application root.</para> 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 <para>The <literal>SecurityEnforcementFilter</literal> primarily
provides redirection and session management support. It delegates provides session management support and initiates authentication when
actual <literal>FilterInvocation</literal> security decisions to the required. It delegates actual <literal>FilterInvocation</literal>
configured <literal>FilterSecurityInterceptor</literal>.</para> security decisions to the configured
<literal>FilterSecurityInterceptor</literal>.</para>
<para>Like any other security interceptor, the <para>Like any other security interceptor, the
<literal>FilterSecurityInterceptor</literal> requires a reference to <literal>FilterSecurityInterceptor</literal> requires a reference to
@ -1560,19 +1569,18 @@ public boolean supports(Class clazz);</programlisting></para>
<sect2 id="security-ui-http-basic"> <sect2 id="security-ui-http-basic">
<title>HTTP Basic Authentication</title> <title>HTTP Basic Authentication</title>
<para>Primarily to cater for the needs of remoting protocols such as <para>The Acegi Security System for Spring provides a
Hessian and Burlap, the Acegi Security System for Spring provides a
<literal>BasicProcessingFilter</literal> which is capable of <literal>BasicProcessingFilter</literal> which is capable of
processing authentication credentials presented in HTTP headers (for processing authentication credentials presented in HTTP headers. This
standard authentication of web browser users, we recommend HTTP can be used for authenticating calls made by Spring remoting protocols
Session Authentication). The standard governing HTTP Basic (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 Authentication is defined by RFC 1945, Section 11, and the
<literal>BasicProcessingFilter</literal> conforms with this <literal>BasicProcessingFilter</literal> conforms with this
RFC.</para> RFC.</para>
<para>To implement HTTP Basic Authentication, it is necessary to add <para>To implement HTTP Basic Authentication, it is necessary to add
the following filter to <literal>web.xml</literal>, behind a the following filter to <literal>web.xml</literal>:</para>
<literal>FilterToBeanProxy</literal>:</para>
<para><programlisting>&lt;filter&gt; <para><programlisting>&lt;filter&gt;
&lt;filter-name&gt;Acegi HTTP BASIC Authorization Filter&lt;/filter-name&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 <para>For a discussion of <literal>FilterToBeanProxy</literal>, please
refer to the FilterInvocation Security Interceptor section. The refer to the FilterInvocation Security Interceptor section. The
application context will need to define 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; <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="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> &lt;/bean&gt;</programlisting></para>
<para>The configured <literal>AuthenticationManager</literal> <para>The configured <literal>AuthenticationManager</literal>
processes each authentication request. If authentication fails, a 403 processes each authentication request. If authentication fails, the
(forbidden) response will be returned in response to the HTTP request. configured <literal>AuthenticationEntryPoint</literal> will be used to
If authentication is successful, the resulting 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>Authentication</literal> object will be placed into the
<literal>HttpSession</literal> attribute indicated by <literal>HttpSession</literal> attribute indicated by
<literal>HttpSessionIntegrationFilter.ACEGI_SECURITY_AUTHENTICATION_KEY</literal>. <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 was not attempted because the HTTP header did not contain a supported
authentication request, the filter chain will continue as normal. The authentication request, the filter chain will continue as normal. The
only time the filter chain will be interrupted is if authentication only time the filter chain will be interrupted is if authentication
fails and a 403 response is returned, as discussed in the previous fails and the <literal>AuthenticationEntryPoint</literal> is called,
paragraph.</para> as discussed in the previous paragraph.</para>
<para>HTTP Basic Authentication is recommended to be used instead of <para>HTTP Basic Authentication is recommended to be used instead of
Container Adapters. It can be used in conjunction with HTTP Session Container Adapters. It can be used in conjunction with HTTP Session
Authentication, as demonstrated in the Contacts sample Authentication, as demonstrated in the Contacts sample application.
application.</para> You can also use it instead of HTTP Session Authentication if you
wish.</para>
</sect2> </sect2>
<sect2 id="security-ui-well-known"> <sect2 id="security-ui-well-known">

View File

@ -52,6 +52,11 @@
<bean id="basicProcessingFilter" class="net.sf.acegisecurity.ui.basicauth.BasicProcessingFilter"> <bean id="basicProcessingFilter" class="net.sf.acegisecurity.ui.basicauth.BasicProcessingFilter">
<property name="authenticationManager"><ref bean="authenticationManager"/></property> <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> </bean>
<!-- ~~~~~~~~~~~~~~~~~~~~ AUTHORIZATION DEFINITIONS ~~~~~~~~~~~~~~~~~~~ --> <!-- ~~~~~~~~~~~~~~~~~~~~ AUTHORIZATION DEFINITIONS ~~~~~~~~~~~~~~~~~~~ -->

View File

@ -47,6 +47,11 @@
<bean id="basicProcessingFilter" class="net.sf.acegisecurity.ui.basicauth.BasicProcessingFilter"> <bean id="basicProcessingFilter" class="net.sf.acegisecurity.ui.basicauth.BasicProcessingFilter">
<property name="authenticationManager"><ref bean="authenticationManager"/></property> <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> </bean>
<!-- ~~~~~~~~~~~~~~~~~~~~ AUTHORIZATION DEFINITIONS ~~~~~~~~~~~~~~~~~~~ --> <!-- ~~~~~~~~~~~~~~~~~~~~ AUTHORIZATION DEFINITIONS ~~~~~~~~~~~~~~~~~~~ -->
@ -138,6 +143,10 @@
<bean id="securityEnforcementFilter" class="net.sf.acegisecurity.intercept.web.SecurityEnforcementFilter"> <bean id="securityEnforcementFilter" class="net.sf.acegisecurity.intercept.web.SecurityEnforcementFilter">
<property name="filterSecurityInterceptor"><ref bean="filterInvocationInterceptor"/></property> <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> <property name="loginFormUrl"><value>/acegilogin.jsp</value></property>
</bean> </bean>