Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support authorization in service provider #174

Merged
merged 7 commits into from
Jan 11, 2015
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)
====================================
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