5. JAX-RS with authentication

Besides JSF 'endpoints', Octopus can also be used to protect JAX-RS (REST) endpoints. There are 3 different types of usages related to JAX-RS and one of them is presented in this chapter.

It can be used when you have 2 (or more) applications created with Octopus and they need to retrieve data maintained by the other application. This is the typical scenario for a Self-Contained System where data is retrieved from another 'domain'.

The information about the logged in user in Application A will be transferred with the help of a JWT in the header, to Application B.

This type of applications is only supported with Java EE 7 as it uses JAX-RS 2.0 concepts for the communication.

5.1 Main scenario - Server

The example consists of 2 applications which work together and will be created from scratch.

This server part will have a JAX-RS endpoint which will be called by the client.

The code for this example can be found in the gitbook/ex5_srv directory of this Git Repository.

5.1.1 Create Maven Project

  • Go to a directory within the terminal where the project will be created.
  • Execute the Maven command to create an empty Java EE project.

        mvn archetype:generate -Dfilter=com.airhacks:javaee7-essentials-archetype
    
  • Select the archetype (number 1)

  • Select option 4 for Java8.
  • Use as artifactId ex5_srv (will be the name of the war file)
  • Open the created project in the IDE.

5.1.2 Add the dependencies

  • Add the repository containing the Octopus artefacts

      <repositories>
          <repository>
             <id>bintray_jcenter</id>
             <url>https://jcenter.bintray.com</url>
          </repository>
      </repositories>
    
  • Define the versions of the artefacts as Maven values (within properties section)

      <octopus.version>0.9.7</octopus.version>
      <deltaspike.version>1.7.0</deltaspike.version>
    
  • Add the Maven dependency for the JAX-RS server module

      <dependency>
          <groupId>be.c4j.ee.security.octopus.authentication</groupId>
          <artifactId>jwt-scs-server</artifactId>
          <version>${octopus.version}</version>
      </dependency>
    
  • Add the DeltaSpike dependencies

      <dependency>
          <groupId>org.apache.deltaspike.core</groupId>
          <artifactId>deltaspike-core-api</artifactId>
          <version>${deltaspike.version}</version>
          <scope>compile</scope>
      </dependency>
    
      <dependency>
          <groupId>org.apache.deltaspike.modules</groupId>
          <artifactId>deltaspike-security-module-api</artifactId>
          <version>${deltaspike.version}</version>
          <scope>compile</scope>
      </dependency>
    
      <dependency>
          <groupId>org.apache.deltaspike.core</groupId>
          <artifactId>deltaspike-core-impl</artifactId>
          <version>${deltaspike.version}</version>
          <scope>runtime</scope>
      </dependency>
    
      <dependency>
          <groupId>org.apache.deltaspike.modules</groupId>
          <artifactId>deltaspike-security-module-impl</artifactId>
          <version>${deltaspike.version}</version>
          <scope>runtime</scope>
      </dependency>
    

5.1.3 Define beans.xml

  • We need CDI Injection functionality, therefore it is good to define a beans.xml file within the web-app/WEB-INF directory to control the CDI behavior.

      <?xml version="1.0" encoding="UTF-8"?>
      <beans xmlns="http://xmlns.jcp.org/xml/ns/javaee"
             xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
             xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/beans_1_1.xsd"
             bean-discovery-mode="annotated">
      </beans>
    

5.1.4 Define secured URL pattern

  • Define those JAX-RS URLs which are protected. This can be done within the securedURLs.ini file which needs to be located within the webapp/WEB-INF directory.

      data/** = noSessionCreation, scs
    

5.1.5 Configure Octopus

  • Create the octopusConfig.properties file within the src/main/resources directory so that it will be added to the classpath.
  • Define the secret for the HMAC signing.

      jwt.hmac.secret=szxK-5_eJjs-aUj-64MpUZ-GPPzGLhYPLGl0wrYjYNVAGva2P0lLe6UGKGM7k8dWxsOVGutZWgvmY3l5oVPO3w
    

5.1.6 Implement SecurityDataProvider

  • Since every Octopus application needs a CDI bean implementing this interface, we need a dummy implementation.

      @Override
      public AuthenticationInfo getAuthenticationInfo(AuthenticationToken token) {
          return null;
      }
    
      @Override
      public AuthorizationInfo getAuthorizationInfo(PrincipalCollection principals) {
          return null;
      }
    
  • Annotate the class with the appropriate CDI RequestScoped

      @ApplicationScoped
    

5.1.7 Configure the JAX-RS endpoints

  • Create a class extending the Application JAX-RS class.

      @ApplicationPath("/data")
      public class RestApplication extends Application {
      }
    
  • Delete the file JAXRSConfiguration created by the maven archetype (or adjust it according to the previous item) *

    5.1.8 Create JAX-RS endpoint

  • Create the rest controller class.

      @Path("/hello")
      @javax.inject.Singleton
      public class HelloController {
    
  • We want to return JSON data, so the easiest way is to define a POJO class for this (this can be done as inner class)

      public static class Data {
          private String userName;
    
          public String getUserName() {
              return userName;
          }
    
          public void setUserName(String userName) {
              this.userName = userName;
          }
      }
    
  • Inject access to the currently active user.

      @Inject
      private UserPrincipal userPrincipal;
    
  • Define the method handling the JAX-RS request

      @Path("/user")
      @GET
      @Produces(MediaType.APPLICATION_JSON)
      public Data getUserData() {
          Data data = new Data();
          data.setUserName(userPrincipal.getUserName());
          return data;
      }
    
  • Define that the endpoint can only be called by users who have the permission demo

      @OctopusPermissions("demo")
    

5.2 Explanation server

By adding the artefact authentication:jwt-scs-server we add the capability to retrieve authentication/authorization information from the header when performing a JAX-RS call.

This information is packaged within a JWT (JSON Web Token) and signed (and optionally encrypted). The information within the JWT is used to supply the required information to Octopus regarding authentication and authentication.

The filter which is able to retrieve this information is called scs. That is why we add this filter to the securedURLs.ini file. Together with the noSessionCreation filter since we want that our JAX-RS rest endpoint behaves as a stateless resource. (It is also technical required to make other filters of Octopus behave correctly for JAX-RS endpoints)

Within the octopusConfig.properties file, we define the String (actually used as a byte array) which will be used as cryptographic key within the signing process. The technical explanation can be read on wikipedia

At the server side, we only need to specify the secret, the algorithm is defined on the client side (and available within the JWT header)

Since every Octopus Application needs a CDI bean implementing the SecurityDataProvider interface, we need to specify it here also. Although we don't need it in this example/demo since it only contains a JAX-RS endpoint which uses the header of the call for this information. So the methods can just return null.

Creating the JAX-RS endpoint (controller) itself is nothing Octopus specific. We create a class which extends javax.ws.rs.core.Application and where we define the URL path by means of the @ApplicationPath.

The actual code is then defined in a class marked with the @Path and a method with the @GET annotations. Since the result of the call to this endpoint will be processed by another method, it is best that the communication is performed by using JSON.

The only Octopus specific thing here is that we define that only users who have the permission demo within the original application can execute this endpoint. This is done by specifying the @OctopusPermissions annotation.

5.3 Main scenario - Client

The client application is the second half of this example. It is a web application which also calls the endpoint we have just defined within the server application.

The code for this example can be found in the gitbook/ex5 directory of this Git Repository.

5.3.1 Create Maven Project

  • Go to a directory within the terminal where the project will be created.
  • Execute the Maven command to create an empty Java EE project.

        mvn archetype:generate -Dfilter=com.airhacks:javaee7-essentials-archetype
    
  • Select the archetype (number 1)

  • Select option 4 for Java8.
  • Use as artifactId ex5 (will be the name of the war file)
  • Open the created project in the IDE.

5.3.2 Add the dependencies

  • Add the repository containing the Octopus artefacts

      <repositories>
          <repository>
             <id>bintray_jcenter</id>
             <url>https://jcenter.bintray.com</url>
          </repository>
      </repositories>
    
  • Define the versions of the artefacts as Maven values (within properties section)

      <octopus.version>0.9.7</octopus.version>
      <deltaspike.version>1.7.0</deltaspike.version>
      <primefaces.version>6.0</primefaces.version>
    
  • Add the Maven dependency for the JAX-RS client module

      <dependency>
          <groupId>be.c4j.ee.security.octopus.authentication</groupId>
          <artifactId>jwt-scs-client</artifactId>
          <version>${octopus.version}</version>
      </dependency>
    
  • Add the Maven dependency for the Java EE 7 JSF application

      <dependency>
          <groupId>be.c4j.ee.security.octopus</groupId>
          <artifactId>octopus-javaee7-jsf</artifactId>
          <version>${octopus.version}</version>
      </dependency>
    
  • Add the Maven dependency for PrimeFaces

      <dependency>
          <groupId>org.primefaces</groupId>
          <artifactId>primefaces</artifactId>
          <version>${primefaces.version}</version>
      </dependency>
    
  • Add the Deltaspike dependencies

      <dependency>
          <groupId>org.apache.deltaspike.core</groupId>
          <artifactId>deltaspike-core-api</artifactId>
          <version>${deltaspike.version}</version>
          <scope>compile</scope>
      </dependency>
    
      <dependency>
          <groupId>org.apache.deltaspike.modules</groupId>
          <artifactId>deltaspike-security-module-api</artifactId>
          <version>${deltaspike.version}</version>
          <scope>compile</scope>
      </dependency>
    
      <dependency>
          <groupId>org.apache.deltaspike.core</groupId>
          <artifactId>deltaspike-core-impl</artifactId>
          <version>${deltaspike.version}</version>
          <scope>runtime</scope>
      </dependency>
    
      <dependency>
          <groupId>org.apache.deltaspike.modules</groupId>
          <artifactId>deltaspike-security-module-impl</artifactId>
          <version>${deltaspike.version}</version>
          <scope>runtime</scope>
      </dependency>
    

5.3.3 Define web.xml

The same as in 1.1.3 Define web.xml.

5.3.4 Define beans.xml

  • We need CDI Injection functionality, therefore it is good to define a beans.xml file within the web-app/WEB-INF directory to control the CDI behavior.

      <?xml version="1.0" encoding="UTF-8"?>
      <beans xmlns="http://xmlns.jcp.org/xml/ns/javaee"
             xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
             xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/beans_1_1.xsd"
             bean-discovery-mode="annotated">
      </beans>
    

5.3.5 Define secured URL pattern

  • Define those URLs which are protected. This can be done within the file securedURLs.ini which needs to be located within the webapp/WEB-INF directory.

      pages/** = user
    

5.3.6 Configure Octopus

  • Create the octopusConfig.properties file within the src/main/resources directory so that it will be added to the classpath.
  • Define the secret for the HMAC signing.

      jwt.hmac.secret=szxK-5_eJjs-aUj-64MpUZ-GPPzGLhYPLGl0wrYjYNVAGva2P0lLe6UGKGM7k8dWxsOVGutZWgvmY3l5oVPO3w
    
  • Define the algorithm for the signing

      jwt.algorithms=HS256
    

5.3.7 Define JSF pages

The same as in 1.1.5 Define JSF pages

5.3.8 Define the login page

The same as in 1.1.6 Define the login page

5.3.9 Implement SecurityDataProvider

  • Create a CDI bean called ApplicationSecurityData which implements SecurityDataProvider.

  • Implement the getAuthenticationInfo method to supply the authentication information.

      private int principalId = 0;
    
      @Override
      public AuthenticationInfo getAuthenticationInfo(AuthenticationToken authenticationToken) {
          if (authenticationToken instanceof UsernamePasswordToken) {
              UsernamePasswordToken usernamePasswordToken = (UsernamePasswordToken) authenticationToken;
    
              AuthenticationInfoBuilder authenticationInfoBuilder = new AuthenticationInfoBuilder();
              authenticationInfoBuilder.principalId(principalId++).name(authenticationToken.getPrincipal().toString());
              authenticationInfoBuilder.userName(authenticationToken.getPrincipal().toString());
              // TODO: Change for production. Here we use username as password
              authenticationInfoBuilder.password(usernamePasswordToken.getUsername());
    
              return authenticationInfoBuilder.build();
          }
    
          return null;
      }
    
  • Implement the getAuthorizationInfo method.

      @Override
      public AuthorizationInfo getAuthorizationInfo(PrincipalCollection principalCollection) {
          AuthorizationInfoBuilder builder = new AuthorizationInfoBuilder();
          UserPrincipal principal = (UserPrincipal) principalCollection.getPrimaryPrincipal();
          if ("admin".equalsIgnoreCase(principal.getUserName())) {
              builder.addPermission("demo:*:*");
          }
          return builder.build();
      }
    
  • Don't forget the CDI scope.

      @ApplicationScoped
    

5.3.10 Create CDI managed bean for screen

  • The CDI RequestScoped and Named bean DemoBean will provide some data to our test screen.

  • Create a method to visualize the JWT token send to the server. (not needed in normal application usage)

      @Inject
      private JWTUserToken jwtUserToken;
    
      public String getToken() {
          return jwtUserToken.createJWTUserToken(null, null);
      }
    
  • Create a method to retrieve the result from the endpoint.

      @Inject
      private OctopusSCSUserRestClient octopusSCSUserRestClient;
    
      public String getData() {
    
          Data data;
          try {
              data = octopusSCSUserRestClient.get("http://localhost:8080/ex5_srv/data/hello/user", Data.class);
          } catch (OctopusUnauthorizedException e) {
              data = new Data();
              data.setUserName(e.getMessage());
          }
          return data.getUserName();
      }
    
  • Create the inner class for the Data

Same class as defined in 5.1.8 but need to copy the declaration here also as an inner class.

5.3.11 Show the page with results

  • Update the JSF page within the directory /pages named user.xhtml with the following content.

      <!DOCTYPE html>
    
      <html xmlns="http://www.w3.org/1999/xhtml"
            xmlns:h="http://xmlns.jcp.org/jsf/html"
      >
    
      <h:head>
          <title>Octopus cookbook</title>
    
      </h:head>
    
      <h:body>
    
          <h:form>
              authenticated user : #{loggedInUser}
              <h:commandLink value=" Logout" actionListener="#{loginBean.logout}"
                             action="/index.xhtml"/>
              <br/>
    
              token = #{demoBean.token}
              <br/>
              Username from Rest : #{demoBean.data}
          </h:form>
      </h:body>
    
      </html>
    

5.3.12 Test the application

  • Deploy the application on a Java EE 7 compliant application server capable of running on Java 8.
  • Open the browser pointing to the following URL : http:///ex5/index.xhtml
  • Click on the user page link
  • Use the admin user for testing.
  • The user page should show admin as the username detected by the REST endpoint.
  • Click on the logout button and the user page link again (to test with another user)
  • Try with another username, like test
  • The page should show now an error reported by REST endpoint.

5.4 Explanation

By adding the artefact authentication:jwt-scs-client we add the capability to send authentication/authorization information to the endpoint. This allows us to transfer the security information of the user to other applications.

The value we specify for the parameter jwt.hmac.secret must match the one we have defined on the server side. Only when these 2 values are the same on both sides, the information within the JWT will be accepted. If not, an unauthorized response (HTTP header status 401) will be returned.

On the client side, we also need to specify which algorithm will be used within the signing. We didn't specify it on the server size as this algorithm will be placed within the header of the JWT. And thus the server is capable of knowing this value without the need to define it as a parameter.

In this example, we use the SHA-256 hashing algorithm, hence the parameter is set to HS256. See ??? for a list of supported values.

In the managed bean DemoBean we access the JAX-RS endpoint by using a specific Rest client. We inject this by retrieving an instance of the class OctopusSCSUserRestClient. This class has methods to perform the various Rest methods. There is also a method for retrieving some values with a GET method.

The special thing about this Rest client is that within the header, a JWT is placed with all the information about the user who calls the method. This information is used by the server side to create the required security context information (authentication and authorization information)

That way, the security information can effectively be transferred between applications.

If you want to have a look at which information is transferred, do a base64 decode of the text between the . (dots) within the token which is shown on the screen. (We just asked for a signing, not encryption. That is the reason why the info is clearly readable. But it is secure enough since the signing makes sure it can't be altered)

    {  
       "sub":{  
          "permissions":[  
             "demo:*:*"
          ],
          "roles":[  

          ],
          "name":"admin",
          "namedPermissions":{  

          },
          "id":1,
          "userName":"admin"
       },
       "exp":1511987309,
       "iat":1511987307
    }

5.5 Where to go context

  • Learn about encrypting the JWT in the alternatives 5.6
  • Learn about using tokens and basic authentication for protecting JAX-RS endpoints at ???.
  • Learn about customizations which can be performed in this process at ???.
  • Learn about all parameters of the jwt-scs module at ???

5.6 Alternatives

results matching ""

    No results matching ""