diff --git a/CHANGELOG.md b/CHANGELOG.md index fbf4335748..06fd82ee2a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,7 @@ Developer note -------------- * [#164](https://github.com/dblock/waffle/issues/164): Added unit test in waffle-tests using catch-exception test library to verify the condition caught is actually expected. +* [#188](https://github.com/dblock/waffle/issues/188): Added support for service provider to authorize the principal 1.7.1 (11/30/2014 - waffle-jna only) ==================================== diff --git a/Docs/spring/DelegatingSpringSecuritySingleSignOnFilter.md b/Docs/spring/DelegatingSpringSecuritySingleSignOnFilter.md new file mode 100644 index 0000000000..e5f2b15a8c --- /dev/null +++ b/Docs/spring/DelegatingSpringSecuritySingleSignOnFilter.md @@ -0,0 +1,61 @@ +Delegating Spring Security Single-SignOn Filter +==================================== + +The Waffle Delegating Spring-Security Filter extends the [Spring Security Single-SignOn Filter](https://github.com/dblock/waffle/blob/master/Docs/spring/SpringSecuritySingleSignOnFilter.md) by allowing the application using the filter to inject an additional authenticationmanager to provide authorization to a principal +that is authenticated in towards the active directory in the single sign-on process. + +Configuring Spring Security +--------------------------- +Configure spring security as is done for [Spring Security Single-SignOn Filter](https://github.com/dblock/waffle/blob/master/Docs/spring/SpringSecuritySingleSignOnFilter.md) + +Security Filter Options +----------------------- + +The `DelegatingNegotiateSecurityFilter` bean can be configured with the following options in addition to the ones provided by [NegotiateSecurityFilter] (https://github.com/dblock/waffle/blob/master/Docs/spring/SpringSecuritySingleSignOnFilter.md): + +* AuthenticationManager: Allows for the service provider to authorize the principal. +* AuthenticationSuccessHandler: Allows for the service provider to further populate the org.springframework.security.core.Authentication object. +* AuthenticationFailureHandler: Called if the AuthenticationManager throws an org.springframework.security.core.AuthenticationException. +* AccessDeniedHandler; Called if the AuthenticationManager throws an org.springframework.security.access.AccessDeniedException. +``` xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + +``` + + + +Waffle Spring-Security Demo +--------------------------- + +A demo application can be found in the Waffle distribution in the `Samples\waffle-spring-filter` directory. Copy the entire directory into Tomcat's or Jetty's webapps directory and navigate to http://localhost:8080/waffle-spring-filter/. diff --git a/Docs/spring/SpringSecuritySingleSignOnFilter.md b/Docs/spring/SpringSecuritySingleSignOnFilter.md index 86640ff08a..12fbbad72d 100644 --- a/Docs/spring/SpringSecuritySingleSignOnFilter.md +++ b/Docs/spring/SpringSecuritySingleSignOnFilter.md @@ -2,7 +2,7 @@ Spring Security Single-SignOn Filter ==================================== The Waffle Spring-Security Filter implements the Negotiate and Basic protocols with Kerberos and NTLM single sign-on support for web applications that utilize Spring-Security. This allows users to browse to a Windows intranet site without having to re-enter credentials for browsers that support Kerberos or NTLM and to fall back to Basic authentication for those that do not. For more information about Spring-Security see [http://static.springsource.org/spring-security/site/](http://static.springsource.org/spring-security/site/). - +NOTE: Also available with delegation to support authentication for the service provider [here] (https://github.com/dblock/waffle/blob/master/Docs/spring/DelegatingSpringSecuritySingleSignOnFilter.md) Configuring Spring Security --------------------------- diff --git a/Source/JNA/waffle-spring-security3/src/main/java/waffle/spring/DelegatingNegotiateSecurityFilter.java b/Source/JNA/waffle-spring-security3/src/main/java/waffle/spring/DelegatingNegotiateSecurityFilter.java new file mode 100644 index 0000000000..a405e40f81 --- /dev/null +++ b/Source/JNA/waffle-spring-security3/src/main/java/waffle/spring/DelegatingNegotiateSecurityFilter.java @@ -0,0 +1,245 @@ +/** + * Waffle (https://github.com/dblock/waffle) + * + * Copyright (c) 2010 - 2014 Application Security, Inc. + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Application Security, Inc. + */ +/** + * + */ +package waffle.spring; + +import java.io.IOException; + +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.security.access.AccessDeniedException; +import org.springframework.security.authentication.AuthenticationManager; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.AuthenticationException; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.security.web.access.AccessDeniedHandler; +import org.springframework.security.web.authentication.AuthenticationFailureHandler; +import org.springframework.security.web.authentication.AuthenticationSuccessHandler; + +/** + * + * + *

+ * Supports optional injection of spring security entities, allowing Waffle to act as an interface towards an identity + * provider(the AD). + *

+ * + * Below mentioned entities are verified to be set before invoked, inherited entities are not. + * + * + * Example configuration: + * + *
+ * {@code
+ * 
+ * 		
+ * 		
+ * 		
+ * 		
+ * 		
+ * 		
+ * 		
+ * 			
+ * 		
+ * 	
+ * 
+ * }
+ * 
+ */ +public class DelegatingNegotiateSecurityFilter extends NegotiateSecurityFilter { + private static final Logger LOGGER = LoggerFactory.getLogger(NegotiateSecurityFilter.class); + + private AuthenticationManager authenticationManager; + private AuthenticationSuccessHandler authenticationSuccessHandler; + private AuthenticationFailureHandler authenticationFailureHandler; + private AccessDeniedHandler accessDeniedHandler; + + /** + * @return the accessDeniedHandler + */ + public AccessDeniedHandler getAccessDeniedHandler() { + return accessDeniedHandler; + } + + /** + * @param accessDeniedHandler + * the accessDeniedHandler to set + */ + public void setAccessDeniedHandler(final AccessDeniedHandler accessDeniedHandler) { + this.accessDeniedHandler = accessDeniedHandler; + } + + /** + * @return the authenticationFailureHandler + */ + public AuthenticationFailureHandler getAuthenticationFailureHandler() { + return authenticationFailureHandler; + } + + /** + * @param authenticationFailureHandler + * the authenticationFailureHandler to set + */ + public void setAuthenticationFailureHandler(final AuthenticationFailureHandler authenticationFailureHandler) { + this.authenticationFailureHandler = authenticationFailureHandler; + } + + public DelegatingNegotiateSecurityFilter() { + super(); + LOGGER.debug("[waffle.spring.NegotiateSecurityFilter] loaded"); + } + + @Override + protected boolean setAuthentication(final HttpServletRequest request, final HttpServletResponse response, + final Authentication authentication) { + try { + if (authenticationManager != null) { + logger.debug("Delegating to custom authenticationmanager"); + final Authentication customAuthentication = authenticationManager.authenticate(authentication); + SecurityContextHolder.getContext().setAuthentication(customAuthentication); + } + if (authenticationSuccessHandler != null) { + try { + authenticationSuccessHandler.onAuthenticationSuccess(request, response, authentication); + } catch (final IOException e) { + logger.warn("Error calling authenticationSuccessHandler: " + e.getMessage()); + return false; + } catch (final ServletException e) { + logger.warn("Error calling authenticationSuccessHandler: " + e.getMessage()); + return false; + } + } + } catch (final AuthenticationException e) { + + logger.warn("Error authenticating user in custom authenticationmanager: " + e.getMessage()); + sendAuthenticationFailed(request, response, e); + return false; + } catch (final AccessDeniedException e) { + logger.warn("Error authorizing user in custom authenticationmanager: " + e.getMessage()); + sendAccessDenied(request, response, e); + return false; + } + return true; + } + + @Override + public void afterPropertiesSet() throws ServletException { + super.afterPropertiesSet(); + + if (this.getProvider() == null) { + throw new ServletException("Missing NegotiateSecurityFilter.Provider"); + } + } + + /** + * Forward to authenticationFailureHandler. + * + * @param response + * HTTP Response + * @param close + * Close connection. + */ + private void sendAuthenticationFailed(final HttpServletRequest request, final HttpServletResponse response, + final AuthenticationException ae) { + if (authenticationFailureHandler != null) { + try { + authenticationFailureHandler.onAuthenticationFailure(request, response, ae); + return; + } catch (final IOException e) { + LOGGER.warn("IOException invoking authenticationFailureHandler: " + e.getMessage()); + } catch (final ServletException e) { + LOGGER.warn("ServletException invoking authenticationFailureHandler: " + e.getMessage()); + } + } + super.sendUnauthorized(response, true); + } + + /** + * Forward to accessDeniedHandler. + * + * @param response + * HTTP Response + * @param close + * Close connection. + */ + private void sendAccessDenied(final HttpServletRequest request, final HttpServletResponse response, + final AccessDeniedException ae) { + if (accessDeniedHandler != null) { + try { + accessDeniedHandler.handle(request, response, ae); + return; + } catch (final IOException e) { + LOGGER.warn("IOException invoking accessDeniedHandler: " + e.getMessage()); + } catch (final ServletException e) { + LOGGER.warn("ServletException invoking accessDeniedHandler: " + e.getMessage()); + } + } + // fallback + sendUnauthorized(response, true); + } + + /** + * @return the authenticationSuccessHandler + */ + public AuthenticationSuccessHandler getAuthenticationSuccessHandler() { + return authenticationSuccessHandler; + } + + /** + * @param authenticationSuccessHandler + * the authenticationSuccessHandler to set + */ + public void setAuthenticationSuccessHandler(final AuthenticationSuccessHandler authenticationSuccessHandler) { + this.authenticationSuccessHandler = authenticationSuccessHandler; + } + + /** + * @return the authenticationManager + */ + public AuthenticationManager getAuthenticationManager() { + return authenticationManager; + } + + /** + * @param authenticationManager + * the authenticationManager to set + */ + public void setAuthenticationManager(final AuthenticationManager authenticationManager) { + this.authenticationManager = authenticationManager; + } + +} diff --git a/Source/JNA/waffle-spring-security3/src/main/java/waffle/spring/NegotiateSecurityFilter.java b/Source/JNA/waffle-spring-security3/src/main/java/waffle/spring/NegotiateSecurityFilter.java index 4374d62417..31a85f8fb1 100644 --- a/Source/JNA/waffle-spring-security3/src/main/java/waffle/spring/NegotiateSecurityFilter.java +++ b/Source/JNA/waffle-spring-security3/src/main/java/waffle/spring/NegotiateSecurityFilter.java @@ -106,7 +106,9 @@ public void doFilter(final ServletRequest req, final ServletResponse res, final final Authentication authentication = new WindowsAuthenticationToken(principal, this.grantedAuthorityFactory, this.defaultGrantedAuthority); - SecurityContextHolder.getContext().setAuthentication(authentication); + if (!setAuthentication(request, response, authentication)) { + return; + } LOGGER.info("successfully logged in user: {}", windowsIdentity.getFqn()); @@ -118,6 +120,18 @@ public void doFilter(final ServletRequest req, final ServletResponse res, final chain.doFilter(request, response); } + /* + * Invoked when authentication towards ad was succesful to populate securitycontext Override to add service provider + * authorization checks. + * + * @return if security context was set. + */ + protected boolean setAuthentication(final HttpServletRequest request, final HttpServletResponse response, + final Authentication authentication) { + SecurityContextHolder.getContext().setAuthentication(authentication); + return true; + } + @Override public void afterPropertiesSet() throws ServletException { super.afterPropertiesSet(); @@ -135,7 +149,7 @@ public void afterPropertiesSet() throws ServletException { * @param close * Close connection. */ - private void sendUnauthorized(final HttpServletResponse response, final boolean close) { + protected void sendUnauthorized(final HttpServletResponse response, final boolean close) { try { this.provider.sendUnauthorized(response); if (close) { diff --git a/Source/JNA/waffle-spring-security4/src/main/java/waffle/spring/DelegatingNegotiateSecurityFilter.java b/Source/JNA/waffle-spring-security4/src/main/java/waffle/spring/DelegatingNegotiateSecurityFilter.java new file mode 100644 index 0000000000..a405e40f81 --- /dev/null +++ b/Source/JNA/waffle-spring-security4/src/main/java/waffle/spring/DelegatingNegotiateSecurityFilter.java @@ -0,0 +1,245 @@ +/** + * Waffle (https://github.com/dblock/waffle) + * + * Copyright (c) 2010 - 2014 Application Security, Inc. + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Application Security, Inc. + */ +/** + * + */ +package waffle.spring; + +import java.io.IOException; + +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.security.access.AccessDeniedException; +import org.springframework.security.authentication.AuthenticationManager; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.AuthenticationException; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.security.web.access.AccessDeniedHandler; +import org.springframework.security.web.authentication.AuthenticationFailureHandler; +import org.springframework.security.web.authentication.AuthenticationSuccessHandler; + +/** + * + * + *

+ * Supports optional injection of spring security entities, allowing Waffle to act as an interface towards an identity + * provider(the AD). + *

+ * + * Below mentioned entities are verified to be set before invoked, inherited entities are not. + * + * + * Example configuration: + * + *
+ * {@code
+ * 
+ * 		
+ * 		
+ * 		
+ * 		
+ * 		
+ * 		
+ * 		
+ * 			
+ * 		
+ * 	
+ * 
+ * }
+ * 
+ */ +public class DelegatingNegotiateSecurityFilter extends NegotiateSecurityFilter { + private static final Logger LOGGER = LoggerFactory.getLogger(NegotiateSecurityFilter.class); + + private AuthenticationManager authenticationManager; + private AuthenticationSuccessHandler authenticationSuccessHandler; + private AuthenticationFailureHandler authenticationFailureHandler; + private AccessDeniedHandler accessDeniedHandler; + + /** + * @return the accessDeniedHandler + */ + public AccessDeniedHandler getAccessDeniedHandler() { + return accessDeniedHandler; + } + + /** + * @param accessDeniedHandler + * the accessDeniedHandler to set + */ + public void setAccessDeniedHandler(final AccessDeniedHandler accessDeniedHandler) { + this.accessDeniedHandler = accessDeniedHandler; + } + + /** + * @return the authenticationFailureHandler + */ + public AuthenticationFailureHandler getAuthenticationFailureHandler() { + return authenticationFailureHandler; + } + + /** + * @param authenticationFailureHandler + * the authenticationFailureHandler to set + */ + public void setAuthenticationFailureHandler(final AuthenticationFailureHandler authenticationFailureHandler) { + this.authenticationFailureHandler = authenticationFailureHandler; + } + + public DelegatingNegotiateSecurityFilter() { + super(); + LOGGER.debug("[waffle.spring.NegotiateSecurityFilter] loaded"); + } + + @Override + protected boolean setAuthentication(final HttpServletRequest request, final HttpServletResponse response, + final Authentication authentication) { + try { + if (authenticationManager != null) { + logger.debug("Delegating to custom authenticationmanager"); + final Authentication customAuthentication = authenticationManager.authenticate(authentication); + SecurityContextHolder.getContext().setAuthentication(customAuthentication); + } + if (authenticationSuccessHandler != null) { + try { + authenticationSuccessHandler.onAuthenticationSuccess(request, response, authentication); + } catch (final IOException e) { + logger.warn("Error calling authenticationSuccessHandler: " + e.getMessage()); + return false; + } catch (final ServletException e) { + logger.warn("Error calling authenticationSuccessHandler: " + e.getMessage()); + return false; + } + } + } catch (final AuthenticationException e) { + + logger.warn("Error authenticating user in custom authenticationmanager: " + e.getMessage()); + sendAuthenticationFailed(request, response, e); + return false; + } catch (final AccessDeniedException e) { + logger.warn("Error authorizing user in custom authenticationmanager: " + e.getMessage()); + sendAccessDenied(request, response, e); + return false; + } + return true; + } + + @Override + public void afterPropertiesSet() throws ServletException { + super.afterPropertiesSet(); + + if (this.getProvider() == null) { + throw new ServletException("Missing NegotiateSecurityFilter.Provider"); + } + } + + /** + * Forward to authenticationFailureHandler. + * + * @param response + * HTTP Response + * @param close + * Close connection. + */ + private void sendAuthenticationFailed(final HttpServletRequest request, final HttpServletResponse response, + final AuthenticationException ae) { + if (authenticationFailureHandler != null) { + try { + authenticationFailureHandler.onAuthenticationFailure(request, response, ae); + return; + } catch (final IOException e) { + LOGGER.warn("IOException invoking authenticationFailureHandler: " + e.getMessage()); + } catch (final ServletException e) { + LOGGER.warn("ServletException invoking authenticationFailureHandler: " + e.getMessage()); + } + } + super.sendUnauthorized(response, true); + } + + /** + * Forward to accessDeniedHandler. + * + * @param response + * HTTP Response + * @param close + * Close connection. + */ + private void sendAccessDenied(final HttpServletRequest request, final HttpServletResponse response, + final AccessDeniedException ae) { + if (accessDeniedHandler != null) { + try { + accessDeniedHandler.handle(request, response, ae); + return; + } catch (final IOException e) { + LOGGER.warn("IOException invoking accessDeniedHandler: " + e.getMessage()); + } catch (final ServletException e) { + LOGGER.warn("ServletException invoking accessDeniedHandler: " + e.getMessage()); + } + } + // fallback + sendUnauthorized(response, true); + } + + /** + * @return the authenticationSuccessHandler + */ + public AuthenticationSuccessHandler getAuthenticationSuccessHandler() { + return authenticationSuccessHandler; + } + + /** + * @param authenticationSuccessHandler + * the authenticationSuccessHandler to set + */ + public void setAuthenticationSuccessHandler(final AuthenticationSuccessHandler authenticationSuccessHandler) { + this.authenticationSuccessHandler = authenticationSuccessHandler; + } + + /** + * @return the authenticationManager + */ + public AuthenticationManager getAuthenticationManager() { + return authenticationManager; + } + + /** + * @param authenticationManager + * the authenticationManager to set + */ + public void setAuthenticationManager(final AuthenticationManager authenticationManager) { + this.authenticationManager = authenticationManager; + } + +} diff --git a/Source/JNA/waffle-spring-security4/src/main/java/waffle/spring/NegotiateSecurityFilter.java b/Source/JNA/waffle-spring-security4/src/main/java/waffle/spring/NegotiateSecurityFilter.java index 4374d62417..31a85f8fb1 100644 --- a/Source/JNA/waffle-spring-security4/src/main/java/waffle/spring/NegotiateSecurityFilter.java +++ b/Source/JNA/waffle-spring-security4/src/main/java/waffle/spring/NegotiateSecurityFilter.java @@ -106,7 +106,9 @@ public void doFilter(final ServletRequest req, final ServletResponse res, final final Authentication authentication = new WindowsAuthenticationToken(principal, this.grantedAuthorityFactory, this.defaultGrantedAuthority); - SecurityContextHolder.getContext().setAuthentication(authentication); + if (!setAuthentication(request, response, authentication)) { + return; + } LOGGER.info("successfully logged in user: {}", windowsIdentity.getFqn()); @@ -118,6 +120,18 @@ public void doFilter(final ServletRequest req, final ServletResponse res, final chain.doFilter(request, response); } + /* + * Invoked when authentication towards ad was succesful to populate securitycontext Override to add service provider + * authorization checks. + * + * @return if security context was set. + */ + protected boolean setAuthentication(final HttpServletRequest request, final HttpServletResponse response, + final Authentication authentication) { + SecurityContextHolder.getContext().setAuthentication(authentication); + return true; + } + @Override public void afterPropertiesSet() throws ServletException { super.afterPropertiesSet(); @@ -135,7 +149,7 @@ public void afterPropertiesSet() throws ServletException { * @param close * Close connection. */ - private void sendUnauthorized(final HttpServletResponse response, final boolean close) { + protected void sendUnauthorized(final HttpServletResponse response, final boolean close) { try { this.provider.sendUnauthorized(response); if (close) {