SEC-53: BasicProcessingFilter only to reauthenticate if the SecurityContextHolder contains an unauthenticated Authentication, or an Authentication with a different username.

This commit is contained in:
Ben Alex 2005-11-03 09:45:30 +00:00
parent 690ab27a52
commit d9be0f86fd
2 changed files with 51 additions and 36 deletions

View File

@ -12,6 +12,7 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
package net.sf.acegisecurity.ui.basicauth; package net.sf.acegisecurity.ui.basicauth;
import net.sf.acegisecurity.Authentication; import net.sf.acegisecurity.Authentication;
@ -45,13 +46,13 @@ import javax.servlet.http.HttpServletResponse;
/** /**
* Processes a HTTP request's BASIC authorization headers, putting the result * Processes a HTTP request's BASIC authorization headers, putting the result
* into the <code>SecurityContextHolder</code>. * into the <code>SecurityContextHolder</code>.
* *
* <p> * <p>
* For a detailed background on what this filter is designed to process, refer * 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 * 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. * 11.1</A>. Any realm name presented in the HTTP request is ignored.
* </p> * </p>
* *
* <p> * <p>
* In summary, this filter is responsible for processing any request that has a * In summary, this filter is responsible for processing any request that has a
* HTTP request header of <code>Authorization</code> with an authentication * HTTP request header of <code>Authorization</code> with an authentication
@ -60,28 +61,28 @@ import javax.servlet.http.HttpServletResponse;
* "Aladdin" with password "open sesame" the following header would be * "Aladdin" with password "open sesame" the following header would be
* presented: * presented:
* </p> * </p>
* *
* <p> * <p>
* <code>Authorization: Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ==</code>. * <code>Authorization: Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ==</code>.
* </p> * </p>
* *
* <p> * <p>
* This filter can be used to provide BASIC authentication services to both * This filter can be used to provide BASIC authentication services to both
* remoting protocol clients (such as Hessian and SOAP) as well as standard * remoting protocol clients (such as Hessian and SOAP) as well as standard
* user agents (such as Internet Explorer and Netscape). * user agents (such as Internet Explorer and Netscape).
* </p> * </p>
* *
* <P> * <P>
* If authentication is successful, the resulting {@link Authentication} object * If authentication is successful, the resulting {@link Authentication} object
* will be placed into the <code>SecurityContextHolder</code>. * will be placed into the <code>SecurityContextHolder</code>.
* </p> * </p>
* *
* <p> * <p>
* If authentication fails, an {@link AuthenticationEntryPoint} implementation * If authentication fails, an {@link AuthenticationEntryPoint} implementation
* is called. Usually this should be {@link BasicProcessingFilterEntryPoint}, * is called. Usually this should be {@link BasicProcessingFilterEntryPoint},
* which will prompt the user to authenticate again via BASIC authentication. * which will prompt the user to authenticate again via BASIC authentication.
* </p> * </p>
* *
* <p> * <p>
* Basic authentication is an attractive protocol because it is simple and * Basic authentication is an attractive protocol because it is simple and
* widely deployed. However, it still transmits a password in clear text and * widely deployed. However, it still transmits a password in clear text and
@ -90,7 +91,7 @@ import javax.servlet.http.HttpServletResponse;
* authentication wherever possible. See {@link * authentication wherever possible. See {@link
* net.sf.acegisecurity.ui.digestauth.DigestProcessingFilter}. * net.sf.acegisecurity.ui.digestauth.DigestProcessingFilter}.
* </p> * </p>
* *
* <p> * <p>
* <b>Do not use this class directly.</b> Instead configure * <b>Do not use this class directly.</b> Instead configure
* <code>web.xml</code> to use the {@link * <code>web.xml</code> to use the {@link
@ -101,10 +102,17 @@ import javax.servlet.http.HttpServletResponse;
* @version $Id$ * @version $Id$
*/ */
public class BasicProcessingFilter implements Filter, InitializingBean { public class BasicProcessingFilter implements Filter, InitializingBean {
//~ Static fields/initializers =============================================
private static final Log logger = LogFactory.getLog(BasicProcessingFilter.class); private static final Log logger = LogFactory.getLog(BasicProcessingFilter.class);
//~ Instance fields ========================================================
private AuthenticationEntryPoint authenticationEntryPoint; private AuthenticationEntryPoint authenticationEntryPoint;
private AuthenticationManager authenticationManager; private AuthenticationManager authenticationManager;
//~ Methods ================================================================
public void setAuthenticationEntryPoint( public void setAuthenticationEntryPoint(
AuthenticationEntryPoint authenticationEntryPoint) { AuthenticationEntryPoint authenticationEntryPoint) {
this.authenticationEntryPoint = authenticationEntryPoint; this.authenticationEntryPoint = authenticationEntryPoint;
@ -130,8 +138,7 @@ public class BasicProcessingFilter implements Filter, InitializingBean {
"An AuthenticationEntryPoint is required"); "An AuthenticationEntryPoint is required");
} }
public void destroy() { public void destroy() {}
}
public void doFilter(ServletRequest request, ServletResponse response, public void doFilter(ServletRequest request, ServletResponse response,
FilterChain chain) throws IOException, ServletException { FilterChain chain) throws IOException, ServletException {
@ -165,40 +172,47 @@ public class BasicProcessingFilter implements Filter, InitializingBean {
password = token.substring(delim + 1); password = token.substring(delim + 1);
} }
UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(username, // Only reauthenticate if username doesn't match ContextHolder and user isn't authenticated (see SEC-53)
password); Authentication existingAuth = SecurityContextHolder.getContext()
authRequest.setDetails(new WebAuthenticationDetails(httpRequest, .getAuthentication();
false));
Authentication authResult; if ((existingAuth == null)
|| !existingAuth.getName().equals(username)
|| !existingAuth.isAuthenticated()) {
UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(username,
password);
authRequest.setDetails(new WebAuthenticationDetails(
httpRequest, false));
try { Authentication authResult;
authResult = authenticationManager.authenticate(authRequest);
} catch (AuthenticationException failed) { try {
// Authentication failed authResult = authenticationManager.authenticate(authRequest);
if (logger.isDebugEnabled()) { } catch (AuthenticationException failed) {
logger.debug("Authentication request for user: " + // Authentication failed
username + " failed: " + failed.toString()); if (logger.isDebugEnabled()) {
logger.debug("Authentication request for user: "
+ username + " failed: " + failed.toString());
}
SecurityContextHolder.getContext().setAuthentication(null);
authenticationEntryPoint.commence(request, response, failed);
return;
} }
SecurityContextHolder.getContext().setAuthentication(null); // Authentication success
authenticationEntryPoint.commence(request, response, failed); if (logger.isDebugEnabled()) {
logger.debug("Authentication success: "
+ authResult.toString());
}
return; SecurityContextHolder.getContext().setAuthentication(authResult);
} }
// Authentication success
if (logger.isDebugEnabled()) {
logger.debug("Authentication success: " +
authResult.toString());
}
SecurityContextHolder.getContext().setAuthentication(authResult);
} }
chain.doFilter(request, response); chain.doFilter(request, response);
} }
public void init(FilterConfig arg0) throws ServletException { public void init(FilterConfig arg0) throws ServletException {}
}
} }

View File

@ -183,6 +183,7 @@ public class BasicProcessingFilterTests extends TestCase {
MockHttpServletResponse response = new MockHttpServletResponse(); MockHttpServletResponse response = new MockHttpServletResponse();
// Test // Test
assertNull(SecurityContextHolder.getContext().getAuthentication());
executeFilterInContainerSimulator(config, filter, request, response, executeFilterInContainerSimulator(config, filter, request, response,
chain); chain);
@ -280,7 +281,7 @@ public class BasicProcessingFilterTests extends TestCase {
// NOW PERFORM FAILED AUTHENTICATION // NOW PERFORM FAILED AUTHENTICATION
// Setup our HTTP request // Setup our HTTP request
token = "marissa:WRONG_PASSWORD"; token = "otherUser:WRONG_PASSWORD";
request = new MockHttpServletRequest(); request = new MockHttpServletRequest();
request.addHeader("Authorization", request.addHeader("Authorization",
"Basic " + new String(Base64.encodeBase64(token.getBytes()))); "Basic " + new String(Base64.encodeBase64(token.getBytes())));