1. BASIC - org.apache.catalina.authenticator.BasicAuthenticator
2. FORM - org.apache.catalina.authenticator.FormAuthenticator
3. DIGEST - org.apache.catalina.authenticator.DigestAuthenticator
4. CLIENT-CERT - org.apache.catalina.authenticator.SSLAuthenticator
My previous post explains how BASIC authentication works with Tomcat.
In this post, we'll be adding a new type of Authenticator to Tomcat.
5. OPENID - org.wso2.OpenIDAuthenticator
With this you can protect your web resources with OpenID authentication.
I am using WSO2 OpenID Relying Party components which do ship with WSO2 Identity Solution.
Let's get started.
First we need to configure Tomcat to use our custom Authenticator.
Extract [CATALINA_HOME]\server\lib\catalina.jar and edit the file \org\apache\catalina\startup\Authenticators.properties to look like following - simply adding our Authenticator to it.
# These must match the allowed values for auth-method as defined by the spec BASIC=org.apache.catalina.authenticator.BasicAuthenticator CLIENT-CERT=org.apache.catalina.authenticator.SSLAuthenticator DIGEST=org.apache.catalina.authenticator.DigestAuthenticator FORM=org.apache.catalina.authenticator.FormAuthenticator NONE=org.apache.catalina.authenticator.NonLoginAuthenticator OPENID=org.wso2.OpenIDAuthenticatorNow we need to re-pack the extracted jar with our change to catalina.jar and keep it in it's original location.
You can download other dependency jars from here.
Copy the jars inside [ZIP_FILE]\jars to [CATALINA_HOME]\server\lib and the jars from [ZIP_FILE]\endorsed to [CATALINA_HOME]\common\endorsed.
That's it with Tomcat configuration.
Now, let's see how we can configure OPENID authentication for our webapp.
It's basically the same way you configure BASIC or any other auth-method for your web app.
You can copy [ZIP_FILE]\demo-app folder to [CATALINA_HOME]\webapps.
Let's have a look at [CATALINA_HOME]\webapps\demo-app\WEB-INF\web.xml.
<web-app> <security-constraint> <web-resource-collection> <web-resource-name>secured resources</web-resource-name> <url-pattern>/web/*</url-pattern> </web-resource-collection> <auth-constraint> <role-name>*</role-name> </auth-constraint> </security-constraint> <login-config> <auth-method>OPENID</auth-method> <form-login-config> <form-login-page>/openid-login.jsp</form-login-page> <form-error-page>/denied.jsp</form-error-page> </form-login-config> </login-config> </web-app>Here, the openid-login.jsp and denied.jsp pages are not application specific - so can be reused across.
All - set, let's see how the demo works - you can also access the online demo from here.
http://localhost:8080/demo-app/ - this is not a protected resource.
Click on the link to, Protected Resource - since this is OpenID protected and you are not authenticated yet, you'll be redirected to the OpenID login page - Type your OpenID there and complete the OpenID authentication routine.
You are on the protected resource now...
I'll just dump the code here for the OpenIDAuthenticator and the OpenIDRealm - it's self-explanatory through comments.
package org.wso2;
import java.io.IOException;
import java.security.Principal;
import javax.servlet.http.HttpServletResponse;
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.wso2.solutions.identity.relyingparty.RelyingPartyException;
import org.wso2.solutions.identity.relyingparty.TokenVerifierConstants;
import org.wso2.solutions.identity.relyingparty.openid.OpenIDAuthenticationRequest;
import org.wso2.solutions.identity.relyingparty.openid.OpenIDConsumer;
import org.wso2.solutions.identity.relyingparty.openid.OpenIDRequestType;
import org.wso2.solutions.identity.relyingparty.openid.OpenIDUtil;
/**
* This extends the functionality of FormAuthenticator to facilitate OpenID logins.
*
* @author Prabath Siriwardena @ WSO2
* http://www.wso2.org
* http://blog.facilelogin.com
*
*/
public class OpenIDAuthenticator extends FormAuthenticator {
/**
* {@inheritDoc}
*/
public boolean authenticate(Request request, Response response, LoginConfig config) {
Principal principal = null;
boolean loginAction = false;
boolean isAuthenticated = false;
String requestURI = null;
Realm realm = null;
String openID = null;
// References to objects we will need later
Session session = null;
principal = request.getUserPrincipal();
if (principal != null) {
// We are here because we have being authenticated successfully, before.
return true;
}
// Check whether this is a re-submit of the original request URI after successful
// authentication? If so, forward the *original* request instead.
if (matchRequest(request)) {
return matchRequest(request, response, config);
}
// This should be the OpenID return to url.
requestURI = request.getDecodedRequestURI();
// This request came from the login page - let me login - here are my credentials.
loginAction = (request.getParameter("login") != null);
if (!loginAction) {
// This can be the initial request for the protected resource or being redirected back
// by the OpenID Provider.
if (OpenIDUtil.isOpenIDAuthetication(request)) {
// This is an OpenID response - follow the OpenID protocol
String auth = null;
try {
OpenIDConsumer.getInstance().setSessionAttributes(request);
auth = (String) request.getAttribute(TokenVerifierConstants.SERVLET_ATTR_STATE);
if (auth != null && TokenVerifierConstants.STATE_SUCCESS.equals(auth)) {
isAuthenticated = true;
} else {
forwardToErrorPage(request, response, config);
return (false);
}
} catch (RelyingPartyException e) {
forwardToErrorPage(request, response, config);
return (false);
}
} else {
try {
session = request.getSessionInternal(true);
saveRequest(request, session);
} catch (IOException ioe) {
return (false);
}
request.getSession().setAttribute("requestURI", requestURI);
forwardToLoginPage(request, response, config);
return (false);
}
}
// You are here, because you came here directly from the openid-login page or you are
// authenticated at OP and redircted back.
if (!isAuthenticated) {
// Let's build the OpenID authentication request.
try {
doOpenIDAuthentication(request, response);
return false;
} catch (RelyingPartyException e) {
forwardToErrorPage(request, response, config);
return (false);
}
}
realm = context.getRealm();
session = request.getSessionInternal(false);
if (!(realm instanceof OpenIDRealm)) {
realm = new OpenIDRealm();
context.setRealm(realm);
}
openID = (String) request.getAttribute("openid_identifier");
principal = realm.authenticate(openID, "");
if (principal == null) {
forwardToErrorPage(request, response, config);
return (false);
}
// Save the authenticated Principal in our session
session.setNote(Constants.FORM_PRINCIPAL_NOTE, principal);
// Save the OpenID
session.setNote(Constants.SESS_USERNAME_NOTE, openID);
// We have no password for OpenID
session.setNote(Constants.SESS_PASSWORD_NOTE, "");
// Redirect the user to the original request URI (which will cause
// the original request to be restored)
requestURI = savedRequestURL(session);
try {
response.sendRedirect(response.encodeRedirectURL(requestURI));
} catch (IOException e) {
return (false);
}
return (false);
}
/**
* Performs OpenID authentication
*
* @param request Request we are processing
* @param response Response we are creating
* @throws RelyingPartyException
*/
protected void doOpenIDAuthentication(Request request, Response response)
throws RelyingPartyException {
OpenIDAuthenticationRequest openIDAuthRequest = null;
openIDAuthRequest = new OpenIDAuthenticationRequest(request, response);
openIDAuthRequest.setOpenIDUrl((String) request.getParameter("openIdUrl"));
openIDAuthRequest.addRequestType(OpenIDRequestType.SIMPLE_REGISTRATION);
if (request.getProtocol().equals("HTTP/1.1")) {
openIDAuthRequest.setReturnUrl("http://" + request.getLocalName() + ":"
+ request.getLocalPort() + request.getSession().getAttribute("requestURI"));
} else {
openIDAuthRequest.setReturnUrl("https://" + request.getLocalName() + ":"
+ request.getLocalPort() + request.getSession().getAttribute("requestURI"));
}
OpenIDConsumer.getInstance().doOpenIDAuthentication(openIDAuthRequest);
}
/**
* Check whether this is a re-submit of the original request URI after successful
* authentication? If so, forward the *original* request instead.
*
* @param request Request we are processing
* @param response Response we are creating
* @param config Login configuration describing how authentication should be performed
*/
private boolean matchRequest(Request request, Response response, LoginConfig config) {
Session session = null;
Principal principal = null;
session = request.getSessionInternal(true);
principal = (Principal) session.getNote(Constants.FORM_PRINCIPAL_NOTE);
register(request, response, principal, Constants.FORM_METHOD, (String) session
.getNote(Constants.SESS_USERNAME_NOTE), (String) session
.getNote(Constants.SESS_PASSWORD_NOTE));
// If we're caching principals we no longer need the username
// and password in the session, so remove them
if (cache) {
session.removeNote(Constants.SESS_USERNAME_NOTE);
session.removeNote(Constants.SESS_PASSWORD_NOTE);
}
try {
if (restoreRequest(request, session)) {
return (true);
} else {
response.sendError(HttpServletResponse.SC_BAD_REQUEST);
return (false);
}
} catch (IOException e) {
forwardToErrorPage(request, response, config);
return (false);
}
}
}
package org.wso2;
import java.security.Principal;
import org.apache.catalina.Context;
import org.apache.catalina.connector.Request;
import org.apache.catalina.connector.Response;
import org.apache.catalina.deploy.SecurityConstraint;
import org.apache.catalina.realm.GenericPrincipal;
import org.apache.catalina.realm.RealmBase;
/**
* This extends the functionality of RealmBase to facilitate OpenID logins.
*
* @author Prabath Siriwardena @ WSO2
* http://www.wso2.org
* http://blog.facilelogin.com
*
*/
public class OpenIDRealm extends RealmBase {
/**
* No passwords for OpenID
*/
protected String getPassword(String openID) {
return "";
}
/**
* {@inheritDoc}
*/
protected Principal getPrincipal(String OpenID) {
return new GenericPrincipal(this, OpenID, "", null, null);
}
/**
* We have no roles associated.
*/
public boolean hasRole(Principal principal, String role) {
return false;
}
/**
* Give this realm required permissions.
*/
public boolean hasResourcePermission(Request request, Response response,
SecurityConstraint[] constraints, Context context) {
return true;
}
/**
* Realm name
*/
protected String getName() {
return "OpenIDRealm";
}
}