Browse files

SES-54: Option to prevent SpnegoAuthenticationProcessingFilter overwr…

…iting authentication in security context
  • Loading branch information...
1 parent 8305b62 commit da45af75d22f8299afab50c15c1747596a5781b8 Mike Wiesner committed Oct 1, 2010
View
184 ...pringframework/security/extensions/kerberos/web/SpnegoAuthenticationProcessingFilter.java
@@ -25,6 +25,7 @@
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
+import org.springframework.security.authentication.AnonymousAuthenticationToken;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
@@ -39,60 +40,65 @@
/**
* Parses the SPNEGO authentication Header, which was generated by the browser
- * and creates a {@link KerberosServiceRequestToken} out if it. It will then call the
- * {@link AuthenticationManager}.
- *
- * <p>A typical Spring Security configuration might look like this:</p>
+ * and creates a {@link KerberosServiceRequestToken} out if it. It will then
+ * call the {@link AuthenticationManager}.
+ *
+ * <p>
+ * A typical Spring Security configuration might look like this:
+ * </p>
+ *
* <pre>
* &lt;beans xmlns=&quot;http://www.springframework.org/schema/beans&quot;
- * xmlns:xsi=&quot;http://www.w3.org/2001/XMLSchema-instance&quot; xmlns:sec=&quot;http://www.springframework.org/schema/security&quot;
- * xsi:schemaLocation=&quot;http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.0.xsd
- * http://www.springframework.org/schema/security http://www.springframework.org/schema/security/spring-security-3.0.xsd&quot;&gt;
- *
- * &lt;sec:http entry-point-ref=&quot;spnegoEntryPoint&quot;&gt;
- * &lt;sec:intercept-url pattern=&quot;/secure/**&quot; access=&quot;IS_AUTHENTICATED_FULLY&quot; /&gt;
- * &lt;sec:custom-filter ref=&quot;spnegoAuthenticationProcessingFilter&quot; position=&quot;BASIC_AUTH_FILTER&quot; /&gt;
- * &lt;/sec:http&gt;
- *
- * &lt;bean id=&quot;spnegoEntryPoint&quot; class=&quot;org.springframework.security.extensions.kerberos.web.SpnegoEntryPoint&quot; /&gt;
- *
- * &lt;bean id=&quot;spnegoAuthenticationProcessingFilter&quot;
- * class=&quot;org.springframework.security.extensions.kerberos.web.SpnegoAuthenticationProcessingFilter&quot;&gt;
- * &lt;property name=&quot;authenticationManager&quot; ref=&quot;authenticationManager&quot; /&gt;
- * &lt;/bean&gt;
- *
- * &lt;sec:authentication-manager alias=&quot;authenticationManager&quot;&gt;
- * &lt;sec:authentication-provider ref=&quot;kerberosServiceAuthenticationProvider&quot; /&gt;
- * &lt;/sec:authentication-manager&gt;
- *
- * &lt;bean id=&quot;kerberosServiceAuthenticationProvider&quot;
- * class=&quot;org.springframework.security.extensions.kerberos.KerberosServiceAuthenticationProvider&quot;&gt;
- * &lt;property name=&quot;ticketValidator&quot;&gt;
- * &lt;bean class=&quot;org.springframework.security.extensions.kerberos.SunJaasKerberosTicketValidator&quot;&gt;
- * &lt;property name=&quot;servicePrincipal&quot; value=&quot;HTTP/web.springsource.com&quot; /&gt;
- * &lt;property name=&quot;keyTabLocation&quot; value=&quot;classpath:http-java.keytab&quot; /&gt;
- * &lt;/bean&gt;
- * &lt;/property&gt;
- * &lt;property name=&quot;userDetailsService&quot; ref=&quot;inMemoryUserDetailsService&quot; /&gt;
- * &lt;/bean&gt;
- *
- * &lt;bean id=&quot;inMemoryUserDetailsService&quot;
- * class=&quot;org.springframework.security.core.userdetails.memory.InMemoryDaoImpl&quot;&gt;
- * &lt;property name=&quot;userProperties&quot;&gt;
- * &lt;value&gt;
- * mike@SECPOD.DE=notUsed,ROLE_ADMIN
- * &lt;/value&gt;
- * &lt;/property&gt;
- * &lt;/bean&gt;
+ * xmlns:xsi=&quot;http://www.w3.org/2001/XMLSchema-instance&quot; xmlns:sec=&quot;http://www.springframework.org/schema/security&quot;
+ * xsi:schemaLocation=&quot;http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.0.xsd
+ * http://www.springframework.org/schema/security http://www.springframework.org/schema/security/spring-security-3.0.xsd&quot;&gt;
+ *
+ * &lt;sec:http entry-point-ref=&quot;spnegoEntryPoint&quot;&gt;
+ * &lt;sec:intercept-url pattern=&quot;/secure/**&quot; access=&quot;IS_AUTHENTICATED_FULLY&quot; /&gt;
+ * &lt;sec:custom-filter ref=&quot;spnegoAuthenticationProcessingFilter&quot; position=&quot;BASIC_AUTH_FILTER&quot; /&gt;
+ * &lt;/sec:http&gt;
+ *
+ * &lt;bean id=&quot;spnegoEntryPoint&quot; class=&quot;org.springframework.security.extensions.kerberos.web.SpnegoEntryPoint&quot; /&gt;
+ *
+ * &lt;bean id=&quot;spnegoAuthenticationProcessingFilter&quot;
+ * class=&quot;org.springframework.security.extensions.kerberos.web.SpnegoAuthenticationProcessingFilter&quot;&gt;
+ * &lt;property name=&quot;authenticationManager&quot; ref=&quot;authenticationManager&quot; /&gt;
+ * &lt;/bean&gt;
+ *
+ * &lt;sec:authentication-manager alias=&quot;authenticationManager&quot;&gt;
+ * &lt;sec:authentication-provider ref=&quot;kerberosServiceAuthenticationProvider&quot; /&gt;
+ * &lt;/sec:authentication-manager&gt;
+ *
+ * &lt;bean id=&quot;kerberosServiceAuthenticationProvider&quot;
+ * class=&quot;org.springframework.security.extensions.kerberos.KerberosServiceAuthenticationProvider&quot;&gt;
+ * &lt;property name=&quot;ticketValidator&quot;&gt;
+ * &lt;bean class=&quot;org.springframework.security.extensions.kerberos.SunJaasKerberosTicketValidator&quot;&gt;
+ * &lt;property name=&quot;servicePrincipal&quot; value=&quot;HTTP/web.springsource.com&quot; /&gt;
+ * &lt;property name=&quot;keyTabLocation&quot; value=&quot;classpath:http-java.keytab&quot; /&gt;
+ * &lt;/bean&gt;
+ * &lt;/property&gt;
+ * &lt;property name=&quot;userDetailsService&quot; ref=&quot;inMemoryUserDetailsService&quot; /&gt;
+ * &lt;/bean&gt;
+ *
+ * &lt;bean id=&quot;inMemoryUserDetailsService&quot;
+ * class=&quot;org.springframework.security.core.userdetails.memory.InMemoryDaoImpl&quot;&gt;
+ * &lt;property name=&quot;userProperties&quot;&gt;
+ * &lt;value&gt;
+ * mike@SECPOD.DE=notUsed,ROLE_ADMIN
+ * &lt;/value&gt;
+ * &lt;/property&gt;
+ * &lt;/bean&gt;
* &lt;/beans&gt;
* </pre>
*
- * If you get a "GSSException: Channel binding mismatch (Mechanism level:ChannelBinding not provided!)
- * have a look at this <a href="http://bugs.sun.com/view_bug.do?bug_id=6851973">bug</a>.<br />
- * A workaround unti this is fixed in the JVM is to change
- * HKEY_LOCAL_MACHINE\System\CurrentControlSet\Control\LSA\SuppressExtendedProtection to 0x02
+ * If you get a "GSSException: Channel binding mismatch (Mechanism
+ * level:ChannelBinding not provided!) have a look at this <a
+ * href="http://bugs.sun.com/view_bug.do?bug_id=6851973">bug</a>.<br />
+ * A workaround unti this is fixed in the JVM is to change
+ * HKEY_LOCAL_MACHINE\System
+ * \CurrentControlSet\Control\LSA\SuppressExtendedProtection to 0x02
+ *
*
- *
* @author Mike Wiesner
* @since 1.0
* @version $Id$
@@ -101,50 +107,59 @@
*/
public class SpnegoAuthenticationProcessingFilter extends GenericFilterBean {
-
private AuthenticationManager authenticationManager;
private AuthenticationSuccessHandler successHandler;
private AuthenticationFailureHandler failureHandler;
-
+ private boolean skipIfAlreadyAuthenticated = true;
+
- /* (non-Javadoc)
- * @see javax.servlet.Filter#doFilter(javax.servlet.ServletRequest, javax.servlet.ServletResponse, javax.servlet.FilterChain)
+ /*
+ * (non-Javadoc)
+ *
+ * @see javax.servlet.Filter#doFilter(javax.servlet.ServletRequest,
+ * javax.servlet.ServletResponse, javax.servlet.FilterChain)
*/
- public void doFilter(ServletRequest req, ServletResponse res,
- FilterChain chain) throws IOException, ServletException {
+ public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) req;
HttpServletResponse response = (HttpServletResponse) res;
- String header = request.getHeader("Authorization");
+ if (skipIfAlreadyAuthenticated) {
+ Authentication existingAuth = SecurityContextHolder.getContext().getAuthentication();
+
+ if (existingAuth != null && existingAuth.isAuthenticated()
+ && (existingAuth instanceof AnonymousAuthenticationToken) == false) {
+ chain.doFilter(request, response);
+ return;
+ }
+ }
+ String header = request.getHeader("Authorization");
if ((header != null) && header.startsWith("Negotiate ")) {
if (logger.isDebugEnabled()) {
- logger.debug("Received Negotiate Header for request "+ request.getRequestURL()+ ": " + header);
+ logger.debug("Received Negotiate Header for request " + request.getRequestURL() + ": " + header);
}
byte[] base64Token = header.substring(10).getBytes("UTF-8");
byte[] kerberosTicket = Base64.decode(base64Token);
- KerberosServiceRequestToken authenticationRequest = new KerberosServiceRequestToken(
- kerberosTicket);
+ KerberosServiceRequestToken authenticationRequest = new KerberosServiceRequestToken(kerberosTicket);
Authentication authentication;
try {
- authentication = authenticationManager
- .authenticate(authenticationRequest);
+ authentication = authenticationManager.authenticate(authenticationRequest);
} catch (AuthenticationException e) {
- // That shouldn't happen, as it is most likely a wrong configuration on the server side
- logger.warn("Negotiate Header was invalid: "+header, e);
+ // That shouldn't happen, as it is most likely a wrong
+ // configuration on the server side
+ logger.warn("Negotiate Header was invalid: " + header, e);
SecurityContextHolder.clearContext();
if (failureHandler != null) {
failureHandler.onAuthenticationFailure(request, response, e);
- }
- else {
+ } else {
response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
response.flushBuffer();
}
return;
}
if (successHandler != null) {
- successHandler.onAuthenticationSuccess(request, response, authentication);
+ successHandler.onAuthenticationSuccess(request, response, authentication);
}
SecurityContextHolder.getContext().setAuthentication(authentication);
}
@@ -155,46 +170,59 @@ public void doFilter(ServletRequest req, ServletResponse res,
/**
* The authentication manager for validating the ticket.
- *
+ *
* @param authenticationManager
*/
public void setAuthenticationManager(AuthenticationManager authenticationManager) {
this.authenticationManager = authenticationManager;
}
-
+
/**
- * This handler is called after a successful authentication.
- * One can add additional authentication behavior by setting this.<br />
+ * This handler is called after a successful authentication. One can add
+ * additional authentication behavior by setting this.<br />
* Default is null, which means nothing additional happens
- *
+ *
* @param successHandler
*/
public void setSuccessHandler(AuthenticationSuccessHandler successHandler) {
this.successHandler = successHandler;
}
/**
- * This handler is called after a failure authentication.
- * In most cases you only get Kerberos/SPNEGO failures with a wrong server
- * or network configurations and not during runtime. If the client encounters
- * an error, he will just stop the communication with server and therefore
- * this handler will not be called in this case.<br />
+ * This handler is called after a failure authentication. In most cases you
+ * only get Kerberos/SPNEGO failures with a wrong server or network
+ * configurations and not during runtime. If the client encounters an error,
+ * he will just stop the communication with server and therefore this
+ * handler will not be called in this case.<br />
* Default is null, which means that the Filter returns the HTTP 500 code
*
* @param failureHandler
*/
public void setFailureHandler(AuthenticationFailureHandler failureHandler) {
this.failureHandler = failureHandler;
}
+
+
+ /**
+ * Should Kerberos authentication be skipped if a user is already authenticated
+ * for this request (e.g. in the HTTP session).
+ *
+ * @param skipIfAlreadyAuthenticated default is true
+ */
+ public void setSkipIfAlreadyAuthenticated(boolean skipIfAlreadyAuthenticated) {
+ this.skipIfAlreadyAuthenticated = skipIfAlreadyAuthenticated;
+ }
- /* (non-Javadoc)
- * @see org.springframework.web.filter.GenericFilterBean#afterPropertiesSet()
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * org.springframework.web.filter.GenericFilterBean#afterPropertiesSet()
*/
@Override
public void afterPropertiesSet() throws ServletException {
super.afterPropertiesSet();
Assert.notNull(this.authenticationManager, "authenticationManager must be specified");
}
-
}
View
83 ...gframework/security/extensions/kerberos/web/SpnegoAuthenticationProcessingFilterTest.java
@@ -30,8 +30,10 @@
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
+import org.springframework.security.authentication.AnonymousAuthenticationToken;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.BadCredentialsException;
+import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.authority.AuthorityUtils;
@@ -42,15 +44,13 @@
/**
* Test class for {@link SpnegoAuthenticationProcessingFilter}
- *
+ *
* @author Mike Wiesner
* @since 1.0
* @version $Id$
*/
public class SpnegoAuthenticationProcessingFilterTest {
-
-
private SpnegoAuthenticationProcessingFilter filter;
private AuthenticationManager authenticationManager;
private HttpServletRequest request;
@@ -68,7 +68,6 @@
private static final String TOKEN_PREFIX = "Negotiate ";
private static final BadCredentialsException BCE = new BadCredentialsException("");
-
@Before
public void before() throws Exception {
// mocking
@@ -85,33 +84,32 @@ public void before() throws Exception {
public void testEverythingWorks() throws Exception {
everythingWorks();
}
-
+
@Test
public void testEverythingWorksWithHandlers() throws Exception {
createHandler();
everythingWorks();
verify(successHandler).onAuthenticationSuccess(request, response, AUTHENTICATION);
- verify(failureHandler, never()).onAuthenticationFailure(any(HttpServletRequest.class),
- any(HttpServletResponse.class), any(AuthenticationException.class));
+ verify(failureHandler, never()).onAuthenticationFailure(any(HttpServletRequest.class), any(HttpServletResponse.class),
+ any(AuthenticationException.class));
}
private void everythingWorks() throws IOException, ServletException {
// stubbing
- when(request.getHeader(HEADER)).thenReturn(TOKEN_PREFIX+TEST_TOKEN_BASE64);
+ when(request.getHeader(HEADER)).thenReturn(TOKEN_PREFIX + TEST_TOKEN_BASE64);
when(authenticationManager.authenticate(new KerberosServiceRequestToken(TEST_TOKEN))).thenReturn(AUTHENTICATION);
// testing
filter.doFilter(request, response, chain);
verify(chain).doFilter(request, response);
assertEquals(AUTHENTICATION, SecurityContextHolder.getContext().getAuthentication());
}
-
-
@Test
public void testNoHeader() throws Exception {
filter.doFilter(request, response, chain);
- // If the header is not present, the filter is not allowed to call authenticate()
+ // If the header is not present, the filter is not allowed to call
+ // authenticate()
verify(authenticationManager, never()).authenticate(any(Authentication.class));
// chain should go on
verify(chain).doFilter(request, response);
@@ -123,20 +121,71 @@ public void testAuthenticationFails() throws Exception {
authenticationFails();
verify(response).setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
}
-
+
@Test
public void testAuthenticationFailsWithHandlers() throws Exception {
createHandler();
authenticationFails();
verify(failureHandler).onAuthenticationFailure(request, response, BCE);
- verify(successHandler, never()).onAuthenticationSuccess(any(HttpServletRequest.class),
- any(HttpServletResponse.class), any(Authentication.class));
+ verify(successHandler, never()).onAuthenticationSuccess(any(HttpServletRequest.class), any(HttpServletResponse.class),
+ any(Authentication.class));
verify(response, never()).setStatus(anyInt());
}
+ @Test
+ public void testAlreadyAuthenticated() throws Exception {
+ try {
+ Authentication existingAuth = new UsernamePasswordAuthenticationToken("mike", "mike",
+ AuthorityUtils.createAuthorityList("ROLE_TEST"));
+ SecurityContextHolder.getContext().setAuthentication(existingAuth);
+ when(request.getHeader(HEADER)).thenReturn(TOKEN_PREFIX + TEST_TOKEN_BASE64);
+ filter.doFilter(request, response, chain);
+ verify(authenticationManager, never()).authenticate(any(Authentication.class));
+ } finally {
+ SecurityContextHolder.clearContext();
+ }
+ }
+
+ @Test
+ public void testAlreadyAuthenticatedWithNotAuthenticatedToken() throws Exception {
+ try {
+ // this token is not authenticated yet!
+ Authentication existingAuth = new UsernamePasswordAuthenticationToken("mike", "mike");
+ SecurityContextHolder.getContext().setAuthentication(existingAuth);
+ everythingWorks();
+ } finally {
+ SecurityContextHolder.clearContext();
+ }
+ }
+
+ @Test
+ public void testAlreadyAuthenticatedWithAnonymousToken() throws Exception {
+ try {
+ Authentication existingAuth = new AnonymousAuthenticationToken("test", "mike",
+ AuthorityUtils.createAuthorityList("ROLE_TEST"));
+ SecurityContextHolder.getContext().setAuthentication(existingAuth);
+ everythingWorks();
+ } finally {
+ SecurityContextHolder.clearContext();
+ }
+ }
+
+ @Test
+ public void testAlreadyAuthenticatedNotActive() throws Exception {
+ try {
+ Authentication existingAuth = new UsernamePasswordAuthenticationToken("mike", "mike",
+ AuthorityUtils.createAuthorityList("ROLE_TEST"));
+ SecurityContextHolder.getContext().setAuthentication(existingAuth);
+ filter.setSkipIfAlreadyAuthenticated(false);
+ everythingWorks();
+ } finally {
+ SecurityContextHolder.clearContext();
+ }
+ }
+
private void authenticationFails() throws IOException, ServletException {
// stubbing
- when(request.getHeader(HEADER)).thenReturn(TOKEN_PREFIX+TEST_TOKEN_BASE64);
+ when(request.getHeader(HEADER)).thenReturn(TOKEN_PREFIX + TEST_TOKEN_BASE64);
when(authenticationManager.authenticate(any(Authentication.class))).thenThrow(BCE);
// testing
@@ -145,19 +194,17 @@ private void authenticationFails() throws IOException, ServletException {
// future version should call some error handler
verify(chain, never()).doFilter(any(ServletRequest.class), any(ServletResponse.class));
}
-
+
private void createHandler() {
successHandler = mock(AuthenticationSuccessHandler.class);
failureHandler = mock(AuthenticationFailureHandler.class);
filter.setSuccessHandler(successHandler);
filter.setFailureHandler(failureHandler);
}
-
@After
public void after() {
SecurityContextHolder.clearContext();
}
-
}

0 comments on commit da45af7

Please sign in to comment.