JBoss.orgCommunity Documentation

Chapter 13. Errai Security

13.1. Basic Model
13.2. Getting Started
13.2.1. Making Users
13.2.2. Authentication from the Client
13.3. RestrictedAccess
13.3.1. Simple Roles as Strings
13.3.2. Provided Roles
13.3.3. RPC Services
13.3.4. Page Navigation
13.3.5. Hiding UI Elements
13.4. Form Based Login
13.5. Using an Alternative to PicketLink
13.6. Using Keycloak for Authentication
13.6.1. How It Works (Overview)
13.6.2. Setup

Errai Security provides a lightweight security API for declaring RPC services and client-side UI elements which require authentication or authorization.

Plugin Tip

Use the Errai Forge Addon Add Errai Features command and select Errai Security to follow along with this section.

Manual Setup

Checkout the Manual Setup Section for instructions on how to manually add Errai Security to your project.

Errai Security provides two main concepts:

By default the server-side Errai Security module uses PicketLink for authentication. Later on we will explain how to use an alternative backend.

The simplest way to begin experimenting with Errai Security is to add Users and Roles to PicketLink programmatically. Here is some sample server-side code from the Errai Security Demo.

@Singleton

@Startup
public class PicketLinkDefaultUsers {
  @Inject
  private PartitionManager partitionManager; 1
  /**
   * <p>Loads some users during the first construction.</p>
   */
  @PostConstruct
  public void create() {
    final IdentityManager identityManager = partitionManager.createIdentityManager();
    final RelationshipManager relationshipManager = partitionManager.createRelationshipManager();
    User john = new User("john");
    john.setEmail("john@doe.com");
    john.setFirstName("John");
    john.setLastName("Doe");
    User hacker = new User("hacker");
    hacker.setEmail("hacker@illegal.ru");
    hacker.setFirstName("Hacker");
    hacker.setLastName("anonymous");
    identityManager.add(john); 
2
    identityManager.add(hacker);
    final Password defaultPassword = new Password("123");
    identityManager.updateCredential(john, defaultPassword);
    identityManager.updateCredential(hacker, defaultPassword);
    Role roleDeveloper = new Role("simple");
    Role roleAdmin = new Role("admin");
    identityManager.add(roleDeveloper);
    identityManager.add(roleAdmin);
    relationshipManager.add(new Grant(john, roleDeveloper)); 
3
    relationshipManager.add(new Grant(john, roleAdmin));
  }
}

Here are the important things that are happening here:

1

PicketLink uses the concept of partitions, which are sections that can contain different users and roles. What we really need to make users and roles are the IdentityManager and RelationshipManager, but these objects are @RequestScoped so in order to access them when the application starts we must @Inject the PartitionManager.

2

Here we add are new users to the IdentityManager. It is also used below to give passwords to the new users, and to add the simple and admin roles.

3

The RelationshipManager defines relationships between entities. In this case, it is used to specify that a user belongs to a role.

Once you’ve created some users and roles, you’re ready to write some client-side code. Authentication is performed with the org.jboss.errai.security.shared.service.AuthenticationService via Errai RPC.

Here is some sample code involving the user john from the previous Security Demo excerpt.

The annotation @RestrictedAccess is the only annotation necessary to secure a resource or UI element. In general, @RestrictedAccess blocks a resource from users who are either not logged in or who lack required roles. Roles are defined through the @RestrictedAccess annotation in one of the following two ways.

To secure an Errai RPC service, simply annotate the RPC interface (either the entire type or just a method) with one of the security annotations.

For example:

When access to a secured RPC service is denied an UnauthenticatedException or UnauthorizedException is thrown. This error is then transmitted back to the client, where it can be caught with an ErrorCallback (provided when the RPC is invoked).

Here is how we would invoke the previous MixedService example with error handling:

MessageBuilder.createCall(new RemoteCallback<Void>() {


    @Override
    public void callback(Void response) {
      // ...
    }
  }, new ErrorCallback<Message>() { 
1
    @Override
    public boolean error(Message message, Throwable t) {
      if (instanceof UnauthenticatedException) {
        // User is not logged in.
        return false;
      }
      else if (instanceof UnauthorizedException) {
        // User is logged in but lacked sufficient roles.
        return false;
      }
      else {
        // Some other error has happened. Let it propogate.
        return true;
      }
    }
  }, MixedService.class).adminService();

1

This ErrorCallback is parameterized with the type Message because it is an Errai Bus RPC. In the next section we will demonstrate the use of a JAX-RS RPC.

JAX-RS RPCs are secured exactly as bus RPCs. Here is the first example from the previous section, but converted to use JAX-RS instead of the Errai Bus.

@Path("/rest-endpoint")

@RestrictedAccess
public interface UserOnlyStuff {
  @Path("/some-method")
  @GET
  public void someMethod();
  @Path("/other-method")
  @GET
  public void otherMethod();
}

There are two important differences when calling a secured JAX-RS RPC (in contrast to an Errai Bus RPC):

Because there is no global error-handling, you should always pass a RestErrorCallback when using a JAX-RS RPC. Errai provides the DefaultRestSecurityErrorCallback that provides the same default behaviour as the DefaultBusSecurityErrorCallback mentioned above. It can also optionally wrap a provided callback as demonstrated below:

Any class annotated with @Page can also be marked with @RestrictedAccess. By doing so, users will be prevented from navigating to the given page if they are not logged in or lack authorization.

Here are two simple examples:

Security checks performed before page navigation do not use any RPC calls, but are instead performed from a cached (in-memory) instance of the org.jboss.errai.security.shared.api.identity.User. This prevents the possibility of lengthy delays between page navigation while waiting for RPC return values.

But the drawback is that any attempts to navigate to a secured @Page before the cache is populated will result in redirection to the LoginPage — even if the user is in fact logged in.

In practice, this is only likely to happen if a user starts an Errai app with a URL to a secure page while still logged in on the server from a previous session.

One option offered by Errai is to persist the org.jboss.errai.security.shared.api.identity.User object in a cookie. This can be done by adding the following to ErraiApp.properties:

errai.security.user_cookie_enabled=true

With this option enabled the User will be persisted in a browser cookie, which is loaded quickly enough to avoid the described navigation issue. This feature can also be used to allow an application to work offline, or allow the server to log in a user on an initial page request.

If you do not wish to use this feature you will likely want to handle this case in the @PageShowing method of your LoginPage. Here is an outline of what you might want to do:

@Page(role = LoginPage.class)

@Templated
public class ExampleLoginPage extends Composite {
  @Inject
  private SecurityContext securityContext;
  @Inject
  private Caller<AuthenticationService> authService;
  @Inject
  @DataField
  private Label status;
  @PageShowing
  public void checkForPendingCache() {
    // Check if cache is invalid.
    if (!securityContext.isUserCacheValid()) {
      // Update the status.
      status.setText("loading...");
      // Force cache to update by calling getUser
      authService.call(new RemoteCallback<User> {
        @Override
        public void callback(User user) {
          /* An interceptor will have updated the cache by now.
             So check if we are logged in and redirect if necessary.
          */
          if (!user.equals(User.ANONYMOUS)) {
            /* This is a special transition that takes us back to
               a secure page from which we were redirected. */
            securityContext.navigateBackOrHome();
          }
          else {
            status.setText("You are not logged in.");
          }
        }
      }).getUser();
    }
  }
}

If you do enable the Errai Security cookie, it is possible to use a form-based login from outside your GWT/Errai app. The errai-security-server jar contains a servlet filter for encoding the currently authenticated user as a cookie in the http response. Here are the steps for setting this up:

All Errai Security authentication is implemented with Errai Remote Procedure Calls to the AuthenticationService. A default implementation of this interface using PicketLink is provided in the errai-security-picketlink jar. But it is possible to use a different sever-side security framework by providing your own custom implementation of AuthenticationService and annotating it with @Service. In that case your project should not depend on errai-security-picketlink.

Keycloak is is a new project that provides integrated SSO and IDM for browser apps and RESTful web services. By using Keycloak it is possible to outsource the responsibility of authentication and account management from your application entirely. Errai Security provides an optional errai-security-keycloak jar that provides an implementation of the AuthenticationService that works with Keycloak.

To start from scratch and add Keycloak integration to your application:

  • Setup a Keycloak server. Please consult the Keycloak documentation for details on how this is achieved.
  • Start the Keycloak server.
  • Go to the Keycloak Administrative Console (i.e. http://localhost:8080/auth/admin/) (the username and password are both admin on first use).
  • Click Add Realm and create a custom realm for your application.
  • Select the Clients tab and click Create, then fill in the following to add the client application to this realm:

    • Client ID: the name of your client application (i.e. errai-security-demo)
    • Access Type: public
    • Redirect URI: the url of your application (i.e. http://localhost:8080/[your-application]/*)
  • After saving your application, choose the new application in the menu and make sure the following are set:

    • In the Roles tab add your custom roles.
    • In the Installation tab choose the format option keycloak.json and copy the contents in your WEB-INF/keycloak.json file.
  • Click on Users on the side-panel to add a user:

    • Fill out the Username, Email, First Name, and Last Name with any values.
    • After saving go to the Credentials tab and set a password.
    • Go to the Role Mappings tab. Add at least one role to the Assigned Roles for your application (scroll down to Application Roles and select your application to do this).
  • Add the errai-security-keycloak jar to your project and make sure it’s being deployed to the server. In maven, the dependency is org.jboss.errai:errai-security-keycloak.
  • Configure the ErraiUserCookieFilter in your web.xml. All that is necessary is adding a filter-mapping for your GWT host page like so:

    
      <filter-mapping>
        <filter-name>ErraiUserCookieFilter</filter-name>
        <url-pattern>/index.html</url-pattern>
      </filter-mapping>
  • Configure the ErraiLoginRedirectFilter in your web.xml.

    • Create a filter-mapping of this filter onto a path that will act as a url to the Keycloak login page. For example, if your deployed app is called my-app and you wanted <server-uri>/my-app/app-login as your login url then you would add the following:

      
        <filter-mapping>
          <filter-name>ErraiLoginRedirectFilter</filter-name>
          <url-pattern>/app-login</url-pattern>
        </filter-mapping>
    • Add a security-constraint to login url. This is what actually causes the redirection to Keycloak. All the filter does is redirect back to your app (which happens after the login completes). For the previous example, the constraint would look like this:

      
        <security-constraint>
          <web-resource-collection>
            <web-resource-name>Login</web-resource-name>
            <url-pattern>/app-login</url-pattern>
          </web-resource-collection>
          <auth-constraint>
            <role-name>*</role-name>
          </auth-constraint>
        </security-constraint>
    • Optionally configure the URL that the ErraiLoginRedirectFilter redirects to. You can do this with the redirectLocation param, which takes a path relative to the app context:

      
        <filter>
          <filter-name>ErraiLoginRedirectFilter</filter-name>
          <init-param>
            <param-name>redirectLocation</param-name>
            <param-value>/index.jsp</param-value>
          </init-param>
        </filter>
  • Set the login method to use Keycloak in you web.xml:

    
      <login-config>
        <auth-method>KEYCLOAK</auth-method>
        <realm-name>[your-realm-name]</realm-name>
      </login-config>
  • Add roles available to users in your application to the web.xml. Here is an example declaration of a "user" role:

    
      <security-role>
        <role-name>user</role-name>
      </security-role>

All users must have at least one role

With this configuration all users must have at least a single role, or else they will not be redirected propertly. Unfortunately, there is no way to define a security-constraint that only requires authentication. The simplest solution is to add a default role to your realm.