From f8d514a5df1f828b0b824d8f1556eb3f43713164 Mon Sep 17 00:00:00 2001 From: aschaefer Date: Wed, 8 Jan 2014 10:19:07 +0100 Subject: [PATCH] SES-136 Option to provide content with WWW-Authentication:Negotiate Response of SpnegoEntryPoint by forward --- .../kerberos/web/SpnegoEntryPoint.java | 106 ++++++++++++++---- .../kerberos/web/SpnegoEntryPointTest.java | 46 ++++++-- 2 files changed, 117 insertions(+), 35 deletions(-) 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 2e3a2d5..502d3ee 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 @@ -1,12 +1,9 @@ /* * Copyright 2009 the original author or authors. - * * 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 - * + * 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. @@ -16,41 +13,102 @@ 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 scenarion 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 * @version $Id$ * @see SpnegoAuthenticationProcessingFilter */ -public class SpnegoEntryPoint implements AuthenticationEntryPoint { +public class SpnegoEntryPoint implements AuthenticationEntryPoint +{ - private static final Log LOG = LogFactory.getLog(SpnegoEntryPoint.class); + private static final Log LOG = LogFactory.getLog(SpnegoEntryPoint.class); - /* (non-Javadoc) - * @see org.springframework.security.web.AuthenticationEntryPoint#commence(javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse, org.springframework.security.core.AuthenticationException) - */ - 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 String forwardUrl; - } + protected boolean forward = false; + + /** + * 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 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 either be relative to the web-app context path + * (include a leading {@code /}) or an absolute URL. + */ + public SpnegoEntryPoint(String forwardUrl) + { + this(); + if (StringUtils.hasText(forwardUrl)) + { + Assert.isTrue(UrlUtils.isValidRedirectUrl(forwardUrl), "forwardUrl specified must be a valid forward URL"); + Assert.isTrue(!UrlUtils.isAbsoluteUrl(forwardUrl), "loginFormURL specified must not be absolute"); + this.forwardUrl = forwardUrl; + this.forward = true; + } + } + + /* + * (non-Javadoc) + * @see org.springframework.security.web.AuthenticationEntryPoint#commence(javax.servlet.http.HttpServletRequest, + * javax.servlet.http.HttpServletResponse, org.springframework.security.core.AuthenticationException) + */ + 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 14fe9cb..e92fee4 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,31 +15,55 @@ */ 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 Andre Schaefer, Namics AG * @since 1.0 * @version $Id$ */ public class SpnegoEntryPointTest { - private SpnegoEntryPoint entryPoint = new SpnegoEntryPoint(); + @Test + public void testEntryPointOk() 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 testEntryPointOk() throws Exception { - HttpServletResponse response = mock(HttpServletResponse.class); + @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); - entryPoint.commence(null, response, null); - - verify(response).addHeader("WWW-Authenticate", "Negotiate"); - verify(response).setStatus(HttpServletResponse.SC_UNAUTHORIZED); - } + } + @Test(expected = IllegalArgumentException.class) + public void testEntryPointForwardAbsolute() throws Exception { + new SpnegoEntryPoint("http://test/login"); + } }