January 31, 2012

Easy OAuth using DaliCore and Glassfish: the service provider

DaliCore is really starting to get some momentum recently, so I thought it is time to write something about it. DaliCore currently contains three modules:
dalicore-ejb
contains everything related to managing the dalicore entities: User, Content, Group and Permission
dalicore-oauth
allows web applications to become an oauth service provider
dalicore-externalnetwork
allows web applications to setup a connection with oauth providers

This blog entry will focus on the second module. I'll show you how easy it is with dalicore-oauth to create a web application that can serve as an oauth service provider. In the next blog entry, we'll make a second web application that serves as the oauth service consumer that will use dalicore-externalnetwork to connect to our service provider. Our application server of choice is glassfish 3.1.1 and we'll be using Jersey for our web related components.

It's probably better to download the sample service provider application already. This should make it easier to follow all the steps below.

Creating an application

Allright, time to get started. We start with creating the web application that will act as our oauth service provider. Fire up NetBeans and create a new Maven Web Application project.

Create a new Maven Web Application project in NetBeans

Maven setup

To be able to make use of dalicore-oauth, we need to change the pom.xml and add LodgOn's maven repository. Include the following code in your pom.xml:

<repositories>
  <repository>
    <id>lodgon</id>
    <url>http://maven.lodgon.com/maven2</url>
  </repository>
</repositories>

Now we can add the maven dependency to dalicore-oauth and other modules that we will need. Add the following dependencies in your pom.xml, in the dependencies section:

<dependency>
  <groupId>com.lodgon.dalicore</groupId>
  <artifactId>dalicore-ejb</artifactId>
  <version>0.9.11-SNAPSHOT</version>
</dependency>
<dependency>
  <groupId>com.lodgon.dalicore</groupId>
  <artifactId>dalicore-oauth</artifactId>
  <version>0.9.11-SNAPSHOT</version>
</dependency>
<dependency>
  <groupId>com.sun.jersey</groupId>
  <artifactId>jersey-server</artifactId>
  <version>1.8</version>
  <scope>provided</scope>
</dependency>
<dependency>
  <groupId>com.sun.jersey.contribs.jersey-oauth</groupId>
  <artifactId>oauth-signature</artifactId>
  <version>1.8</version>
</dependency>

Jersey handler and jsp's

An oauth service provider typically needs to provide three end points: one for requesting a request token, one to authenticate a user and a third to request an access token. The creation of the request and access tokens are automatically handled by dalicore-oauth. So, the only thing we need to add is the handling of user authentication. Create a new class called OAuthHandler with the following code:

@Path("oauth")
@ManagedBean
public class OAuthHandler {
  @Inject
  OAuthBean oauthBean;
  @Inject
  UserBean userBean;

  @GET
  @Path("authenticate")
  public Viewable authenticate(@QueryParam("oauth_token") String oauthToken) {
    return new Viewable("/login.jsp", new Model(oauthToken));
  }

  @POST
  @Path("login")
  public Viewable login(@FormParam("oauth_token") String oauthToken,
          @FormParam("username") String userName,
          @FormParam("password") String password) throws DaliCoreException {
    User user = userBean.getByUserId(userName);
    if (user == null) {
      user = new OAuthUser();
      user.setUserId(userName);
      user = userBean.create(user);
      userBean.setPassword(user.getId(), password);
    }

    if (userBean.validatePassword(user.getId(), password)) {
      DaliToken requestToken = oauthBean.findDaliToken(oauthToken, DaliToken.Type.OAUTH_REQUEST_TOKEN);
      return new Viewable("/authorize.jsp", new Model(oauthToken, user, oauthToken.getDaliServiceConsumer()));
    } else {
      return new Viewable("/login.jsp", new Model(oauthToken, "Login failed."));
    }
  }

  @POST
  @Path("authorize")
  public Response authorize(@FormParam("oauth_token") String oauthToken,
          @FormParam("userUid") String userUid,
          @DefaultValue("no") @FormParam("authorized") String authorized) throws DaliCoreException, URISyntaxException, UnsupportedEncodingException {
    DaliToken requestToken = oauthBean.findDaliToken(oauthToken, DaliToken.Type.OAUTH_REQUEST_TOKEN);

    if ("yes".equals(authorized)) {
      User user = userBean.getByUid(userUid);
      if (user != null) {
        String verifier = oauthBean.authorizeServiceConsumer(user.getId(), oauthToken, true);
        return Response.seeOther(new URI(requestToken.getCallback() + "?" + OAuthParameters.TOKEN + "=" + URLEncoder.encode(oauthToken, "UTF-8") + "&" + OAuthParameters.VERIFIER + "=" + URLEncoder.encode(verifier, "UTF-8"))).build();
      } else {
        return Response.ok(new Viewable("/login.jsp", new Model(oauthToken))).build();
      }
    } else {
      return Response.seeOther(new URI(requestToken.getCallback())).build();
    }
  }

  public static class Model {
    private String message;
    private String oauthToken;
    private DaliServiceConsumer serviceConsumer;
    private User user;

    public Model(String oauthToken) {
      this.oauthToken = oauthToken;
    }

    public Model(String oauthToken, String message) {
      this.oauthToken = oauthToken;
      this.message = message;
    }

    public Model(String oauthToken, User user, DaliServiceConsumer serviceConsumer) {
      this.oauthToken = oauthToken;
      this.user = user;
      this.serviceConsumer = serviceConsumer;
    }

    public String getMessage() {
      return message;
    }

    public void setMessage(String message) {
      this.message = message;
    }

    public String getOauthToken() {
      return oauthToken;
    }

    public void setOauthToken(String oauthToken) {
      this.oauthToken = oauthToken;
    }

    public DaliServiceConsumer getServiceConsumer() {
      return serviceConsumer;
    }

    public void setServiceConsumer(DaliServiceConsumer serviceConsumer) {
      this.serviceConsumer = serviceConsumer;
    }

    public User getUser() {
      return user;
    }

    public void setUser(User user) {
      this.user = user;
    }
  }
}

The class is a Jersey handler that contains three endpoints. The first endpoint authenticate will be called by service consumers that would like to authenticate a user for their application. We will show a basic login page where a user can provide a username and password.

The second endpoint login handles a user that tries to login. For the sake of simplicity, we will create a new user when no user was found with the provided username. Notice, that this user must be of type OAuthUser, which is an extension of the dalicore-ejb User. Upon successful login, the user will be redirected to an authorization page where he/she can choose whether to allow the service consumer to access his/her user data.

Which brings us to the third endpoint authorize, where a user authorizes the service consumer. When the user allows access to the service consumer, we will call the service consumer's callback. A service consumer should have provided this callback when it requested the request token earlier on in the oauth process. We will come back to that when we create the service consumer application.

Below you will find the two jsp's (login.jsp and authorize.jsp) that we will need, one to allow a user to login and another one to authorize a specific service consumer.

<%@page contentType="text/html" pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
  <head>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
    <title>dalicore-oauth service provider</title>
  </head>
  <body>
    <h1>Authenticate</h1>

    <form method="POST" action="${pageContext.request.contextPath}/rest/oauth/login">
      <input type="hidden" name="oauth_token" value="${it.oauthToken}"/>
      username: <input type="text" name="username"/><br/>
      password: <input type="password" name="password"/><br/>
      <input type="submit" value="login"/>
    </form>
  </body>
</html>
<%@page contentType="text/html" pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
  <head>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
    <title>dalicore-oauth service provider</title>
  </head>
  <body>
    <h1>Authorize</h1>

    <h4>Do you want to allow the service consumer <b>${it.serviceConsumer.name}</b> to access your data:</h4>

    <form method="POST" action="${pageContext.request.contextPath}/rest/oauth/authorize">
      <input type="hidden" name="oauth_token" value="${it.oauthToken}"/>
      <input type="hidden" name="userUid" value="${it.user.uid}"/>
      <input type="submit" name="authorize" value="yes"/>
      <input type="submit" name="authorize" value="no"/>
    </form>
  </body>
</html>

CDI configuration

You probably noticed that at the top of the OAuthHandler class, we use CDI to inject a reference to the UserBean and the OAuthBean. In order for CDI to work, we need to add a beans.xml in the WEB-INF directory. So, create the directory WEB-INF in the Web Pages directory and add the following beans.xml:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://java.sun.com/xml/ns/javaee"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/beans_1_0.xsd">
</beans>

web.xml configuration

The next steps are required to allow the dalicore-oauth module to be plugged in correctly. We need to configure the Jersey servlet so it can handle oauth requests. This is all done in the standard deployment descriptor (probably better known as web.xml). In NetBeans you can right-click on Web Pages and choose "New -> Standard Deployment Descriptor (web.xml)". In the web.xml file we will add the section for the jersey servlet:

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

<web-app xmlns="http://java.sun.com/xml/ns/javaee"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
  version="3.0">
  <servlet>
    <servlet-name>jerseyservlet</servlet-name>
    <servlet-class>com.sun.jersey.spi.container.servlet.ServletContainer</servlet-class>
    <init-param>
      <param-name>com.sun.jersey.config.property.oauth.ignorePathPattern</param-name>
      <param-value>requestToken|accessToken</param-value>
    </init-param>
    <init-param>
      <param-name>com.sun.jersey.config.property.packages</param-name>
      <param-value>com.sun.jersey.oauth.server.api.resources;com.lodgon.dali.core.oauth;com.mycompany.oauthprovider</param-value>
    </init-param>
  </servlet>

  <servlet-mapping>
    <servlet-name>jerseyservlet</servlet-name>
    <url-pattern>/rest/*</url-pattern>
  </servlet-mapping>

  <session-config>
    <session-timeout>
      30
    </session-timeout>
  </session-config>
</web-app>

There are two init parameters for the Jersey servlet that require some explanation. When a URL on our web application is called with the Authorization HTTP header, it will be filtered by the Jersey OAuth filter. However, getting a request token or access token also contains the Authorization HTTP header. Since these aren't regular OAuth requests, these should be ignored by the OAuth filter. This can be done by specifying the com.sun.jersey.config.property.oauth.ignorePathPattern init parameter.

The second init parameter com.sun.jersey.config.property.packages is required to tell Jersey which packages to scan for providers and handlers. Be sure not to forget to specify the package in which you created the OAuthHandler class. Otherwise it won't be detected by Jersey and you'll receive 404 responses when trying to authenticate a user.

JPA configuration

The final requirement is to configure our connection to the database. This is needed because dalicore-oauth persistently stores its request and access tokens, its users and its list of service consumers. Create a persistence.xml file in the directory src/main/resources/META-INF with the following content:

<?xml version="1.0" encoding="UTF-8"?>
<persistence version="2.0" xmlns="http://java.sun.com/xml/ns/persistence" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/persistence http://java.sun.com/xml/ns/persistence/persistence_2_0.xsd">
  <persistence-unit name="oauth_provider_pu" transaction-type="JTA">
    <provider>org.eclipse.persistence.jpa.PersistenceProvider</provider>
    <jta-data-source>jdbc/oauthproviderdb</jta-data-source>
    <class>com.lodgon.dali.core.entity.Application</class>
    <class>com.lodgon.dali.core.entity.Content</class>
    <class>com.lodgon.dali.core.entity.ContentPermission</class>
    <class>com.lodgon.dali.core.entity.Field</class>
    <class>com.lodgon.dali.core.entity.Group</class>
    <class>com.lodgon.dali.core.entity.GroupPermission</class>
    <class>com.lodgon.dali.core.entity.TypePermission</class>
    <class>com.lodgon.dali.core.entity.User</class>
    <class>com.lodgon.dali.core.entity.UserPermission</class>
    <class>com.lodgon.dali.core.oauth.entity.Attribute</class>
    <class>com.lodgon.dali.core.oauth.entity.DaliToken</class>
    <class>com.lodgon.dali.core.oauth.entity.DaliServiceConsumer</class>
    <class>com.lodgon.dali.core.oauth.entity.OAuthUser</class>
    <exclude-unlisted-classes>false</exclude-unlisted-classes>
    <properties>
      <property name="eclipselink.ddl-generation" value="create-tables"/>
    </properties>
  </persistence-unit>
</persistence>

Pre-deployment configuration

We're almost there. Before we can actually deploy our application, we need to add the JDBC resources to glassfish. Create a file called sun-resources.xml with the following content:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE resources PUBLIC "-//Sun Microsystems, Inc.//DTD Application Server 9.0 Resource Definitions //EN" "http://www.sun.com/software/appserver/dtds/sun-resources_1_3.dtd">
<resources>
  <jdbc-connection-pool connection-validation-method="auto-commit" datasource-classname="org.apache.derby.jdbc.EmbeddedDataSource" res-type="javax.sql.DataSource" name="derby_oauthproviderdb_j2eepool" wrap-jdbc-objects="false">
    <property name="ConnectionAttributes" value=";create=true" />
    <property name="DatabaseName" value="memory:oauthproviderdb" />
  </jdbc-connection-pool>
  <jdbc-resource enabled="true" jndi-name="jdbc/oauthproviderdb" object-type="user" pool-name="derby_oauthproviderdb_j2eepool"/>
</resources>

I choose to connect to an in memory derby database here, but you can of course configure your resources to connect to a real database. Add the resources to glassfish by calling the following asadmin command:

$ asadmin add-resources sun-resources.xml

Building and deployment

Now you can build the application and deploy it in glassfish. Building can be done in NetBeans by pressing F11 and deploying is done with the shortkey F6. You can of course also use the command line if you prefer that:

$ mvn clean install
$ asadmin deploy --name oauthprovider --contextroot oauthprovider target/oauthprovider-1.0-SNAPSHOT.war

The application should deploy successfully and now you can point your browser to the following location: http://localhost:8080/oauthprovider/rest/oauth/authenticate. This should show the login screen.

Now it's time to head over to part 2, where we will create the service consumer side that will connect to the service provider application we created here.

No comments: