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"; } }