Securing a web application

duration 15 minutes
Git clone to get going right away:
git clone https://github.com/OpenLiberty/guide-security-intro.git
Copy Github clone command

Learn how to secure a web application through authentication and authorization.

What you’ll learn

You’ll learn how to secure a web application by performing authentication and authorization using Java EE Security. Authentication confirms the identity of the user by verifying a user’s credentials while authorization determines whether a user has access to restricted resources.

Java EE Security provides capability to configure the basic authentication, form authentication or custom form authentication mechanism by using annotations in servlets. It also provides SecurityContext API for programmatic security checks in application code.

You’ll implement form authentication for a simple web frontend. You’ll also learn to specify security constraints for a servlet and use the SecurityContext API to determine the role of a logged in user.

Getting started

The fastest way to work through this guide is to clone the Git repository and use the projects that are provided inside:

git clone https://github.com/openliberty/guide-security-intro.git
cd guide-security-intro

The start directory contains the starting project that you will build upon.

The finish directory contains the finished project that you will build.

Try what you’ll build

The finish directory in the root of this guide contains the finished application secured with form authentication. Feel free to give it a try before you proceed.

To try out the application, first navigate to the finish directory and then run the following Maven goals to build the application and run it inside Open Liberty:

mvn install liberty:start-server

Navigate your browser to this URL to access the application: http://localhost:9080

The application automatically switches from an HTTP connection to a secure HTTPS connection and forwards you to a login page. If the browser gives you a certificate warning, it’s because the Liberty server created a self-signed SSL certificate by default. You may follow your browser’s provided instructions to accept the certificate and continue.

Sign into the application with one of the following user credentials from the user registry, which have been provided to you:

UsernamePasswordRoleGroup

alice

alicepwd

user

Employee

bob

bobpwd

admin, user

Manager, Employee

carl

carlpwd

admin, user

TeamLead, Employee

dave

davepwd

N/A

PartTime

Notice that when you sign in as Bob or Carl, the browser redirects to the admin page and you can view their names and roles. When you sign in as Alice, you can only view Alice’s name. When you sign in as Dave, you’ll be blocked and will see a Error 403: Authorization failed message because Dave doesn’t have a role supported by the application.

Once you’re done checking out the application, log out and stop the Open Liberty server:

mvn liberty:stop-server

Adding authentication and authorization

For this application, users will be asked to login with a form when they access the application. Users are authenticated and depending on their roles, they will be redirected to the pages that they are authorized to access. If authentication or authorization failed, users will be sent to an error page. The application supports two roles, admin and user.

Navigate to the start directory to begin.

Create the HomeServlet class.
src/main/java/io/openliberty/guides/ui/HomeServlet.java

HomeServlet.java

 1package io.openliberty.guides.ui;
 2
 3import java.io.IOException;
 4import javax.inject.Inject;
 5import javax.security.enterprise.SecurityContext;
 6import javax.security.enterprise.authentication.mechanism.http.FormAuthenticationMechanismDefinition;
 7import javax.security.enterprise.authentication.mechanism.http.LoginToContinue;
 8import javax.servlet.ServletException;
 9import javax.servlet.annotation.HttpConstraint;
10import javax.servlet.annotation.ServletSecurity;
11import javax.servlet.annotation.WebServlet;
12import javax.servlet.http.HttpServlet;
13import javax.servlet.http.HttpServletRequest;
14import javax.servlet.http.HttpServletResponse;
15
16@WebServlet(urlPatterns = "/home")
17@FormAuthenticationMechanismDefinition(
18    loginToContinue = @LoginToContinue(errorPage = "/error.html",
19                                       loginPage = "/welcome.html"))
20@ServletSecurity(value = @HttpConstraint(rolesAllowed = { "user", "admin" },
21  transportGuarantee = ServletSecurity.TransportGuarantee.CONFIDENTIAL))
22public class HomeServlet extends HttpServlet {
23
24    private static final long serialVersionUID = 1L;
25
26    @Inject
27    private SecurityContext securityContext;
28
29    /**
30     * @see HttpServlet#doGet(HttpServletRequest request, HttpServletResponse
31     *      response)
32     */
33    protected void doGet(HttpServletRequest request, HttpServletResponse response)
34        throws ServletException, IOException {
35        if (securityContext.isCallerInRole(Utils.ADMIN)) {
36            response.sendRedirect("/admin.jsf");
37        } else if  (securityContext.isCallerInRole(Utils.USER)) {
38            response.sendRedirect("/user.jsf");
39        }
40    }
41
42    /**
43     * @see HttpServlet#doPost(HttpServletRequest request, HttpServletResponse
44     *      response)
45     */
46    protected void doPost(HttpServletRequest request, HttpServletResponse response)
47        throws ServletException, IOException {
48        doGet(request, response);
49    }
50}

The HomeServlet servlet is the entry point of the application. To enable form authentication for the HomeServlet class, define the @FormAuthenticationMechanismDefinition annotation and set its loginToContinue attribute with a @LoginToContinue annotation defining welcome.html as the login page and error.html as the error page.

The welcome.html and error.html pages that implement the login form and error page respectively are provided for you under the src/main/webapp directory. The login form in the welcome.html page uses the j_security_check action, which is defined by Java EE and available by default.

Authorization determines whether a user can access a resource. To restrict access to authenticated users with user and admin roles only, define the @ServletSecurity annotation with the @HttpConstraint annotation and set the rolesAllowed attribute to these two roles.

The transportGuarantee attribute defines the constraint on the traffic between the client and the application. Set it to CONFIDENTIAL to enforce that all user data must be encrypted, which is why an HTTP connection from a browser will be switched to an HTTPS one.

The SecurityContext interface provides programmatic access to Java EE Security API. Inject a SecurityContext instance into the HomeServlet class. The doGet() method uses the isCallerInRole() method from SecurityContext API to check a user’s role and then forwards the response to the appropriate page.

The src/main/webapp/WEB-INF/web.xml file contains the rest of the security declaration for the application.

web.xml

 1<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
 2    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
 3    xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd"
 4    version="3.1">
 5    <display-name>Liberty Project</display-name>
 6
 7    <!-- WebAppJSF: Faces Servlet -->
 8    <servlet>
 9      <servlet-name>Faces Servlet</servlet-name>
10      <servlet-class>javax.faces.webapp.FacesServlet</servlet-class>
11      <load-on-startup>1</load-on-startup>
12    </servlet>
13
14    <!-- WebAppJSF: Faces Servlet Mapping -->
15    <servlet-mapping>
16      <servlet-name>Faces Servlet</servlet-name>
17      <url-pattern>*.jsf</url-pattern>
18    </servlet-mapping>
19
20    <welcome-file-list>
21      <welcome-file>/index.html</welcome-file>
22    </welcome-file-list>
23
24    <!-- SECURITY ROLES -->
25    <security-role>
26      <role-name>admin</role-name>
27    </security-role>
28
29    <security-role>
30      <role-name>user</role-name>
31    </security-role>
32
33    <!-- SECURITY CONSTRAINTS -->
34    <security-constraint>
35      <web-resource-collection>
36        <web-resource-name>AdminViewProperties</web-resource-name>
37        <url-pattern>/admin.jsf</url-pattern>
38        <http-method>GET</http-method>
39      </web-resource-collection>
40      <auth-constraint>
41        <role-name>admin</role-name>
42      </auth-constraint>
43    </security-constraint>
44
45    <security-constraint>
46      <web-resource-collection>
47        <web-resource-name>UserViewProperties</web-resource-name>
48        <url-pattern>/user.jsf</url-pattern>
49        <http-method>GET</http-method>
50      </web-resource-collection>
51      <auth-constraint>
52        <role-name>user</role-name>
53      </auth-constraint>
54    </security-constraint>
55    <deny-uncovered-http-methods/>
56
57    <!-- Handle 403 Error -->
58    <error-page>
59      <error-code>403</error-code>
60      <location>/error403.html</location>
61    </error-page>
62</web-app>

The <security-role/> elements define the roles supported by the application, which are user and admin. The <security-constraint/> elements further specify that JSF resources like the user.jsf and admin.jsf pages can only be accessed by users with user and admin role respectively.

Configuring the user registry

User registries store user account information, such as username and password, for use by applications to perform security-related operations. Typically, application servers would be configured to use an external registry like a Lightweight Directory Access Protocol (LDAP) registry and applications would access information in the registry for authentication and authorization using API like Java EE Security API.

Open Liberty provides an easy-to-use basic user registry for developers, which you will configure.

Create the UserRegistry configuration file.
src/main/liberty/config/userRegistry.xml

userRegistry.xml

 1<server description="Sample Liberty server">
 2  <basicRegistry id="basic" realm="WebRealm">
 3    <user name="bob"
 4      password="{xor}PTA9Lyg7" /> <!-- bobpwd -->
 5    <user name="alice"
 6      password="{xor}PjM2PDovKDs=" />  <!-- alicepwd -->
 7    <user name="carl"
 8      password="{xor}PD4tMy8oOw==" />  <!-- carlpwd -->
 9    <user name="dave"
10      password="{xor}Oz4pOi8oOw==" />  <!-- davepwd -->
11
12    <group name="Manager">
13      <member name="bob" />
14    </group>
15
16    <group name="TeamLead">
17      <member name="carl" />
18    </group>
19
20    <group name="Employee">
21      <member name="alice" />
22      <member name="bob" />
23      <member name="carl" />
24    </group>
25
26    <group name="PartTime">
27      <member name="dave" />
28    </group>
29  </basicRegistry>
30</server>

The registry has four users, bob, alice, carl and dave, and four groups, Employee, TeamLead, Manager and PartTime. Each user belongs to one or more groups.

It is not recommended to store passwords in plain text. The passwords in the userRegistry.xml file are encoded using the Liberty securityUtility command with XOR encoding.

The server.xml file contains the security configuration of the server.

server.xml

 1<server description="Sample Liberty server">
 2
 3  <featureManager>
 4    <feature>appSecurity-3.0</feature>
 5    <feature>jsf-2.3</feature>
 6    <feature>servlet-4.0</feature>
 7  </featureManager>
 8
 9  <httpEndpoint id="defaultHttpEndpoint"
10    httpPort="${server.http.port}"
11    httpsPort="${server.https.port}" />
12
13  <include location="userRegistry.xml"/>
14
15  <application location="${application.name}"
16               type="war"
17               id="${application.name}"
18               name="${application.name}" context-root="/">
19    <application-bnd>
20      <security-role name="admin">
21        <group name="Manager" />
22        <group name="TeamLead" />
23      </security-role>
24      <security-role name="user">
25        <group name="Employee" />
26      </security-role>
27    </application-bnd>
28  </application>
29
30</server>

Use the <include/> element to add the basic user registry configuration to your server configuration. Open Liberty will include configuration information from the specified XML file in its server configuration.

Map the groups in the user registry to the appropriate user roles supported by the application for proper user authorization. The Manager and TeamLead groups are mapped to the admin role while the Employee group is mapped to the user role using the <security-role/> and <group/> elements under the <application-bnd/> configuration.

Building and running the application

To build the application, run the Maven install phase from the command line in the start directory:

mvn install

This command builds the application and creates a .war file in the target directory. It also configures and installs Open Liberty into the target/liberty/wlp directory.

Next, run the Maven liberty:start-server goal:

mvn liberty:start-server

This goal starts an Open Liberty server instance. Your Maven pom.xml is already configured to start the application in this server instance.

Point your browser to http://localhost:9080. As you can see, the browser gets redirected from an HTTP connection to an HTTPS connection automatically, which is due to the transport guarantee defined in the HomeServlet class.

You will see a login form since form authentication is implemented and configured. Sign into the application using one of the credentials from the following table, which is defined in the configured user registry.

UsernamePasswordRoleGroup

alice

alicepwd

user

Employee

bob

bobpwd

admin, user

Manager, Employee

carl

carlpwd

admin, user

TeamLead, Employee

dave

davepwd

N/A

PartTime

Notice that when you sign in as Bob or Carl, the browser redirects to the admin page and you can view their names and roles. When you sign in as Alice, you can only view Alice’s name. When you sign in as Dave, you’ll be blocked and will see a Error 403: Authorization failed message because Dave doesn’t have a role supported by the application.

Once you’re done checking out the application, log out and stop the Open Liberty server:

mvn liberty:stop-server

Testing the application

Write SecurityTest class to test the authentication and authorization of the application.

Create the SecurityTest class.
src/test/java/it/io/openliberty/guides/security/SecurityTest.java

SecurityTest.java

  1package it.io.openliberty.guides.security;
  2
  3import static org.junit.Assert.assertEquals;
  4import static org.junit.Assert.assertTrue;
  5
  6import java.net.URI;
  7import java.util.ArrayList;
  8import java.util.List;
  9
 10import javax.net.ssl.SSLContext;
 11import javax.servlet.http.HttpServletResponse;
 12
 13import org.apache.http.HttpResponse;
 14import org.apache.http.NameValuePair;
 15import org.apache.http.client.HttpClient;
 16import org.apache.http.client.config.CookieSpecs;
 17import org.apache.http.client.config.RequestConfig;
 18import org.apache.http.client.entity.UrlEncodedFormEntity;
 19import org.apache.http.client.methods.HttpGet;
 20import org.apache.http.client.methods.HttpPost;
 21import org.apache.http.impl.client.HttpClientBuilder;
 22import org.apache.http.message.BasicNameValuePair;
 23import org.apache.http.util.EntityUtils;
 24import org.junit.Before;
 25import org.junit.Test;
 26
 27public class SecurityTest {
 28
 29    private static String urlHttp;
 30    private static String urlHttps;
 31
 32    @Before
 33    public void setup() throws Exception {
 34        urlHttp = "http://localhost:" + System.getProperty("liberty.test.port");
 35        urlHttps = "https://localhost:" + System.getProperty("liberty.test.ssl.port");
 36        TestUtils.trustAll();
 37    }
 38
 39    @Test
 40    public void testAuthenticationFail() throws Exception {
 41        executeURL("/", "bob", "wrongpassword", true, -1, "Don't care");
 42    }
 43
 44    @Test
 45    public void testAuthorizationForAdmin() throws Exception {
 46        executeURL("/", "bob", "bobpwd", false,
 47            HttpServletResponse.SC_OK, "admin, user");
 48    }
 49
 50    @Test
 51    public void testAuthorizationForUser() throws Exception {
 52        executeURL("/", "alice", "alicepwd", false,
 53            HttpServletResponse.SC_OK, "<title>User</title>");
 54    }
 55
 56    @Test
 57    public void testAuthorizationFail() throws Exception {
 58        executeURL("/", "dave", "davepwd", false,
 59            HttpServletResponse.SC_FORBIDDEN, "Error 403: Authorization failed");
 60    }
 61
 62    private void executeURL(
 63        String testUrl, String userid, String password,
 64        boolean expectLoginFail, int expectedCode, String expectedContent)
 65        throws Exception {
 66
 67        // Use HttpClient to execute the testUrl by HTTP
 68        URI url = new URI(urlHttp + testUrl);
 69        HttpGet getMethod = new HttpGet(url);
 70        HttpClientBuilder clientBuilder = HttpClientBuilder.create();
 71        SSLContext sslContext = SSLContext.getDefault();
 72        clientBuilder.setSSLContext(sslContext);
 73        clientBuilder.setDefaultRequestConfig(
 74            RequestConfig.custom().setCookieSpec(CookieSpecs.STANDARD).build());
 75        HttpClient client = clientBuilder.build();
 76        HttpResponse response = client.execute(getMethod);
 77
 78        // Response should be login.html
 79        String loginBody = EntityUtils.toString(response.getEntity(), "UTF-8");
 80        assertTrue(
 81            "Not redirected to home.html",
 82            loginBody.contains("window.location.assign"));
 83        String[] redirect = loginBody.split("'");
 84
 85        // Use j_security_check to login
 86        HttpPost postMethod = new HttpPost(urlHttps + "/j_security_check");
 87        List<NameValuePair> nvps = new ArrayList<NameValuePair>();
 88        nvps.add(new BasicNameValuePair("j_username", userid ));
 89        nvps.add(new BasicNameValuePair("j_password", password));
 90        postMethod.setEntity(new UrlEncodedFormEntity(nvps, "UTF-8"));
 91        response = client.execute(postMethod);
 92        assertEquals(
 93            "Expected " + HttpServletResponse.SC_FOUND + " status code for login",
 94        HttpServletResponse.SC_FOUND, response.getStatusLine().getStatusCode());
 95
 96        // Return if login fails
 97        if (expectLoginFail) {
 98            String location = response.getFirstHeader("Location").getValue();
 99            assertTrue(
100                "Error.html was not returned",
101                location.contains("error.html"));
102            return;
103        }
104
105        // Use HttpClient to execute the redirected url
106        url = new URI(urlHttps + redirect[1]);
107        getMethod = new HttpGet(url);
108        response = client.execute(getMethod);
109        assertEquals(
110            "Expected " + expectedCode + " status code for login",
111            expectedCode, response.getStatusLine().getStatusCode());
112
113        // Return if not SC_OK
114        if (expectedCode != HttpServletResponse.SC_OK) {
115            return;
116        }
117
118        // Check the content of the response returned
119        String actual = EntityUtils.toString(response.getEntity(), "UTF-8");
120        assertTrue(
121            "The actual content did not contain the userid \"" + userid +
122            "\". It was:\n" + actual,
123        actual.contains(userid));
124        assertTrue(
125            "The url " + testUrl +
126            " did not return the expected content \"" + expectedContent + "\"" +
127            "The actual content was:\n" + actual,
128        actual.contains(expectedContent));
129    }
130
131}

The testAuthenticationFail() method tests an invalid user authentication while the testAuthorizationFail() method tests unauthorized access to the application.

The testAuthorizationForAdmin() and testAuthorizationForUser() methods, respectively, verify that a user with admin role and a user with user role are properly authenticated and can access authorized resource.

Running the tests

If the server is still running from the previous steps, stop it using the Maven liberty:stop-server goal from command line in the start directory:

mvn liberty:stop-server

Then, verify that the tests pass using the Maven verify goal:

mvn verify

It may take some time before build is complete. If the tests pass, you will see a similar output to the following:

-------------------------------------------------------
 T E S T S
-------------------------------------------------------
Running it.io.openliberty.guides.security.SecurityTest
Tests run: 4, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 1.78 sec - in it.io.openliberty.guides.security.SecurityTest

Results :

Tests run: 4, Failures: 0, Errors: 0, Skipped: 0

Great work! You’re done!

You learned how to use Java EE Security in Open Liberty to authenticate and authorize users to secure your web application.

Next, you can try the related MicroProfile JWT guide. It demonstrates technologies to secure backend services.

Guide Attribution

Securing a web application by Open Liberty is licensed under CC BY-ND 4.0

Copied to clipboard
Copy code block
Copy file contents
Git clone this repo to get going right away:
git clone https://github.com/OpenLiberty/guide-security-intro.git
Copy github clone command
Copied to clipboard

Nice work! Where to next?

What could make this guide better?

Raise an issue to share feedback

Create a pull request to contribute to this guide

Need help?

Ask a question on Stack Overflow

What did you think of this guide?

Extreme Dislike Dislike Like Extreme Like