2008-07-02

OpenID authentication with Spring Security

This page describes an experiment with adding OpenID authentication to a web application using Spring Security. It describes OpenID a little and gives detailed instructions on how I was able to add OpenID authentication to a web application. I hope this page is useful for you. If you find any mistakes I made or if I'm not clear, please respond.

OpenID

OpenID authentication makes it possible to use the same 'username' and password for multiple sites. The 'username' is in fact a URL to a web page, typically the URL of a homepage or personal blog. You can register an ID at several sites, or run an OpenID server. I registered my OpenID at http://claimid.com/. My OpenID, registered at the claimid.com site is https://openid.claimid.com/fred-vos. I can use this ID at different sites, but if I don't like claimid.com anymore, I don't want to change my ID everywhere. So I used a service OpenID authentication allows. I made a web page using a URL to the following page: http://openid.fredvos.nl/ and added the following lines in the body/header of the HTML page:
  <link rel="openid.server" href="https://openid.claimid.com/server" />
<link rel="openid.delegate" href="https://openid.claimid.com/fred-vos" />
This tells an authentication agent to use my https://openid.claimid.com/fred-vos ID and use https://openid.claimid.com/server as the server. If I want to switch to another OpenID server or forget my password, I can create an ID at another site and change the two lines in my http://openid.fredvos.nl/ page and continue to use my personal OpenID. More information on OpenID can be found at many sites on the Internet, for instance at the OpenID homepage.

I not only want to use my OpenID at several sites, I also want to use OpenID authentication for my web applications. Spring Security, formerly known as 'Acegi', supports a lot of authentication mechanisms. Recently Spring Security added support for OpenID authentication, so I tried Spring security for my experiment.

Kora

First I created a small working web application called 'kora'. Maybe I'm going to develop this thing further into something useful, but for now it's just a sample application.

I used Maven as the build tool for this. The web application consists of an index.jsp page (URL=http://localhost:8180/kora-1.0-SNAPSHOT/index.jsp) with a link to a servlet that responds to http://localhost:8180/kora-1.0-SNAPSHOT/kora?request=play with a web page showing the text 'Pling!'. Tomcat listens to port 8180 on my machine. At this stage Kora didn't use authentication/authorization.

Then I tried to get a part of this web application accessible only via my OpenID.

Added some jars to the local repository

Some of the jars I needed, were not available in Maven repositories, so I had to add these to my local repository by hand.

I downloaded Spring Security version 2.0.2 as a zip-file at the Spring Framework downloads page, unpacked the zip-file, did a cd to the spring-security-2.0.2/dist directory and added the core and openid jars to my local Maven repository, using:

$ mvn install:install-file \
-Dfile=spring-security-core-2.0.2.jar \
-DgroupId=org.springframework.security \
-DartifactId=spring-security-core \
-Dversion=2.0.2 \
-Dpackaging=jar \
-DgeneratePom=true
$ mvn install:install-file \
-Dfile=spring-security-openid-2.0.2.jar \
-DgroupId=org.springframework.security \
-DartifactId=spring-security-openid \
-Dversion=2.0.2 \
-Dpackaging=jar \
-DgeneratePom=true

Added dependencies to pom.xml

Then I added the following dependencies to my Maven pom.xml:

  <dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-core</artifactId>
<version>2.0.2</version>
</dependency>

<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-openid</artifactId>
<version>2.0.2</version>
</dependency>

<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-web</artifactId>
<version>2.5.5</version>
</dependency>

<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aop</artifactId>
<version>2.5.5</version>
</dependency>

<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>2.5.5</version>
</dependency>

<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-dao</artifactId>
<version>2.0.2</version>
</dependency>

<dependency>
<groupId>org.openid4java</groupId>
<artifactId>openid4java</artifactId>
<version>0.9.3</version>
</dependency>

<dependency>
<groupId>javax.servlet</groupId>
<artifactId>jstl</artifactId>
<version>1.2</version>
</dependency>

<dependency>
<groupId>taglibs</groupId>
<artifactId>standard</artifactId>
<version>1.1.2</version>
</dependency>

File applicationContext.xml

This is a file that describes the access rights. I copied a file called applicationContext.xml from an unpacked samples zip found here, to src/main/webapp/WEB-INF/applicationContext.xml.
Here's a slightly edited version:

<?xml version="1.0" encoding="UTF-8"?>

<b:beans xmlns="http://www.springframework.org/schema/security"
xmlns:b="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-2.0.xsd
http://www.springframework.org/schema/security
http://www.springframework.org/schema/security/spring-security-2.0.1.xsd">

<http>
<intercept-url pattern="/**" access="ROLE_USER" />
<intercept-url pattern="/index.jsp*" filters="none" />
<intercept-url pattern="/openidlogin.jsp*" filters="none" />

<logout />
<openid-login login-page="/openidlogin.jsp" />
</http>

<authentication-manager alias="authenticationManager" />

<user-service id="userService">

<user name="http://openid.fredvos.nl/" password="notused"
authorities="ROLE_SUPERVISOR,ROLE_USER" />
</user-service>

</b:beans>

As you can see, pages openidlogin.jsp (discussed later) and index.jsp can be accessed by anyone, and all other pages in all directories (/**) only by persons with ROLE_USER and that the person with OpenID http://openid.fredvos.nl/ has roles ROLE_USER and ROLE_SUPERVISOR, so this person can access all Kora pages. Furthermore you see that the login page for OpenID login is openidlogin.jsp. This page is discussed below.

File openidlogin.jsp

This is the login page. I copied a file called openidlogin.jsp from the sample application mentioned before to src/main/webapp/openidlogin.jsp. I haven't made any changes to this file. Here's the source:

<%@ taglib prefix='c' uri='http://java.sun.com/jstl/core_rt' %>
<%@ page import="org.springframework.security.ui.AbstractProcessingFilter" %>
<%@ page import="org.springframework.security.ui.webapp.AuthenticationProcessingFilter" %>

<%@ page import="org.springframework.security.AuthenticationException" %>

<html>
<head>
<title>Open ID Login</title>
</head>

<body onload="document.f.j_username.focus();">
<h3>Please Enter Your OpenID Identity</h3>

<%-- this form-login-page form is also used as the
form-error-page to ask for a login again.
--%>
<c:if test="${not empty param.login_error}">
<font color="red">

Your login attempt was not successful, try again.<br/><br/>
Reason: <c:out value="${SPRING_SECURITY_LAST_EXCEPTION.message}"/>.
</font>
</c:if>


<form name="f" action="<c:url value='j_spring_openid_security_check'/>" method="POST">

<table>
<tr><td>OpenID Identity:</td><td><input type='text' name='j_username' value='<c:if test="${not empty param.login_error}"><c:out value="${SPRING_SECURITY_LAST_USERNAME}"/></c:if>'/></td></tr>

<tr><td colspan='2'><input name="submit" type="submit"></td></tr>
<tr><td colspan='2'><input name="reset" type="reset"></td></tr>
</table>

</form>

</body>
</html>

File web.xml

To activate the filtering process through Spring Security, I added the following text to my src/main/webapp/WEB-INF/web.xml file:

  <filter>
<filter-name>springSecurityFilterChain</filter-name>
<filter-class>
org.springframework.web.filter.DelegatingFilterProxy
</filter-class>
</filter>

<filter-mapping>
<filter-name>springSecurityFilterChain</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>

<servlet>
<servlet-name>context</servlet-name>
<servlet-class>
org.springframework.web.context.ContextLoaderServlet
</servlet-class>
<load-on-startup>1</load-on-startup>

</servlet>

Test

After

$ mvn tomcat:deploy

everything was compiled, a war-file was assembled, and the war-file was sent to Tomcat running on localhost and listening to port 8180. Opening URL http://localhost:8180/kora-1.0-SNAPSHOT/index.jsp showed the page with the link to the protected http://localhost:8180/kora-1.0-SNAPSHOT/kora?request=play page in my browser. Clicking this link didn't show the protected page at first, but the request was intercepted and the openidlogin.jsp was shown. Entering my OpenID and clicking the 'Submit query' button, redirected the browser to the claimid.com site. After I logged in to that page with fred-vos as username and my password, I got redirected to the protected page that says 'Pling!'.

Not that easy - please respond

It looks as if adding the authentication was an immediate and simple success, but it wasn't. It was quite difficult. Only after I found the sample application, I was able to get everything working. There are a lot of pages on the Internet with outdated instructions. The instructions on this page will be outdated too in the future if I don't add updates. So please respond if it doesn't work anymore, if possible with necessary changes.