JBoss Community Archive (Read Only)

PicketLink

TicketMonster

Introduction

This example shows you how to configure the TicketMonster application provided by the JBoss Developer Framework to use the PicketLink Security layer.

What this example is about ?

Basically, we'll use PicketLink to provide the following features:

  • Access Management (Authentication and Authorization)

    • FORM-based Authentication;

    • Role-based Authorization (RBAC) for URIs;

    • Annotation-based Authorization (Using Apache Deltaspike Security Annotations);

      • PicketLink and Apache Deltaspike can be used together to provide a rich and robust set of CDI security related extensions.

    • Logout;

    • User Self-Registration;

    • REST endpoints for login, logout, self-registration and user information.

  • Identity Management

    • JPA-backed Identity Store to manage users,roles, groups and relationships;

The TicketMonster users can be categorized in two types:

  • User

  • Adminstrators

Most of the pages are public, so any TicketMonster user can access them. But only registered and authenticated users can book events. Also, only administrators have access to the admin pages.

This example will show you how to:

  • Configure a login page for your application that uses AJAX to invoke a REST login service;

  • Logout your users by invoking a REST logout service;

  • Restrict access for your application's URIs based on the user's roles;

  • Create a simple self-registration functionality using AJAX and a REST endpoint;

  • Share between client and server a simple security context for authenticated users;

Before you start

Before you start, it is important that you understand some key concepts like:

If you want to take a look at the sources of a TicketMonster version with all the changes discussed along this tutorial, please check:

Configure and Deploy TicketMonster

Before continuing, please follow the TicketMonster Tutorial about how to configure your environment, build and run the application.

Make sure you have configured the Administration UI as described in the TicketMonster tutorial.

PicketLink Configuration

PicketLink can be easily enabled in the TicketMonster application by using the PicketLink Base library. This library provides some integration points for CDI applications to create a security layer that provides all PicketLink security capabilities.

What are the steps ?

After having your TicketMonster application properly configured and running (with the administration UIs) you need to:

  • Configure JBoss BOMs for Security and Apache Deltaspike
    JBoss distributes a complete set of Java EE 6 APIs including a Bill of Materials (BOM). A BOM specifies the versions of a "stack" (or a collection) of artifacts so that we always get the correct versions.

  • Configure the PicketLink dependencies
    These dependencies enable Picketlink in your application. It provides all the authentication, authorization and identity management capabilities.

  • Configure Apache Deltaspike Security dependencies
    As you'll see later, we use Apache Deltaspike for annotation-based authorization.

  • Configure the PicketLink IDM default schema dependenciy
    As we're going to use a JPA-backed identity store, we need to provide some JPA entities in order to store identity related information. To make things easier, PicketLink already provides some default entities out-of-box.

  • Configure the persistence.xml with the JPA entities provided by the picketlink-idm-schema dependency 

  • Produce a EntityManager with the @PicketLink Qualifier

Maven Dependencies

The easiest way to configure the PicketLink dependencies is using the JBoss BOM Security Stack:

If you select and copy the following pom.xml elements, you may need to paste them into a text editor and remove additional formatting. 

<!-- Define the version of the JBoss EAP BOMs we want to import to specify tested stacks. -->
<version.jboss.bom.eap>6.2.0-redhat-1</version.jboss.bom.eap>

 <!-- Define the version of the JBoss WFK BOMs we want to import to specify tested stacks. -->
<version.jboss.bom.wfk>2.4.0-redhat-1</version.jboss.bom.wfk>

<dependencyManagement>
  <dependencies>
    <!-- JBoss BOM for Java EE 6 APIs and PicketLink -->
    <dependency>
      <groupId>org.jboss.bom.eap</groupId>
      <artifactId>jboss-javaee-6.0-with-security</artifactId>
      <version>${version.jboss.bom.eap}</version>
      <type>pom</type>
      <scope>import</scope>
    </dependency>
    <!-- JBoss BOM for Java EE 6 APIs and Apache Deltaspike -->
    <dependency>
      <groupId>org.jboss.bom.wfk</groupId>
      <artifactId>jboss-javaee-6.0-with-deltaspike</artifactId>
      <version>${version.jboss.bom.wfk}</version>
      <type>pom</type>
      <scope>import</scope>
    </dependency>
  </dependencies>
</dependencyManagement>

We're also configuring Apache Deltaspike BOM. We'll use Apache Deltaspike Security Annotations to demonstrate annotation-based authorization.

Now we have the BOMs properly configured, we just define the dependencies as following:

<!-- PicketLink dependencies. They will provide all security capabilities for the application. -->

<!-- PicketLink API provides authentication and authorization services. -->
<dependency>
  <groupId>org.picketlink</groupId>
  <artifactId>picketlink-api</artifactId>
  <scope>compile</scope>
</dependency>

<!-- PicketLink Impl provides the default implementation for the PicketLink API. -->
<dependency>
  <groupId>org.picketlink</groupId>
  <artifactId>picketlink-impl</artifactId>
  <scope>runtime</scope>
</dependency>

<!-- PicketLink IDM Schema provides some built-in JPA entities to be used with the JPA Identity Store. -->
<dependency>
  <groupId>org.picketlink</groupId>
  <artifactId>picketlink-idm-simple-schema</artifactId>
  <scope>compile</scope>
</dependency>

<!-- Deltaspike API. We use compile scope as we need compile against its API -->
<dependency>
  <groupId>org.apache.deltaspike.core</groupId>
  <artifactId>deltaspike-core-api</artifactId>
  <scope>compile</scope>
</dependency>

<!-- Deltaspike Impl. we use runtime scope as we need its implementation
     dependencies only on runtime -->
<dependency>
  <groupId>org.apache.deltaspike.core</groupId>
  <artifactId>deltaspike-core-impl</artifactId>
  <scope>runtime</scope>
</dependency>

<!-- Deltaspike Security Module API. We use compile scope as we need
     to compile against its API -->
<dependency>
  <groupId>org.apache.deltaspike.modules</groupId>
  <artifactId>deltaspike-security-module-api</artifactId>
  <scope>compile</scope>
</dependency>

<!-- Deltaspike Security Impl. we use runtime scope as we need its implementation
     dependencies only on runtime -->
<dependency>
  <groupId>org.apache.deltaspike.modules</groupId>
  <artifactId>deltaspike-security-module-impl</artifactId>
  <scope>runtime</scope>
</dependency>

PicketLink Configuration

In our case, since we're using a JPA-backed Identity Store, we need to define the JPA entities provided by the sample schema in the META-INF/persistence.xml: 

<?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="primary">
        ...

        <!-- All those entity classes are provided by the picketlink-idm-simple-schema module. -->

        <class>org.picketlink.idm.jpa.model.sample.simple.AttributedTypeEntity</class>
        <class>org.picketlink.idm.jpa.model.sample.simple.AccountTypeEntity</class>
        <class>org.picketlink.idm.jpa.model.sample.simple.RoleTypeEntity</class>
        <class>org.picketlink.idm.jpa.model.sample.simple.GroupTypeEntity</class>
        <class>org.picketlink.idm.jpa.model.sample.simple.IdentityTypeEntity</class>
        <class>org.picketlink.idm.jpa.model.sample.simple.RelationshipTypeEntity</class>
        <class>org.picketlink.idm.jpa.model.sample.simple.RelationshipIdentityTypeEntity</class>
        <class>org.picketlink.idm.jpa.model.sample.simple.PartitionTypeEntity</class>
        <class>org.picketlink.idm.jpa.model.sample.simple.PasswordCredentialTypeEntity</class>
        <class>org.picketlink.idm.jpa.model.sample.simple.AttributeTypeEntity</class>

        ...
 </persistence-unit>
</persistence>

For last, we need to tell PicketLink which EntityManager should be used when performing the IDM operations.

org.jboss.jdf.example.ticketmonster.util.Resources
    @Produces
    @PicketLink
    @PersistenceContext(unitName = "primary")
    private EntityManager picketLinkEntityManager;

For more information about how to configure other Identity Store (LDAP or File based) check the PicketLink documentation.

Authentication

Most of the functionality provided by TicketMonster is done with JavaScript and AJAX calls to the server. The authentication follows the same principle. Basically, what we have is a AJAX call to a REST endpoint that knows how to authenticate users using a username/password mechanism.

Login Service

The REST endpoint is very simple and uses the PicketLink API to authenticate users, as follows:

org.jboss.jdf.example.ticketmonster.security.rest.LoginService
    @Inject
    private Identity identity;

    @Inject
    private DefaultLoginCredentials credentials;

    @POST
    @Produces(MediaType.APPLICATION_JSON)
    public SecurityContext login(DefaultLoginCredentials credential) {
        if (!this.identity.isLoggedIn()) {
            this.credentials.setUserId(credential.getUserId());
            this.credentials.setPassword(credential.getPassword());
            this.identity.login();
        }

        Account account = this.identity.getAccount();

        if (account == null) {
            account = new User();
        }

        return Response.ok().entity(account).type(MediaType.APPLICATION_JSON_TYPE).build();
    }

PicketLink provides a core component called org.picketlink.Identity. If you have ever used JBoss Seam Security, you find it very familiar. Through this component you're able to authenticate your users using their credentials, check if the user is authenticated or even get the authenticated user information loaded from the underlying identity store.

In this case, we're using a simple username/password authentication. Providing the credentials using a request-scoped org.picketlink.credential.DefaultLoginCredentials instance and invoking the login method on the Identity bean.

If the authentication is successful we return an user instance populated with some information loaded from the IDM.

Logout Service

To logout an user you only need to invoke the logout method on the Identity bean.

org.jboss.jdf.example.ticketmonster.security.rest.LogoutService
    @Inject
    private Identity identity;

    @POST
    public void logout() {
        if (this.identity.isLoggedIn()) {
            this.identity.logout();
        }
    }

In this example we're using a REST endpoint because most of TicketMonster pages are JavaScript/AJAX based. You can also use the Identity bean directly from JSF pages to perform the logout. You can use a EL such as:

#{identity.logout()}

Authorization

Most of TicketMonster functionalities are for public access. But some of them require the user to be authenticated or have a specific role in order to access them.

  • Only authenticated users can book events. We need to protect both pages and REST endpoints.

  • Only authenticated users with the role Administrator can access the admin pages.

Role-based Authorization Filter

To restrict access to the URIs there is a filter that intercepts every and single request to perform some authorization checks. This is a very simple filter, that uses the org.jboss.jdf.example.ticketmonster.security.AuthorizationManager to check if the user is allowed to access a specific URI.

org.jboss.jdf.example.ticketmonster.security.RoleBasedAuthorizationFilter
    @Inject
    private Instance<Identity> identity;

    @Inject
    private Instance<IdentityManager> identityManager;

    @Inject
    private AuthorizationManager authorizationManager;

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException,
            ServletException {
        HttpServletRequest httpRequest = (HttpServletRequest) request;
        HttpServletResponse httpResponse = (HttpServletResponse) response;

        try {
            if (this.authorizationManager.isAllowed(httpRequest)) {
                performAuthorizedRequest(chain, httpRequest, httpResponse);
            } else {
                handleUnauthorizedRequest(httpRequest, httpResponse);
            }
        } catch (AccessDeniedException ade) {
            handleUnauthorizedRequest(httpRequest, httpResponse);
        } catch (Exception e) {
            if (AccessDeniedException.class.isInstance(e.getCause())) {
                handleUnauthorizedRequest(httpRequest, httpResponse);
            } else {
                httpResponse.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
            }
        }
    }

Annotation-based Authorization

As discussed before, we need to protect the REST endpoints from  unauthorized access . In this example, we're using a simple security annotation to restrict method invocations only for authenticated users . Most of this functionality is provided by Apache Deltaspike Security module.

org.jboss.jdf.example.ticketmonster.rest.BookingService
@Consumes(MediaType.APPLICATION_JSON)
@UserLoggedIn
public Response createBooking(BookingRequest bookingRequest) {

    ...

}
As you noticed, the method is above is annotated with the @UserLoggedIn security annotation. Only authenticated users should be allowed to invoke this method.
org.jboss.jdf.example.ticketmonster.security.UserLoggedIn
@Target(value={TYPE,METHOD})
@Retention(value=java.lang.annotation.RetentionPolicy.RUNTIME)
@SecurityBindingType
@Documented
public @interface UserLoggedIn {

}
In order to associate the annotation above with a specific authorization logic, we need to annotate it with the @SecurityBindingType. Now we can provide the authorization logic in the following way:
org.jboss.jdf.example.ticketmonster.security.AuthorizationManager
    @Secures
    @UserLoggedIn
    public boolean isUserLoggedIn(Identity identity) {
        return identity.isLoggedIn();
    }
Note that the method above was annotated with the @Secures and also the @UserLoggedIn. This tells PicketLink that if a method is annotated with the @UserLoggedIn it should first perform the authorization logic above. If this method returns false, a org.apache.deltaspike.security.api.authorization.AccessDeniedException is thrown.

Using EL in JSF pages for Authorization

The org.picketlink.Identity is annotated with @Named. This means that you can easily access it from your JSF pages using EL. The example bellow shows how to check if an user is authenticated before displaying some content:

/src/main/webapp/resources/scaffold/pageTemplate.xhtml
<ui:fragment rendered="#{identity.loggedIn}">
    <li><a href="#">| User: <span id="userLoggedInName" style="color:white;">#{identity.account.firstName} #{identity.account.lastName}</span></a></li>
    <li><a href="#" onclick="performLogout();">| Logout |</a></li>
</ui:fragment>

You can also check for user roles or groups.

org.jboss.jdf.example.ticketmonster.security.AuthorizationManager
    @Inject
    private Instance<Identity> identity;

    @Inject
    private Instance<IdentityManager> identityManager;

    @Inject
    private Instance<RelationshipManager> relationshipManager;

    public boolean isAdmin() {
         Identity identity = getIdentity();

        if (isUserLoggedIn(identity)) {
            IdentityManager identityManager = getIdentityManager();
            RelationshipManager relationshipManager = getRelationshipManager();

            return BasicModel.hasRole(relationshipManager, identity.getAccount(), BasicModel.getRole(identityManager, "Administrator"));
        }

        return false;
    }
Now you can use this method in your pages:

/src/main/webapp/resources/scaffold/pageTemplate.xhtml
<ui:fragment rendered="#{authorizationManager.admin}">
    <li>
        <h:outputLink value="#{request.contextPath}/admin">
            <h:outputText value="Administration"/>
        </h:outputLink>
    </li>
</ui:fragment>

Identity Management

PicketLink also provides some Identity Management features that allows you to easily manage users, roles, groups, credentials and relationships. These features are provided by PicketLink IDM which provides a rich API for Identity Management.

User Self-Registration

The self-registration functionality is provided by a REST endpoint. Basically, this endpoint uses the org.picketlink.idm.IdentityManager and org.picketlink.idm.RelationshipManager to store user information and relationships.

org.jboss.jdf.example.ticketmonster.security.rest.SelfRegistrationService
    @Inject
    private IdentityManager identityManager;

    @Inject
    private RelationshipManager relationshipManager;

    @Inject
    private LoginService loginService;

    @POST
    @Produces(MediaType.APPLICATION_JSON)
    public SecurityContext register(RegistrationRequest request) {
        Map<String, Object> response = new HashMap<String, Object>();
        
        if (!request.getPassword().equals(request.getPasswordConfirmation())) {
            response.put(MESSAGE_RESPONSE_PARAMETER, "Password mismatch.");
        } else {
            try {
                // if there is no user with the provided e-mail, perform registration
                if (BasicModel.getUser(this.identityManager, request.getEmail()) == null) {
                    performRegistration(request);

                    // if the registration was successful, we perform a silent authentication.
                    return performSilentAuthentication(request);
                } else {
                    response.put(MESSAGE_RESPONSE_PARAMETER, "This username is already in use. Try another one.");
                }
            } catch (IdentityManagementException ime) {
                response.put(MESSAGE_RESPONSE_PARAMETER, "Oops ! Registration failed, try it later.");
            }
        }
        
        return Response.ok().entity(response).type(MediaType.APPLICATION_JSON_TYPE).build();
    }

    private void performRegistration(RegistrationRequest request) {
        User newUser = new User(request.getEmail());

        newUser.setFirstName(request.getFirstName());
        newUser.setLastName(request.getLastName());

        this.identityManager.add(newUser);
        
        Password password = new Password(request.getPassword());

        this.identityManager.updateCredential(newUser, password);

        Role userRole = BasicModel.getRole(this.identityManager, "User");

        BasicModel.grantRole(this.relationshipManager, newUser, userRole);
        
        Group userGroup = BasicModel.getGroup(this.identityManager, "Users");
        
        BasicModel.addToGroup(this.relationshipManager, newUser, userGroup);
    }

Initializing the Identity Store during startup

In order to create the default admin user and some roles and groups, we use a bean to initialize the configured identity stores (in our case a JPA-based store).

org.jboss.jdf.example.ticketmonster.security.IdentityManagementInitializer
    @Inject
    private PartitionManager partitionManager;

    @PostConstruct
    public void initialize() {
        IdentityManager identityManager = this.partitionManager.createIdentityManager();

        User admin = new User("admin@ticketmonster.org");

        admin.setFirstName("Almight");
        admin.setLastName("Administrator");
        
        // let's store the admin user
        identityManager.add(admin);

        Password password = new Password("letmein!");

        // updates the admin password
        identityManager.updateCredential(admin, password);

        Role adminRole = new Role("Administrator");

        // stores the admin role
        identityManager.add(adminRole);

        Group adminGroup = new Group("Administrators");

        // stores the admin group
        identityManager.add(adminGroup);

        RelationshipManager relationshipManager = this.partitionManager.createRelationshipManager();

        // grants to the admin user the admin role
        BasicModel.grantRole(relationshipManager, admin, adminRole);
        
        // add the admin user to the admin group
        BasicModel.addToGroup(relationshipManager, admin, adminGroup);
        
        Role userRole = new Role("User");
        
        identityManager.add(userRole);
        
        Group usersGroup = new Group("Users");
        
        identityManager.add(usersGroup);
    }

This allows you to login as administrator providing the following credentials:

  • Username: admin@ticketmonster.org

  • Password: letmein!

JBoss.org Content Archive (Read Only), exported from JBoss Community Documentation Editor at 2020-03-11 12:19:31 UTC, last content change 2013-11-26 21:49:26 UTC.