Implementing portable security in JEE 8

We will modify CourseManagementMavenWebApp from Chapter 7, Creating JEE Applications with EJB, in this section. This project was part of the EJB CourseManagementMavenEAR project, but in this section, we will work with CourseManagementMavenWebApp independently. Copy the CourseManagementMavenWebApp project from Chapter07, as CourseManagementMavenWebApp-jee8 in the Eclipse workspace for this chapter.

We will modify this project to provide the following functionality:

  • AdminServlet is a protected servlet requiring login. We will implement the basic authentication
  • There are three possible user roles: admin, manager, and user
  • Only users in the admin role can see the admin page, served by AdminServlet
  • Only users in the manager role can see the management page, served by ManagementServlet

JEE 8 security APIs require Contexts and Dependency Injection (CDI) to be enabled in the application. We just need to create an empty beans.xml file in the src/main/webapp/WEB-INF folder to enable CDI.

Next, we need to add the following Maven dependency in pom.xml to make the JEE 8 APIs available in the application:

  <dependency>
<groupId>javax</groupId>
<artifactId>javaee-api</artifactId>
<version>8.0</version>
<scope>provided</scope>
</dependency>

Let's create a class called ApplicationConfig (in the packt.book.jee.eclipse.ch7.web.servlet package) to declare all user roles allowed in the application. Here is the source code for the ApplicationConfig class:

package packt.book.jee.eclipse.ch7.web.servlet;
import javax.annotation.security.DeclareRoles;
import javax.enterprise.context.ApplicationScoped;
@DeclareRoles({"admin", "user", "manager"})
@ApplicationScoped
public class ApplicationConfig {}

Now, let's create two servlets, AdminServlet and ManagementServlet, in the packt.book.jee.eclipse.ch7.web.servlet package. If you create these classes using the Eclipse wizard, then it adds the servlet declarations and mappings in web.xml. If you are not using the Eclipse wizard, then either add the declarations and mappings manually, or add the @WebServlet annotation in the servlet classes. Here is the source code for AdminServlet:

package packt.book.jee.eclipse.ch7.web.servlet;

// skipped imports

@BasicAuthenticationMechanismDefinition()
@ServletSecurity(@HttpConstraint(rolesAllowed = {"admin", "manager"} ))
public class AdminServlet extends HttpServlet {

private static final long serialVersionUID = 1L;

@Inject
private SecurityContext securityContext;

public AdminServlet() {
super();
}

protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
if (securityContext.isCallerInRole("manager")) {
request.getRequestDispatcher("/ManagementServlet").forward(request, response);
} else {
response.getWriter().append("Welcome to Admin Page!");
}
}

protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
doGet(request, response);
}
}

The servlet allows access to only the admin and manager roles, using the @ServletSecurity annotation. The servlet also specifies the basic authentication type using @BasicAuthenticationMechanismDefinition. We also ask the JEE container to inject an instance of SecurityContext, which is used in the doGet method to check the user's role. If the user is in the manager role, then the request is forwarded to ManagementServlet, otherwise, access is granted to the current servlet. Note the call to securityContext.isCallerInRole to check the user's role.

Here is the source code for ManagementServlet:

package packt.book.jee.eclipse.ch7.web.servlet;
// skipped imports
@BasicAuthenticationMechanismDefinition(realmName="basic")
@ServletSecurity(@HttpConstraint(rolesAllowed = {"manager"} ))
public class ManagementServlet extends HttpServlet {
private static final long serialVersionUID = 1L;

public ManagementServlet() {
super();
}

protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
response.getWriter().append("Welcome to Management Page!");
}

protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
doGet(request, response);
}
}

The preceding servlet also uses basic authentication, and allows access only to users in the manager role.

With the preceding annotations, no declarative configuration is required for web.xml or any custom container-specific file. But, how do we tell security APIs who are valid users and roles? We do that by implementing the IdentityStore interface. Create the SimpleMapIdentityStore class in the packt.book.jee.eclipse.ch7.web.servlet package. This class should implement the IdentityStore interface:

package packt.book.jee.eclipse.ch7.web.servlet;

// skipped imports

@ApplicationScoped
public class SimpleMapIdentityStore implements IdentityStore {
class UserInfo {
String userName;
String password;
String role;

public UserInfo(String userName, String password, String role) {
this.userName = userName;
this.password = password;
this.role = role;
}
}

private HashMap<String, UserInfo> store = new HashMap<>();

public SimpleMapIdentityStore() {
UserInfo user1 = new UserInfo("user1", "user1_pass", "admin");
UserInfo user2 = new UserInfo("user2", "user2_pass", "user");
UserInfo user3 = new UserInfo("user3", "user3_pass", "manager");
store.put(user1.userName, user1);
store.put(user2.userName, user2);
store.put(user3.userName, user3);
}

public CredentialValidationResult validate(UsernamePasswordCredential usernamePasswordCredential) {
String userName = usernamePasswordCredential.getCaller();
String password = usernamePasswordCredential.getPasswordAsString();

UserInfo userInfo = this.store.get(userName.toLowerCase());
if (userInfo == null || !userInfo.password.equals(password)) {
return INVALID_RESULT;
}

return new CredentialValidationResult(userInfo.userName, new HashSet<>(asList(userInfo.role)));
}
}

It is important that the preceding class is annotated with @ApplicationScoped, so that it is available throughout the application, and CDI can inject it. We have hardcoded users and roles in a HashMap in the preceding class, but you can write the code to get users and roles from any source, such as a database, LDAP, or a file. In the application, there can be more than one IdentityStore. The container would call the  validate method of each one. In the validate method, we are first verifying that the username and password are valid, and then returning an instance of CredentialValidationResult, with the roles of the user attached to it.

Build the application (right-click on the project and select Run As | Maven Install), and deploy it in the GlassFish 5 Server, as described in previous sections. Make sure the context of the application is set to /CourseManagementMavenWebApp-jee8. You can verify this on the GlassFish admin page by editing the deployed application and verifying the value of the Context Root field.  Then browse to http://localhost:8080/CourseManagementMavenWebApp-jee8/AdminServlet. If you log in with user1 credentials, then the admin page will be displayed. If you log in as user3, then the management page will be displayed. Access to all other users is blocked. You would need to close the browser window to try to log in with different users, because once logged in, the user credentials are remembered till the session is invalidated. The application can be easily extended to add a logout option, as we did in previous sections.

In the previous example, we have created a custom identity store. You can implement any code in this to acquire user information, from either a database or LDAP. But, JEE security APIs provide built-in annotations for accessing a database and LDAP as identity stores; that is, @DatabaseIdentityStoreDefinition and @LdapIdentityStoreDefinition. For example, we could modify the ApplicationConfig class to declare a database identity store as follows: 

package packt.book.jee.eclipse.ch7.web.servlet;

import javax.enterprise.context.ApplicationScoped;
import javax.security.enterprise.identitystore.DatabaseIdentityStoreDefinition;
import javax.security.enterprise.identitystore.PasswordHash;

@DatabaseIdentityStoreDefinition (
dataSourceLookup = "jdbc/CourseManagement",
callerQuery = "select password from user_group_view where user_name = ?",
groupsQuery = "select group_name from user_group_view where user_name = ?",
hashAlgorithm = PasswordHash.class,
priority = 10
)
@ApplicationScoped
public class ApplicationConfig {
}

We need to pass the JNDI lookup name for the JDBC resource, which is jdbc/CourseManagement, and SQL queries to validate the username and password and to get groups. These are similar to the SQL queries we configured when creating a Realm on the GlassFish admin page, but with the new security APIs, making the configuration more portable. See https://javaee.github.io/security-spec/spec/jsr375-spec.html#_annotations_and_built_in_identitystore_beans for more details on IdentityStore annotations.

In the preceding example, we have used the basic authentication type. But, you can use form-based authentication using the @FormAuthenticationMechanismDefinition annotation. For example, we could replace @BasicAuthenticationMechanismDefinition with  @FormAuthenticationMechanismDefinition, as follows:

package packt.book.jee.eclipse.ch7.web.servlet;
// ...
@FormAuthenticationMechanismDefinition(
loginToContinue = @LoginToContinue(
loginPage = "/loginServlet",
errorPage = "/loginErrorServlet"
)
)
@DeclareRoles({"admin"})
@ServletSecurity(@HttpConstraint(rolesAllowed = "admin"))
public class AdminServlet extends HttpServlet {
...
}

This configuration is similar to <form-login-config>, which we configured in web.xml in earlier examples. 

Note that the new security APIs work mostly on Java classes, such as servlets, EJBs, and beans, but if you want to protect JSP pages, then you need to use the declarative configuration we learned in previous sections.

Security in JEE is a very large topic, which can't be covered in a book of generic nature. The scope of this chapter is limited to securing JEE resources with a username and password. For detailed information on security in JEE, refer to https://javaee.github.io/tutorial/security-intro.html.
..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.139.97.53