package com.melloware.valve; import java.io.IOException; import java.security.Principal; import javax.servlet.http.HttpServletResponse; import org.apache.catalina.Session; import org.apache.catalina.authenticator.Constants; import org.apache.catalina.connector.Request; import org.apache.catalina.deploy.LoginConfig; import org.apache.commons.lang3.StringUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.pjm.security.util.BasicAuthUtil; /** * An extension of the Apache FORM based authentication, this authentication * valve provides support for BASIC authorization on specific URLs defined in * the basicAuthUrl configuration parameter. If the requesting URL starts with a * matching value in this configuration parameter, it will be subject to BASIC * authorization instead of FORM based authorization. *

* Note that the configuration parameter accepts a comma separated list of * context strings. Also note that this valve does not provide SSO * functionality. Rather, it has generalized functionality that may be used by * applications simply requiring mixed authentication methods. */ public class MultiAuthValve extends org.apache.catalina.authenticator.FormAuthenticator { /** * Logger for this class */ private static final Logger LOG = LoggerFactory.getLogger(MultiAuthValve.class); /** * URLs starting with this string are assumed to require BASIC authorization * rather than the default configured in the web.xml, which is typically FORM * for our applications. */ private String basicAuthUrl; /** * Override authenticate to handle mixed BASIC auth and FORM based * authentication. */ @Override public boolean authenticate(Request request, HttpServletResponse response, LoginConfig config) throws IOException { // if already authenticated, done if (request.getUserPrincipal() != null) return true; boolean result; // if basic auth is required or desired, let's do that if (this.isBasicAuthRequired(request)) { result = this.checkBasicAuth(request, response, config); if (!result) this.writeBasicAuthChallenge(request, response, config); } else // otherwise whatever is in web.xml result = super.authenticate(request, response, config); return result; } /** * Determines if BASIC authorization is required or desired for the given * request. Basic authorization is required if the BasicAuthUrl is set and * the requested URL starts with the pattern specified. It is considered * desired if the request contains an authorization header. This is useful if * services want to hit URLs normally requiring FORM based authentication. *

* @param request the Http Request * @return */ protected boolean isBasicAuthRequired(Request request) { String basicUrl = StringUtils.upperCase(this.getBasicAuthUrl()); String requestUrl = StringUtils.upperCase(request.getRequestURI().substring(request.getContextPath().length())); if (basicUrl != null) { String[] urls = basicUrl.split(","); for (String url : urls) if (requestUrl.startsWith(url.trim())) return true; } String auth = request.getHeader("Authorization"); return StringUtils.isNotEmpty(auth); } /** * Checks the request for Basic Auth and if found, uses that to authenticate * the user. This let's us mix BASIC with FORM based authentication, which is * typically not possible. *

* @param request * @param response * @param config * @return * @throws IOException */ protected boolean checkBasicAuth(Request request, HttpServletResponse response, LoginConfig config) throws IOException { String[] creds = BasicAuthUtil.getBasicAuthCredentials(request); if (creds == null) return false; // authenticate return authenticateBasic(request, response, config, creds[0], creds[1]); } /** * Authenticates against the REALM using the BASIC creds provided. *

* Override to authenticate BASIC authorization using another mechanism. *

* @param request * @param response * @param userName * @param password * @return */ protected boolean authenticateBasic(Request request, HttpServletResponse response, LoginConfig config, String userName, String password) { Session session = request.getSessionInternal(true); Principal principal = context.getRealm().authenticate(userName, password); if (principal == null) return false; LOG.debug("Successfully authenticated Principal {}", principal.getName()); request.setUserPrincipal(principal); session.setNote(Constants.SESS_USERNAME_NOTE, userName); session.setNote(Constants.SESS_PASSWORD_NOTE, password); register(request, response, principal, Request.BASIC_AUTH, userName, password); return true; } /** * Sends the BASIC auth response challenge. *

* @param request * @param response * @throws IOException */ protected void writeBasicAuthChallenge(Request request, HttpServletResponse response, LoginConfig config) throws IOException { BasicAuthUtil.writeBasicAuthChallenge(response, config.getRealmName()); } /** * The basicAuthUrl value. *

* @return the basicAuthUrl */ public String getBasicAuthUrl() { return basicAuthUrl; } /** * Updates the basicAuthUrl value. *

* @param basicAuthUrl the basicAuthUrl to set */ public void setBasicAuthUrl(String basicAuthUrl) { this.basicAuthUrl = basicAuthUrl; } }