|
Size: 13933
Comment: converted to 1.6 markup
|
← Revision 4 as of 2010-08-28 18:08:36 ⇥
Size: 13958
Comment: Added CategoryFAQ link
|
| Deletions are marked like this. | Additions are marked like this. |
| Line 338: | Line 338: |
---- [[CategoryFAQ]] |
The following code was tested with Tomcat 5.5.17 and 5.5.20. See the page SSLWithFORMFallback for instructions.
You will need the Tomcat JARs from a tomcat installation to compile this class.
package at.telekom.tomcat.security;
import java.io.IOException;
import java.security.Principal;
import java.security.cert.X509Certificate;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Locale;
import javax.servlet.ServletException;
import org.apache.catalina.Globals;
import org.apache.catalina.Realm;
import org.apache.catalina.Session;
import org.apache.catalina.authenticator.Constants;
import org.apache.catalina.authenticator.FormAuthenticator;
import org.apache.catalina.connector.Request;
import org.apache.catalina.connector.Response;
import org.apache.catalina.deploy.LoginConfig;
import org.apache.catalina.deploy.SecurityConstraint;
import org.apache.catalina.util.DateTool;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.coyote.ActionCode;
/**
* <p>
* Authenticator Valve for Tomcat
* </p>
* <p>
* This Valve implements a special login method - SSL Certificate based, with FORM fallback.
* It is intended to be used on an SSL protected channel. The Authenticator first tries to authenticate
* based on a client certificate included in the SSL Request. If this is not possible, or the request
* does not contain a client certificate, the Authenticator attempts to authenticate using the
* FORM login method.
* </p>
* <p>
* To use this authenticator, include a <login-config> in your web.xml, but do not include a
* <auth-method> tag. Do include the <form-login-config> tag to configure the pages used if
* FORM login occurs.
* </p>
* <p>
* Instead of the <auth-method> tag, manually configure a Valve in your tomcat configuration
* (server.xml or context.xml). Configure the Valve on the corresponding context, and use this
* class as the Valve type.
* </p>
* @author Richard Unger
*/
public class SSLWithFormFallbackAuthenticator extends FormAuthenticator {
private static Log log = LogFactory.getLog(SSLWithFormFallbackAuthenticator.class);
/**
* "Expires" header always set to Date(1), so generate once only
*/
private static final String DATE_ONE =
(new SimpleDateFormat(DateTool.HTTP_RESPONSE_DATE_HEADER,
Locale.US)).format(new Date(1));
/**
*
*/
public SSLWithFormFallbackAuthenticator() {
super();
}
/**
* @see org.apache.catalina.authenticator.AuthenticatorBase#invoke(org.apache.catalina.connector.Request, org.apache.catalina.connector.Response)
*/
@Override
public void invoke(Request request, Response response)
throws IOException, ServletException {
if (log.isDebugEnabled())
log.debug("Security checking request " +
request.getMethod() + " " + request.getRequestURI());
LoginConfig config = this.context.getLoginConfig();
// Have we got a cached authenticated Principal to record?
if (cache) {
Principal principal = request.getUserPrincipal();
if (principal == null) {
Session session = request.getSessionInternal(false);
if (session != null) {
principal = session.getPrincipal();
if (principal != null) {
if (log.isDebugEnabled())
log.debug("We have cached auth type " +
session.getAuthType() +
" for principal " +
session.getPrincipal());
request.setAuthType(session.getAuthType());
request.setUserPrincipal(principal);
}
}
}
}
// Special handling for form-based logins to deal with the case
// where the login form (and therefore the "j_security_check" URI
// to which it submits) might be outside the secured area
String contextPath = this.context.getPath();
String requestURI = request.getDecodedRequestURI();
if (requestURI.startsWith(contextPath) &&
requestURI.endsWith(Constants.FORM_ACTION)) {
if (!authenticate(request, response, config)) { // RU - this is a call to FORM based auth!
if (log.isDebugEnabled())
log.debug(" Failed authenticate() test ??" + requestURI );
return;
}
}
Realm realm = this.context.getRealm();
// Is this request URI subject to a security constraint?
SecurityConstraint [] constraints
= realm.findSecurityConstraints(request, this.context);
if ((constraints == null) /* &&
(!Constants.FORM_METHOD.equals(config.getAuthMethod())) */ ) {
if (log.isDebugEnabled())
log.debug(" Not subject to any constraint");
getNext().invoke(request, response);
return;
}
// Make sure that constrained resources are not cached by web proxies
// or browsers as caching can provide a security hole
if (disableProxyCaching &&
// FIX-ME: Disabled for Mozilla FORM support over SSL
// (improper caching issue)
//!request.isSecure() &&
!"POST".equalsIgnoreCase(request.getMethod())) {
/* if (securePagesWithPragma) {
// FIX-ME: These cause problems with downloading office docs
// from IE under SSL and may not be needed for newer Mozilla
// clients.
response.setHeader("Pragma", "No-cache");
response.setHeader("Cache-Control", "no-cache");
} else { */
response.setHeader("Cache-Control", "private");
// }
response.setHeader("Expires", DATE_ONE);
}
int i;
// Enforce any user data constraint for this security constraint
if (log.isDebugEnabled()) {
log.debug(" Calling hasUserDataPermission()");
}
if (!realm.hasUserDataPermission(request, response,
constraints)) {
if (log.isDebugEnabled()) {
log.debug(" Failed hasUserDataPermission() test");
}
/*
* ASSERT: Authenticator already set the appropriate
* HTTP status code, so we do not have to do anything special
*/
return;
}
// Since authenticate modifies the response on failure,
// we have to check for allow-from-all first.
boolean authRequired = true;
for(i=0; i < constraints.length && authRequired; i++) {
if(!constraints[i].getAuthConstraint()) {
authRequired = false;
} else if(!constraints[i].getAllRoles()) {
String [] roles = constraints[i].findAuthRoles();
if(roles == null || roles.length == 0) {
authRequired = false;
}
}
}
if(authRequired) {
if (log.isDebugEnabled()) {
log.debug(" Calling authenticate()");
}
if (!authenticateSSL(request, response, config)){ // RU - this is the SSL auth
if (!authenticate(request, response, config)) { // RU - and this is the FORM fallback!
if (log.isDebugEnabled()) {
log.debug(" Failed authenticate() test for both SSL and form");
}
/*
* ASSERT: Authenticator already set the appropriate
* HTTP status code, so we do not have to do anything
* special
*/
return;
}
}
}
if (log.isDebugEnabled()) {
log.debug(" Calling accessControl()");
}
if (!realm.hasResourcePermission(request, response,
constraints,
this.context)) {
if (log.isDebugEnabled()) {
log.debug(" Failed accessControl() test");
}
/*
* ASSERT: AccessControl method has already set the
* appropriate HTTP status code, so we do not have to do
* anything special
*/
return;
}
// Any and all specified constraints have been satisfied
if (log.isDebugEnabled()) {
log.debug(" Successfully passed all security constraints");
}
getNext().invoke(request, response);
}
/**
* Authenticate the user by checking for the existence of a certificate
* chain, and optionally asking a trust manager to validate that we trust
* this user.
*
*
* This method was modified from the original SSLAuthenticator not to throw exceptions
* or modify the response, since there will be FORM authentication fallback.
*
*
* @param request Request we are processing
* @param response Response we are creating
* @param config Login configuration describing how authentication
* should be performed
*
* @exception IOException if an input/output error occurs
*/
public boolean authenticateSSL(Request request,
Response response,
LoginConfig config)
throws IOException {
// Have we already authenticated someone?
Principal principal = request.getUserPrincipal();
//String ssoId = (String) request.getNote(Constants.REQ_SSOID_NOTE);
if (principal != null) {
if (log.isDebugEnabled())
log.debug("Already authenticated '" + principal.getName() + "'");
// Associate the session with any existing SSO session in order
// to get coordinated session invalidation at logout
String ssoId = (String) request.getNote(Constants.REQ_SSOID_NOTE);
if (ssoId != null)
associate(ssoId, request.getSessionInternal(true));
return (true);
}
// NOTE: We don't try to reauthenticate using any existing SSO session,
// because that will only work if the original authentication was
// BASIC or FORM, which are less secure than the CLIENT-CERT auth-type
// specified for this webapp
//
// Uncomment below to allow previous FORM or BASIC authentications
// to authenticate users for this webapp
// TO-DO make this a configurable attribute (in SingleSignOn??)
/*
// Is there an SSO session against which we can try to reauthenticate?
if (ssoId != null) {
if (log.isDebugEnabled())
log.debug("SSO Id " + ssoId + " set; attempting " +
"reauthentication");
// Try to reauthenticate using data cached by SSO. If this fails,
// either the original SSO logon was of DIGEST or SSL (which
// we can't reauthenticate ourselves because there is no
// cached username and password), or the realm denied
// the user's reauthentication for some reason.
// In either case we have to prompt the user for a logon
if (reauthenticateFromSSO(ssoId, request))
return true;
}
*/
// Retrieve the certificate chain for this client
if (log.isDebugEnabled())
log.debug(" Looking up certificates");
X509Certificate certs[] = (X509Certificate[])
request.getAttribute(Globals.CERTIFICATES_ATTR);
if ((certs == null) || (certs.length < 1)) {
log.debug(" No certificates found in HttpRequest.");
request.getCoyoteRequest().action
(ActionCode.ACTION_REQ_SSL_CERTIFICATE, null);
certs = (X509Certificate[])
request.getAttribute(Globals.CERTIFICATES_ATTR);
if (certs!=null && (certs.length >= 1))
log.debug("Certificates found in CoyoteRequest");
}
else
log.debug("Certificates found in HttpRequest");
if ((certs == null) || (certs.length < 1)) {
if (log.isDebugEnabled())
log.debug(" No certificates included with this request");
/* response.sendError(HttpServletResponse.SC_BAD_REQUEST,
sm.getString("authenticator.certificates")); */
return (false);
}
log.debug("Found certificates, authenticating using them!");
// Authenticate the specified certificate chain
principal = context.getRealm().authenticate(certs);
if (principal == null) {
if (log.isDebugEnabled())
log.debug(" Realm.authenticate() returned false");
/* response.sendError(HttpServletResponse.SC_UNAUTHORIZED,
sm.getString("authenticator.unauthorized")); */
return (false);
}
// Cache the principal (if requested) and record this authentication
register(request, response, principal, Constants.CERT_METHOD,
null, null);
return (true);
}
}