1. Simple JSF Application
In this chapter, we create a simple Java EE 7 JSF application which uses Octopus to provide the security. The goal is to learn the different parts of Octopus needed for securing a JSF application.
The first step only does the authentication of your end user, determining who he or she is.
1.1 Main scenario
The code for this example can be found in the gitbook/ex1 directory of this Git Repository.
1.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 ex1 (will be the name of the war file)
- Open the created project in the IDE.
1.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> <primefaces.version>6.0</primefaces.version>
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>
1.1.3 Define web.xml
Since we like the .xhtml extension for JSF pages, create a web.xml (within webapp/WEB-INF directory) file with the following contents.
<?xml version="1.0" encoding="UTF-8" standalone="no"?> <web-app 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/web-app_3_1.xsd" version="3.1"> <servlet> <servlet-name>Faces Servlet</servlet-name> <servlet-class>javax.faces.webapp.FacesServlet</servlet-class> <load-on-startup>1</load-on-startup> </servlet> <servlet-mapping> <servlet-name>Faces Servlet</servlet-name> <url-pattern>*.xhtml</url-pattern> </servlet-mapping> </web-app>
1.1.4 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
1.1.5 Define JSF pages
The index.xhtml, which we define within the root (create the file within the webapp directory), it is publicly, anonymously accessible.
<!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> Hello World <a href="pages/user.xhtml">User page</a> </h:body> </html>
Define the protected user page (user.xhtml) within the directory webapp/pages.
<!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> authenticated user : #{loggedInUser} </h:body> </html>
1.1.6 Define the login page
We will define the login page used by Octopus when user credentials are required. Filename login.xhtml within the webapp directory.
<!DOCTYPE html > <html xmlns="http://www.w3.org/1999/xhtml" xmlns:h="http://java.sun.com/jsf/html" xmlns:p="http://primefaces.org/ui"> <h:head> <title>Octopus cookbook Login</title> </h:head> <h:body> <h2>Login</h2> <h:form id="login"> <h:panelGrid columns="2"> <p:outputLabel for="username" value="Username:"/> <p:inputText id="username" value="#{loginBean.username}" required="true"/> <p:outputLabel for="password" value="Password:"/> <p:password id="password" value="#{loginBean.password}" required="true" feedback="" /> <h:panelGroup/> <p:commandButton value="Login" actionListener="#{loginBean.doLogin}" update="@form" process="@form"/> </h:panelGrid> <p:messages /> </h:form> For this example, password is the same as username and any username will be accepted. </h:body> </html>
1.1.7 Define Octopus data
Octopus asks the authentication and authorization information through a CDI bean implementing the be.c4j.ee.security.realm.SecurityDataProvider interface. Create the be.atbash.ee.security.octopus.book.ex1.ApplicationSecurityData class with the following contents.
package be.atbash.ee.security.octopus.book.ex1; import be.c4j.ee.security.realm.AuthenticationInfoBuilder; import be.c4j.ee.security.realm.SecurityDataProvider; import org.apache.shiro.authc.AuthenticationInfo; import org.apache.shiro.authc.AuthenticationToken; import org.apache.shiro.authc.UsernamePasswordToken; import org.apache.shiro.authz.AuthorizationInfo; import org.apache.shiro.subject.PrincipalCollection; import javax.enterprise.context.ApplicationScoped; @ApplicationScoped public class ApplicationSecurityData implements SecurityDataProvider { 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()); // TODO: Change for production. Here we use username as password authenticationInfoBuilder.password(usernamePasswordToken.getUsername()); return authenticationInfoBuilder.build(); } return null; } @Override public AuthorizationInfo getAuthorizationInfo(PrincipalCollection principalCollection) { return null; } }
1.1.8 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://
/ex1/index.xhtml - Click on the user page link
- Fill in any username and password information in the login page (username must be equals to password)
- Click on the Login button
- See if you end up on the user page and see your username displayed here.
1.2 Explanation
1.2.1 Maven project management
This example uses maven to build the WAR file and specify the different dependencies.
However, Maven is not required to use the Octopus functionality. Other build tools like Gradle or even without any tools, the framework can be used in your application.
The archetype from com.airhacks creates a minimal Java EE 7 based web application which uses Java 8. (although Java 8 is not a requirement of Octopus)
1.2.2 Maven dependencies
The Octopus Maven dependency for Octopus is available for the moment at the JCenter Bintray servers, located at https://jcenter.bintray.com
Since we are creating a simple JSF application, which will be deployed on a Java EE 7 compliant application server, we use the octopus-javaee7-jsf dependency.
Octopus has transient dependencies on some other artefacts. DeltaSpike is also one of the frameworks on which Octopus is built on top. But these dependencies are deliberately set to provided so that it is easier to define your own version.
1.2.3 JSF Url configuration
Within the web.xml we can define the URL pattern to which we are linking the JSF framework.
In this example, we use the .xhtml suffix as a pattern. This makes it easier to protect the source files for the JSF views as we don't need to indicate that they shouldn't be served by the server.
But of course, you are free here to define the best practices or company rules you adhere.
1.2.4 secured URLs
Octopus will use the information within the securedURLs.ini file to determine which URLs are protected.
In this example, we define that all views (URLs) in the pages directory (or subdirectory) requires a user authentication.
All other pages (other subdirectories or the root) will be accessible anonymously.
1.2.5 JSF pages
The index.xhtml page will be accessible by anyone and just contains a link to a protected view within that directory pages which we specified earlier.
On that user page, we show the name of the user which has logged in. As we will see in a next paragraph, we set the username which the end user specified as the name of the authenticated user.
The EL expression #{loggedInUser} is defined by Octopus and always contains the name of the authenticated user.
See the explanation of the getAuthenticationInfo() method on how we can set the name of the authenticated user.
1.2.6 Login page
This login page is automatically called by Octopus when it detects that access to a page requires an authenticated user.
By default, this page must be called login.xhtml and be located within the root of the application. (and is therefor placed within the webapp directory)
The structure, layout, and design are entirely up to the developer. There are only 3 requirements that must be fulfilled to be able to be used by Octopus.
The field used for entering the username must be linked to the userName property of the JSF (or CDI) bean called loginBean. So we are using the EL expression #{loginBean.userName}.
For the password, we need to link it to the password property of the same bean. So we use the EL expression #{loginBean.password}.
The button which is used to send the data to the server must be linked to the doLogin() method of again the same bean. In the example, we used the EL expression #{loginBean.doLogin} as EL expression within the actionListener Property of the PrimeFaces command button. As you can see, AJAX is allowed for this use case.
1.2.7 Supply data to Octopus
Octopus itself has no code or features for retrieving the authentication and authorization data it must use. A CDI bean implementing the be.c4j.ee.security.realm.SecurityDataProvider interface must be provided by the developer.
In our example, we created the class ApplicationSecurityData for this purpose. We have the annotation @ApplicationScoped to make it a CDI bean and have implemented the 2 methods.
Since we have no authorization requirements for this example, we just return null for the getAuthorizationInfo() method.
For the getAuthenticationInfo() method, we need to return something different from null. Otherwise, Octopus will decide that the credentials are invalid without performing any other check.
The method has a parameter with an AuthenticationToken which the user supplied to identify itself. This can be a username - password combination but it can also be a token derived from an OAuth2 token. (see ???)
To build the return value of the method, you can make use of a builder which should make it easier to supply all the values octopus need.
Some info on the required data
- principalId must be a unique identification for the user. it will be used to ask for the authorization information for that user (here not used)
- name is the descriptive text Octopus can use to indicate the authenticate user (instead of his username)
- password is the password what you expect the user entered as part of his credentials.
the userName is handy in case you want to base the authorization information on this value, otherwise, the principalId will do.
1.3 Where to go next
- Try the alternatives (see 1.4) of this example to learn about the more advanced use cases.
- Learn about the steps in case you are using Gradle (see ???)
- Learn about the usage of a Maven BOM for a more opinionated setup of your web application. (See ??)
- Learn about all the options for securing the URLs (See ???)
- Check out the example 2 to see how authorization can be used with Octopus.
- Learn about the different authorization schemes supported by Octopus in the section ???.
- Look at the Appendix ?? for all the options related to the filters which you can use in the securedURLs.ini file.
- Look at the Appendix ?? for a technical explanation about the AuthenticationInfoBuilder.
1.4 Alternatives
1.4.1 Custom location for securedURLs.ini
The file name where the URL patterns are read can be customized with the configuration file of Octopus, octopusConfig.properties.
For this alternative scenario, we assume that we keep the default location and name of the Octopus configuration file (you can specify also another file then octopusConfig.properties). See also ??? for additional information on the octopusConfig.properties file.
You can start with a copy of the example 1 code created in the main scenario described above.
- Create the octopusConfig.properties file within the src/main/resources directory so that it will be added to the classpath.
Define the value for the securedURLs.file parameter
securedURLs.file=/WEB-INF/URLs.ini
Rename the securedURLs.ini file to URLs.ini.
Test the application again.
Additional info
When you don't specify a prefix, see further on, the value is assumed to be a servlet context resource location. Or in other words, the file is searched from the webapps directory onwards in a maven setup.
You can also place the ini file with the URL protection info on the classpath, for example within the src/main/resources directory of your maven project. In that case, you need to use the prefix classpath: in front of the file name.
1.4.2 Custom file for the login page.
By default, Octopus uses the login.xhtml page in the root of the web application when it needs to asks the user credentials.
This alternative scenario shows you how you can specify another name for this login file.
You can start with a copy of the example 1 code, or from alternative 1, created above.
- Create the octopusConfig.properties file, in case it doesn't already exist, within the src/main/resources directory so that it will be added to the classpath.
- Define the value for the loginPage parameter
loginPage=/signin.xhtml
Rename the login.xhtml file to signin.xhtml. and adjust the h1 title.
Test the application again.
Additional info
It is important here that the URL starts with a forward slash so that the redirect will to the correct contextRoot.
Another important thing to note here is the location of the file. By default, Octopus allows all access to files in the root of the application to be accessed anonymously. And it is important that the login page is accessible by users which aren't authenticated yet.
If needed, you can add the location of the login page to the securedURLs.ini and indicate it is accessible anonymously like this:
/some/path/to/login.xhtml = anon
1.4.3 Alternative name for the loginBean
For the logic behind the login page, a CDI bean is provided by Octopus. By default, the CDI name of this bean is loginBean. That is also the reason why we used the EL expression #{loginBean.userName} on the login page.
But there is an option to change this name so that it is in line with the naming conventions of your company, project, etc ...
You can start with a copy of the example 1 code, or from alternative 1, created above.
- Create the octopusConfig.properties file, in case it doesn't already exists, within the src/main/resources directory so that it will be added to the classpath.
- Define the value for the aliasNameLoginBean parameter
aliasNameLoginBean=myLoginView
Change the references to the bean within the login page to the new name myLoginView.
Test the application again.
Additional info
By specifying the parameter, the framework adds another bean definition for the loginBean with the alternative name.
This means that the original bean definition, with the name loginBean, is still available but we can also use the other, alternative name.