<dependencies> ... <dependency> <groupId>org.picketbox</groupId> <artifactId>picketbox-cdi</artifactId> <version>${picketbox.version}</version> </dependency> ... </dependencies>
This example shows you how to configure the Aerogear HTML5 + REST TODO Application with CDI and PicketBox.
Basically, we'll use PicketBox to provide the following features:
Support different authentication mechanisms using JAX-RS Endpoints (OTP, Username/Password, Social - Facebook, Google and Twitter)
Use a database to store users, credentials, roles and groups
Self-registration service for new users
Token-based Authentication using PicketBox Session Management cababilities
Authorization for JAX-RS Endpoints
Automatic user provisioning when using any of the social authentication mechanisms
Handle specific security events
More advanced concepts and features will be provided after the basic functionality is properly discussed and agreed.
Before you start, is recommended that you take a look at:
Before continuing we recommend you take a look at the Configuration API documentation.
To enable PicketBox you only need a few steps, as follows:
Configure PicketBox maven dependencies
Configure PicketBox by creating a @Producer method that produces a ConfigurationBuilder instance.
If you are using Maven, please configure your pom.xml with the following dependencies:
<dependencies> ... <dependency> <groupId>org.picketbox</groupId> <artifactId>picketbox-cdi</artifactId> <version>${picketbox.version}</version> </dependency> ... </dependencies>
@ApplicationScoped public class PicketBoxConfigurer { /** * <p> * As we are using a database identity store, we need to inject and configure the JPATemplate to integrate with your JPA configuration. * </p> */ @Inject private DefaultJPATemplate jpaTemplate; /** * <p>Inject a custom authentication mechanism used to authenticate users with Facebook.</p> */ @Inject private FacebookAuthenticationMechanism fbAuthenticationMechanism; /** * <p>Inject a custom authentication mechanism used to authenticate users with OpenID.</p> */ @Inject private OpenIDAuthenticationMechanism openidAuthenticationMechanism; /** * <p>Inject a custom authentication mechanism used to authenticate users with Twitter.</p> */ @Inject private TwitterAuthenticationMechanism twitterAuthenticationMechanism; /** * <p> * Produces the {@link ConfigurationBuilder}. * </p> * * @return */ @Produces public ConfigurationBuilder produceConfiguration() { ConfigurationBuilder builder = new ConfigurationBuilder(); // configure the social authentication mechanisms builder .authentication() .mechanism(this.fbAuthenticationMechanism) .mechanism(this.openidAuthenticationMechanism) .mechanism(this.twitterAuthenticationMechanism); // configure the identity manager using a JPA-based identity store. builder .identityManager() .jpaStore().template(this.jpaTemplate); // session management configuration builder .sessionManager() .inMemorySessionStore(); return builder; } }
We have configured some custom authentication mechanisms that allow users to authenticate using Facebook, Google (OpenID) and Twitter.
// configure the social authentication mechanisms builder .authentication() .mechanism(this.fbAuthenticationMechanism) .mechanism(this.openidAuthenticationMechanism) .mechanism(this.twitterAuthenticationMechanism);
By default, PicketBox provides some built-in authentication mechanisms such as Username/Password, One-Time Password, Certificate and Trusted Username. That means you don't need to provide any additional configuration to start using them.
See the Authentication Mechanisms documentation for more information.
The Identity Management configuration is done by choosing a JPA-based Identity Store to store users, credentials, roles and groups. That means we are using a database to store those information.
// configure the identity manager using a JPA-based identity store. builder .identityManager() .jpaStore() .template(this.jpaTemplate);
When using a JPA-based Identity Store we need to configure how EntityManager instances should be retrieved and used by PicketBox when invoking identity management operations. This is done by providing a JPATemplate instance.
PicketBox CDI already provides a built-in JPATemplate implementation that gets the current EntityManager instance. If you need more fine-grained control about how the EntityManager is used you can always provide your own implementation.
For more information about how to configure your application's META-INF/persistence.xml, check this documentation.
This example uses the PicketBox Session Management capabilities to control users sessions and to provide a simple token-based authentication.
// session management configuration builder .sessionManager() .inMemorySessionStore();
The configuration above enables the session management capabilities using a In-Memory Session Store. When an user is authenticated a session will be created and associated with the user. Each session has a unique identifier that will be used as a token to identity a specific user between requests.
<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"> <interceptors> <class>org.apache.deltaspike.security.impl.extension.SecurityInterceptor</class> </interceptors> </beans>
The image bellow shows how the login page looks like:
As you can see, the user can be authenticated in different ways:
Username and Password
Username and Password + One-Time Password
Each authentication method maps to a specific JAX-RS Endpoint. The table bellow lists those endpoints and which authentication method they support:
JAX-RS Endpoint |
Authentication Method |
org.aerogear.todo.server.security.authc.SignInEndpoint |
Username and Password |
org.aerogear.todo.server.security.authc.otp.OTPSignInEndpoint |
One-Time Password |
org.aerogear.todo.server.security.authc.social.fb.FacebookSignInEndpoint |
Facebook oAuth2 |
org.aerogear.todo.server.security.authc.social.openid.OpenIDSignInEndpoint |
Google (OpenID) |
org.aerogear.todo.server.security.authc.social.twitter.TwitterSignInEndpoint |
Twitter oAuth2 |
To use the Facebook authentication you need to define some system properties firtst, as follows:
<system-properties> <property name="CLIENT_ID" value="CLIENT_ID"/> <property name="FB_CLIENT_ID" value="FB_CLIENT_ID"/> <property name="FB_CLIENT_SECRET" value="FB_CLIENT_SECRET"/> <property name="FB_RETURN_URL" value="http://localhost:8080/todo-server/facebook"/> </system-properties>
You can get the application client id and secret key from Facebook: https://developers.facebook.com/apps.
The TODO JAX-RS Endpoints can also be used by mobile clients. That said, we need to provide ways to check if a specific request was done by a previously authenticated user.
When an user is authenticated a PicketBox Session is created and associated with him. Sessions have a unique identifier that can be used to identify authenticated users.
All JAX-RS Endpoints are protected by a JAX-RS Interceptor. This interceptor tries to extract the token from the request headers and check if it maps to a valid user session. If the token is valid, the user's security context is restored and he can proceed with the request.
@ApplicationScoped @ServerInterceptor public class SecurityInterceptor implements PreProcessInterceptor { private static final String AUTH_TOKEN_HEADER_NAME = "Auth-Token"; @Inject private PicketBoxIdentity identity; @Override public ServerResponse preProcess(HttpRequest request, ResourceMethod method) throws Failure, WebApplicationException { ServerResponse response = null; if (requiresAuthentication(method) && !this.identity.isLoggedIn()) { boolean isLoggedIn = false; String token = getToken(request); if (token != null) { try { isLoggedIn = identity.restoreSession(token); } catch (AuthenticationException e) { } } if (!isLoggedIn) { AuthenticationResponse authcResponse = new AuthenticationResponse(); authcResponse.setLoggedIn(false); response = new ServerResponse(); response.setEntity(authcResponse); response.setStatus(HttpStatus.SC_FORBIDDEN); } } return response; } /** * <p>Retrieve the token from the request, if present.</p> * * @param request * @return */ private String getToken(HttpRequest request) { List<String> tokenHeader = request.getHttpHeaders().getRequestHeader(AUTH_TOKEN_HEADER_NAME); String token = null; if (tokenHeader != null && !tokenHeader.isEmpty()) { token = tokenHeader.get(0); } return token; } }
PicketBox CDI provides some useful annotations that can be used to put authorization constraints in methods and classes. Are they:
@RolesAllowed: Only allows invocations made by users with the specified roles
@UserLoggedIn: Only allows invocations made by authenticated user
For example, only users with the "admin" role can create projects:
@Stateless @Path("/projects") @TransactionAttribute public class ProjectEndpoint { @PersistenceContext private EntityManager em; @POST @Consumes(MediaType.APPLICATION_JSON) @Produces(MediaType.APPLICATION_JSON) @RolesAllowed ({"admin"}) public Project create(Project entity) { em.joinTransaction(); em.persist(entity); return entity; } ... }
The TODO application provides a self-registration service that allows users to register themselves. This is done by using the PicketBox Identity Management support, that allows you to inject a IdentityManager instance in any CDI bean and use it to perform IDM operations.
@Stateless @Path("/register") public class RegistrationEndpoint { @Inject private IdentityManager identityManager; /** * <p> * Creates a new user using the information provided by the {@link RegistrationRequest} instance. * </p> * * @param request * @return */ @POST @Consumes(MediaType.APPLICATION_JSON) @Produces(MediaType.APPLICATION_JSON) public RegistrationResponse register(final RegistrationRequest request) { RegistrationResponse response = new RegistrationResponse(); if (!validateUserInformation(request)) { response.setStatus("All fields are required."); return response; } if (isUsernameAlreadyInUse(request)) { User user = new SimpleUser(request.getUserName()); user.setFirstName(request.getFirstName()); user.setEmail(request.getEmail()); user.setLastName(request.getLastName()); this.identityManager.add(user); this.identityManager.updateCredential(user, new PlainTextPassword(request.getPassword())); Role roleGuest = new SimpleRole("guest"); this.identityManager.add(roleGuest); this.identityManager.grantRole(user, roleGuest); response.setStatus("Success"); } else { response.setStatus("This username is already in use. Choose another one."); } return response; } ... }
When an user chooses to authenticate using Facebook, for example, the information retrieved from the Facebook API can be used store the user locally by using the Identity Managament support.
Check the org.aerogear.todo.server.security.authc.social.fb.FacebookSignInEndpoint class to see how this is done.