diff --git a/spring-security-kerberos-core/src/main/java/org/springframework/security/extensions/kerberos/web/SpnegoEntryPoint.java b/spring-security-kerberos-core/src/main/java/org/springframework/security/extensions/kerberos/web/SpnegoEntryPoint.java index c45c5c8..777b13b 100644 --- a/spring-security-kerberos-core/src/main/java/org/springframework/security/extensions/kerberos/web/SpnegoEntryPoint.java +++ b/spring-security-kerberos-core/src/main/java/org/springframework/security/extensions/kerberos/web/SpnegoEntryPoint.java @@ -15,37 +15,97 @@ */ package org.springframework.security.extensions.kerberos.web; -import java.io.IOException; - -import javax.servlet.ServletException; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; - import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.springframework.security.core.AuthenticationException; import org.springframework.security.web.AuthenticationEntryPoint; +import org.springframework.security.web.util.UrlUtils; +import org.springframework.util.Assert; +import org.springframework.util.StringUtils; + +import javax.servlet.RequestDispatcher; +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; /** * Sends back a request for a Negotiate Authentication to the browser. * + *

With optional configured forwardUrl it is possible to use form + * login as fallback authentication.

+ * + *

This approach enables security configuration to use SPNEGO in combination + * with login form as fallback for clients that do not support this kind of + * authentication. Set Response Code 401 - unauthorized and forward to login + * page. A useful scenario might be an environment where windows domain is + * present but it is required to access the application also from non domain + * client devices. One could use a combination with form based LDAP login.

+ * + *

See spnego-with-form-login.xml in + * spring-security-kerberos-sample for details

+ * + * * @author Mike Wiesner + * @author Andre Schaefer, Namics AG * @since 1.0 * @see SpnegoAuthenticationProcessingFilter */ public class SpnegoEntryPoint implements AuthenticationEntryPoint { - private static final Log LOG = LogFactory.getLog(SpnegoEntryPoint.class); + private static final Log LOG = LogFactory.getLog(SpnegoEntryPoint.class); - @Override - public void commence(HttpServletRequest request, HttpServletResponse response, - AuthenticationException ex) throws IOException, ServletException { - if (LOG.isDebugEnabled()) { - LOG.debug("Sending back Negotiate Header for request: "+request.getRequestURL()); - } - response.addHeader("WWW-Authenticate", "Negotiate"); - response.setStatus(HttpServletResponse.SC_UNAUTHORIZED); - response.flushBuffer(); - } + private final String forwardUrl; + + private final boolean forward; + + /** + * Instantiates a new spnego entry point. Using this constructor the + * EntryPoint will Sends back a request for a Negotiate Authentication to + * the browser without providing a fallback mechanism for login, Use + * constructor with forwardUrl to provide form based login. + */ + public SpnegoEntryPoint() { + this(null); + } + + /** + * Instantiates a new spnego entry point. This constructor enables security + * configuration to use SPNEGO in combination with login form as fallback + * for clients that do not support this kind of authentication. + * + * @param forwardUrl + * URL where the login page can be found. Should be + * relative to the web-app context path (include a leading + * {@code /}) and can't be absolute URL. + */ + public SpnegoEntryPoint(String forwardUrl) { + if (StringUtils.hasText(forwardUrl)) { + Assert.isTrue(UrlUtils.isValidRedirectUrl(forwardUrl), "Forward url specified must be a valid forward URL"); + Assert.isTrue(!UrlUtils.isAbsoluteUrl(forwardUrl), "Forward url specified must not be absolute"); + this.forwardUrl = forwardUrl; + this.forward = true; + } else { + this.forwardUrl = null; + this.forward = false; + } + } + + public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException ex) + throws IOException, ServletException { + if (LOG.isDebugEnabled()) { + LOG.debug("Add header WWW-Authenticate:Negotiate to " + request.getRequestURL() + ", forward: " + + (forward ? forwardUrl : "no")); + } + response.addHeader("WWW-Authenticate", "Negotiate"); + response.setStatus(HttpServletResponse.SC_UNAUTHORIZED); + + if (forward) { + RequestDispatcher dispatcher = request.getRequestDispatcher(forwardUrl); + dispatcher.forward(request, response); + } else { + response.flushBuffer(); + } + } } diff --git a/spring-security-kerberos-core/src/test/java/org/springframework/security/extensions/kerberos/web/SpnegoEntryPointTest.java b/spring-security-kerberos-core/src/test/java/org/springframework/security/extensions/kerberos/web/SpnegoEntryPointTest.java index 20c4981..374c902 100644 --- a/spring-security-kerberos-core/src/test/java/org/springframework/security/extensions/kerberos/web/SpnegoEntryPointTest.java +++ b/spring-security-kerberos-core/src/test/java/org/springframework/security/extensions/kerberos/web/SpnegoEntryPointTest.java @@ -15,33 +15,68 @@ */ package org.springframework.security.extensions.kerberos.web; -import static org.mockito.Mockito.*; +import org.junit.Test; +import javax.servlet.RequestDispatcher; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; -import org.junit.Test; +import static org.mockito.Matchers.anyString; +import static org.mockito.Mockito.*; /** * Test class for {@link SpnegoEntryPoint} * * @author Mike Wiesner * @author Janne Valkealahti + * @author Andre Schaefer, Namics AG * @since 1.0 */ public class SpnegoEntryPointTest { - private SpnegoEntryPoint entryPoint = new SpnegoEntryPoint(); + private SpnegoEntryPoint entryPoint = new SpnegoEntryPoint(); - @Test - public void testEntryPointOk() throws Exception { - HttpServletRequest request = mock(HttpServletRequest.class); - HttpServletResponse response = mock(HttpServletResponse.class); + @Test + public void testEntryPointOk() throws Exception { + HttpServletRequest request = mock(HttpServletRequest.class); + HttpServletResponse response = mock(HttpServletResponse.class); - entryPoint.commence(request, response, null); + entryPoint.commence(request, response, null); - verify(response).addHeader("WWW-Authenticate", "Negotiate"); - verify(response).setStatus(HttpServletResponse.SC_UNAUTHORIZED); - } + verify(response).addHeader("WWW-Authenticate", "Negotiate"); + verify(response).setStatus(HttpServletResponse.SC_UNAUTHORIZED); + } + + @Test + public void testEntryPointOkWithDispatcher() throws Exception { + SpnegoEntryPoint entryPoint = new SpnegoEntryPoint(); + HttpServletResponse response = mock(HttpServletResponse.class); + HttpServletRequest request = mock(HttpServletRequest.class); + RequestDispatcher requestDispatcher = mock(RequestDispatcher.class); + when(request.getRequestDispatcher(anyString())).thenReturn(requestDispatcher); + entryPoint.commence(request, response, null); + verify(response).addHeader("WWW-Authenticate", "Negotiate"); + verify(response).setStatus(HttpServletResponse.SC_UNAUTHORIZED); + } + + @Test + public void testEntryPointForwardOk() throws Exception { + String forwardUrl = "/login"; + SpnegoEntryPoint entryPoint = new SpnegoEntryPoint(forwardUrl); + HttpServletResponse response = mock(HttpServletResponse.class); + HttpServletRequest request = mock(HttpServletRequest.class); + RequestDispatcher requestDispatcher = mock(RequestDispatcher.class); + when(request.getRequestDispatcher(anyString())).thenReturn(requestDispatcher); + entryPoint.commence(request, response, null); + verify(response).addHeader("WWW-Authenticate", "Negotiate"); + verify(response).setStatus(HttpServletResponse.SC_UNAUTHORIZED); + verify(request).getRequestDispatcher(forwardUrl); + verify(requestDispatcher).forward(request, response); + } + + @Test(expected = IllegalArgumentException.class) + public void testEntryPointForwardAbsolute() throws Exception { + new SpnegoEntryPoint("http://test/login"); + } }