DynamicAuthenticationFilter.java

/**
 * Waffle (https://github.com/dblock/waffle)
 *
 * Copyright (c) 2010 - 2015 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.shiro.dynamic;

import waffle.shiro.negotiate.NegotiateAuthenticationFilter;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.subject.Subject;
import org.apache.shiro.web.filter.authc.FormAuthenticationFilter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;

/**
 * When combined with the {@link waffle.shiro.negotiate.NegotiateAuthenticationStrategy}, this filter can be used to
 * allow a client to choose which authentication filter is used at runtime. This filter assumes the shiro.ini is
 * configured with both the {@link waffle.shiro.negotiate.NegotiateAuthenticationRealm} and some User/Password Realm
 * like: {@link waffle.shiro.GroupMappingWaffleRealm}.
 * 
 * Requires use of {@link waffle.shiro.negotiate.NegotiateAuthenticationStrategy} when more than one realm is configured
 * in shiro.ini (which should be the case for multiple authentication type options).
 * 
 * To use {@link waffle.shiro.negotiate.NegotiateAuthenticationRealm}, the client must pass the parameter
 * {@link #PARAM_NAME_AUTHTYPE} with a value of {@link #PARAM_VAL_AUTHTYPE_NEGOTIATE}.
 * 
 * Example shiro.ini snippet below:
 * 
 * <pre>
 *  # =======================
 *  # Shiro INI configuration
 *  # =======================
 * 
 *  [main]
 * 
 *  # Setup custom AuthenticationRealm
 *  waffleRealmSSO = waffle.shiro.negotiate.NegotiateAuthenticationRealm
 *  waffleUserPass = waffle.shiro.GroupMappingWaffleRealm
 *  securityManager.realms = $waffleRealmSSO, $waffleUserPass
 * 
 * 
 *  # Use the configured native session manager:
 *  sessionManager = org.apache.shiro.web.session.mgt.DefaultWebSessionManager
 *  securityManager.sessionManager = $sessionManager
 * 
 *  # the following call is only necessary in a web-configured ShiroFilter (otherwise
 *  # a native session manager is already enabled):
 *  securityManager.sessionMode = native
 * 
 * 
 *  # cookie for single sign on
 *  cookie = org.apache.shiro.web.servlet.SimpleCookie
 *  cookie.name = SSOcookie
 *  cookie.path = /
 *  securityManager.sessionManager.sessionIdCookie = $cookie
 * 
 * 
 *  authcStrategy = waffle.shiro.negotiate.NegotiateAuthenticationStrategy
 *  securityManager.authenticator.authenticationStrategy = $authcStrategy
 * 
 *  # Waffle filter
 *  waffleFilter = waffle.shiro.dynamic.DynamicAuthenticationFilter
 * 
 *  #Configure filter chains and filter parameters
 *  authc.loginUrl = /login.jsp
 *  waffleFilter.loginUrl = /login.jsp
 *  logout.redirectUrl = login.jsp
 * 
 *  ...
 * 
 *  [urls]
 *  # The 'urls' section is used for url-based security
 *  /logout = logout
 *  /* = waffleFilter
 * 
 * </pre>
 * 
 * @author Dan Rollo Date: 2/21/13 Time: 9:08 PM
 */
public class DynamicAuthenticationFilter extends FormAuthenticationFilter {

    /** The Constant LOGGER. */
    private static final Logger LOGGER                       = LoggerFactory
                                                                     .getLogger(DynamicAuthenticationFilter.class);

    /** The Constant PARAM_NAME_AUTHTYPE. */
    public static final String  PARAM_NAME_AUTHTYPE          = "authType";
    
    /** The Constant PARAM_VAL_AUTHTYPE_NEGOTIATE. */
    public static final String  PARAM_VAL_AUTHTYPE_NEGOTIATE = "j_negotiate";

    /**
     * Wrapper to make protected methods in different package callable from here.
     */
    private static final class WrapNegotiateAuthenticationFilter extends NegotiateAuthenticationFilter {

        /** The parent. */
        private final DynamicAuthenticationFilter parent;

        /**
         * Instantiates a new wrap negotiate authentication filter.
         *
         * @param newParent
         *            the new parent
         */
        WrapNegotiateAuthenticationFilter(final DynamicAuthenticationFilter newParent) {
            this.parent = newParent;
        }

        /* (non-Javadoc)
         * @see waffle.shiro.negotiate.NegotiateAuthenticationFilter#onAccessDenied(javax.servlet.ServletRequest, javax.servlet.ServletResponse)
         */
        @Override
        public boolean onAccessDenied(final ServletRequest request, final ServletResponse response) throws Exception {
            return super.onAccessDenied(request, response);
        }

        /* (non-Javadoc)
         * @see waffle.shiro.negotiate.NegotiateAuthenticationFilter#onLoginSuccess(org.apache.shiro.authc.AuthenticationToken, org.apache.shiro.subject.Subject, javax.servlet.ServletRequest, javax.servlet.ServletResponse)
         */
        @Override
        protected boolean onLoginSuccess(final AuthenticationToken token, final Subject subject,
                final ServletRequest request, final ServletResponse response) throws Exception {
            return this.parent.onLoginSuccess(token, subject, request, response);
        }
    }

    /** The filter negotiate. */
    private final WrapNegotiateAuthenticationFilter filterNegotiate = new WrapNegotiateAuthenticationFilter(this);

    /**
     * Wrapper to make protected methods in different package callable from here.
     */
    private static final class WrapFormAuthenticationFilter extends FormAuthenticationFilter {

        /** The parent. */
        private final DynamicAuthenticationFilter parent;

        /**
         * Instantiates a new wrap form authentication filter.
         *
         * @param newParent
         *            the new parent
         */
        WrapFormAuthenticationFilter(final DynamicAuthenticationFilter newParent) {
            this.parent = newParent;
        }

        /* (non-Javadoc)
         * @see org.apache.shiro.web.filter.authc.FormAuthenticationFilter#onAccessDenied(javax.servlet.ServletRequest, javax.servlet.ServletResponse)
         */
        @Override
        public boolean onAccessDenied(final ServletRequest request, final ServletResponse response) throws Exception {
            return super.onAccessDenied(request, response);
        }

        /* (non-Javadoc)
         * @see org.apache.shiro.web.filter.authc.FormAuthenticationFilter#onLoginSuccess(org.apache.shiro.authc.AuthenticationToken, org.apache.shiro.subject.Subject, javax.servlet.ServletRequest, javax.servlet.ServletResponse)
         */
        @Override
        protected boolean onLoginSuccess(final AuthenticationToken token, final Subject subject,
                final ServletRequest request, final ServletResponse response) throws Exception {
            return this.parent.onLoginSuccess(token, subject, request, response);
        }
    }

    /** The filter form authc. */
    private final WrapFormAuthenticationFilter filterFormAuthc = new WrapFormAuthenticationFilter(this);

    /**
     * Call
     * {@link org.apache.shiro.web.filter.AccessControlFilter#onAccessDenied(javax.servlet.ServletRequest, javax.servlet.ServletResponse)}
     * for the user selected authentication type, which performs login logic.
     * 
     * {@inheritDoc}
     */
    @Override
    protected boolean executeLogin(final ServletRequest request, final ServletResponse response) throws Exception {
        if (this.isAuthTypeNegotiate(request)) {
            DynamicAuthenticationFilter.LOGGER.debug("using filterNegotiate");
            return this.filterNegotiate.onAccessDenied(request, response);
        }
        DynamicAuthenticationFilter.LOGGER.debug("using filterFormAuthc");
        return this.filterFormAuthc.onAccessDenied(request, response);
    }

    /**
     * Checks if is auth type negotiate.
     *
     * @param request
     *            the request
     * @return true, if is auth type negotiate
     */
    boolean isAuthTypeNegotiate(final ServletRequest request) {
        final String authType = request.getParameter(DynamicAuthenticationFilter.PARAM_NAME_AUTHTYPE);
        return authType != null && DynamicAuthenticationFilter.PARAM_VAL_AUTHTYPE_NEGOTIATE.equalsIgnoreCase(authType);
    }

}