Skip to content

Commit

Permalink
Merge pull request #174 from csms/master
Browse files Browse the repository at this point in the history
Support authorization in service provider
  • Loading branch information
hazendaz committed Jan 11, 2015
2 parents 134fbd1 + 0c13ddf commit d6f5eef
Show file tree
Hide file tree
Showing 7 changed files with 585 additions and 5 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,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)
====================================
Expand Down
61 changes: 61 additions & 0 deletions Docs/spring/DelegatingSpringSecuritySingleSignOnFilter.md
Original file line number Diff line number Diff line change
@@ -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

<bean id="waffleNegotiateSecurityFilter"
class="waffle.spring.DelegatingNegotiateSecurityFilter"
scope="tenant">
<property name="allowGuestLogin" value="false" />
<property name="Provider" ref="waffleSecurityFilterProviderCollection" />
<property name="authenticationManager" ref="authenticationManager" />
<property name="authenticationSuccessHandler" ref="authenticationSuccessHandler" />
<property name="authenticationFailureHandler" ref="authenticationFailureHandler" />
<property name="accessDeniedHandler" ref="accessDeniedHandler" />
<property name="defaultGrantedAuthority">
<null />
</property>
</bean>

<security:authentication-manager alias="authenticationManager">
<security:authentication-provider
ref="authenticationProvider" />
</security:authentication-manager>

<bean id="authenticationProvider" class="org.springframework.security.config.authentication.AuthenticationManagerBeanDefinitionParser.NullAuthenticationProvider">

<bean id="authenticationFailureHandler"
class="org.springframework.security.web.authentication.SimpleUrlAuthenticationFailureHandler">
<property name="defaultFailureUrl" value="/errors/403" />
<property name="useForward" value="true" />
</bean>

<bean id="accessDeniedHandler"
class="org.springframework.security.web.access.AccessDeniedHandlerImpl">
<property name="errorPage" value="/errors/403" />
</bean>

```



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/.
2 changes: 1 addition & 1 deletion Docs/spring/SpringSecuritySingleSignOnFilter.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
---------------------------

Expand Down
Original file line number Diff line number Diff line change
@@ -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;

/**
*
*
* <p>
* Supports optional injection of spring security entities, allowing Waffle to act as an interface towards an identity
* provider(the AD).
* </p>
*
* <i>Below mentioned entities are verified to be set before invoked, inherited entities are not.</i>
*
* <ul>
* <li>
* The <code>AuthenticationManager</code> allows for the service provider to authorize the principal.</li>
*
* <li>
* The <code>authenticationSuccessHandler</code> allows for the service provider to further populate the
* {@link org.springframework.security.core.Authentication Authentication} object.</li>
*
* <li>
* The <code>AuthenticationFailureHandler</code> is called if the AuthenticationManager throws an
* {@link org.springframework.security.core.AuthenticationException AuthenticationException}.</li>
*
* <li>
* The <code>AccessDeniedHandler</code> is called if the AuthenticationManager throws an
* {@link org.springframework.security.access.AccessDeniedException AccessDeniedException}.</li>
* </ul>
* Example configuration:
*
* <pre>
* {@code
* <bean id="waffleNegotiateSecurityFilter"
* class="waffle.spring.DelegatingNegotiateSecurityFilter"
* scope="tenant">
* <property name="allowGuestLogin" value="false" />
* <property name="Provider" ref="waffleSecurityFilterProviderCollection" />
* <property name="authenticationManager" ref="authenticationManager" />
* <property name="authenticationSuccessHandler" ref="authenticationSuccessHandler" />
* <property name="authenticationFailureHandler" ref="authenticationFailureHandler" />
* <property name="accessDeniedHandler" ref="accessDeniedHandler" />
* <property name="defaultGrantedAuthority">
* <null />
* </property>
* </bean>
* </code>
* }
* </pre>
*/
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;
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -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());

Expand All @@ -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();
Expand All @@ -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) {
Expand Down
Loading

0 comments on commit d6f5eef

Please sign in to comment.