In the previous chapter we have described how to deploy your application in a robust and reliable environment using clustering. The last stop in our journey will be learning about security, which is a key element of any Enterprise application. You must be able to control and restrict who is permitted to access your applications and what operations users may perform.
The Java Enterprise Edition (Java EE) specification defines a simple role-based security model for Enterprise JavaBeans (EJBs) and web components. The implementation of JBoss security is delivered by the Picketbox framework (formerly known as JBoss security), which is part of the application server and provides the authentication, authorization, auditing, and mapping capabilities to Java applications.
Here is the specific list of topics we will cover:
Java EE security services provide a robust and easily configurable security mechanism for authenticating users and authorizing access to application functions and associated data. To better understand the topics related to security, we should at first give some basic definitions:
Login
module contained in a web/standalone application.In Java EE, the component containers are responsible for providing application security. A container basically provides two types of security: declarative and programmatic. Let's see them both:
For example, Enterprise JavaBeans components use an EJB deployment descriptor that must be named ejb-jar.xml
and placed in the META-INF
folder of the EJB JAR file.
Web components use a web application deployment descriptor named web.xml
located in the WEB-INF
directory.
Since Java EE 5, you can apply declarative security also by means of annotations just like we have for other key APIs (EJB, web services, and so on). Annotations are specified within a class file and, when the application is deployed, this information is translated internally by the application server.
isUserInRole()
for servlets and JSPs (adopted in javax.servlet.http.HttpServletRequest
)isCallerInRole()
for EJBs (adopted in javax.ejb.SessionContext
)Additionally, there are other API calls that provide access to the user's identity:
getUserPrincipal()
for servlets and JSPs (adopted in javax.servlet.http.HttpServletRequest
)getCallerPrincipal()
for EJBs (adopted in javax.ejb.SessionContext
)Using these APIs, you can develop arbitrarily complex authorization models.
JBoss security is qualified as an extension to the application server and it is included by default both in standalone servers and in domain servers:
<extension module="org.jboss.as.security"/>
The following is an extract from the default security subsystem contained in the server configuration file, which contains the RealmUsersRoles
login module that will be used in the next section to secure the Ticket example application:
<subsystem xmlns="urn:jboss:domain:security:1.1"> <security-domains> <security-domain name="other" cache-type="default"> <authentication> <login-module code="Remoting" flag="optional"> <module-option name="password-stacking" value="useFirstPass"/> </login-module> <login-module code="RealmUsersRoles" flag="required"> <module-option name="usersProperties" value="${jboss.server.config.dir}/application-users.properties"/> <module-option name="rolesProperties" value="${jboss.server.config.dir}/application-roles.properties"/> <module-option name="realm" value="ApplicationRealm"/> <module-option name="password-stacking" value="useFirstPass"/> </login-module> </authentication> </security-domain> . . . . </security-domains> </subsystem>
As you can see, the configuration is pretty short as it relies largely on default values, especially for high-level structures like the security management area. By defining your own security management options, you could for example, override the default authentication/authorization managers with your implementations. Since it is likely that you will not need to override these interfaces, we will rather concentrate on the security-domain
element, which is the core aspect of JBoss security.
A security domain can be thought of as a Customs Office for foreigners. Before the request crosses JBoss AS borders, the security domain performs all the required authorization and authentication checks and eventually notifies if he/she can proceed or not.
Security domains are generally configured at server startup and subsequently bound into the JNDI tree under the key java:/jaas/
. Within the security domain, you can configure login authentication modules so that you can easily change your authentication provider by simply changing its login-module
.
There are several login modules implementations available out of the box; there is obviously not enough room here to describe in detail the features of each module, though we will offer a comprehensive description of some popular options, such as:
RealmUsersRoles
login module, which can be used for basic file-based authenticationDatabase
login module, which checks user credentials against a relational databaseShould you need further information about login modules, check out the JBoss AS 7.1 documentation at https://docs.jboss.org/author/display/AS71/Security+subsystem+configuration.
In the following section, we will demonstrate how to secure an application using the RealmUsersRoles
security domain, which has been introduced earlier. The RealmUserRoles
login module is based on the following two files
application-users.properties
: It contains the list of usernames and passwordsapplication-roles.properties
: It contains the mapping between the users and the rolesThese files are located in the application server configuration folder and they are updated each time you add a new user via the add-user.sh
/add-user.cmd
script. For our purposes, we will create a new application user named demouser
that belongs to the role Manager
, as shown in the following screenshot:
Once the user is added, the application-users.properties
file will contain the username and the MD5 encoding of the password:
demouser=290dfdb724ee4ed466b401a22040efd2
Conversely, the application-roles.properties
file will contain the roles granted to the demouser
username once logged in:
demouser=Manager
We can now apply the RealmUserRoles
login module into any Ticket web application described in the book. We will show at first how to provide a BASIC web authentication and then we will show a slightly more complex example using FORM-based authentication.
BASIC-access authentication is the simplest way to provide a username and password when making a request through a browser.
It works by sending an encoded string containing the user credentials. This Base64-encoded string is transmitted and decoded by the receiver, resulting in the colon-separated username and password strings.
Turning on web authentication requires the security-constraints
element to be defined in the web application configuration file (web.xml
), as shown in the following code snippet:
<web-app> . . . . . . <security-constraint> <web-resource-collection> <web-resource-name>HtmlAuth</web-resource-name> <description>application security constraints </description> <url-pattern>/*</url-pattern> <http-method>GET</http-method> <http-method>POST</http-method> </web-resource-collection> <auth-constraint> <role-name>Manager</role-name> </auth-constraint> </security-constraint> <login-config> <auth-method>BASIC</auth-method> <realm-name>file</realm-name> </login-config> <security-role> <role-name>Manager</role-name> </security-role> </web-app>
This configuration will add a security constraint on any JSP/servlet of the web application that will restrict access to users authenticated with the role Manager
. All login modules shown in the earlier section define this role, so you can just use the login module that suits your needs best.
The next configuration tweak needs to be performed on the JBoss web deployment's descriptor, WEB-INF/jboss-web.xml
. You need to declare the security domain here, which will be used to authenticate the users. Since we are using RealmUsersRoles
, which is part of the other built-in login module, we will need to include the java:/jaas/other
context information:
<jboss-web>
<security-domain>java:/jaas/other</security-domain>
</jboss-web>
The following diagram illustrates the whole configuration sequence applied to a Database
login module:
Once you have deployed your application, the outcome should be a blocking pop-up requesting user authentication, as shown in the following screenshot:
Logging in with demouser
and the valid password will grant access to the application with the Manager
role.
FORM-based authentication lets developers customize the authentication user interface, adapting it, for example, to your company's standards. Configuring it in your application requires you to basically modify just the login-config
stanza of the security section of your web.xml
file. Within it, we will be defining a login landing page (login.jsf
) and an error page (error.jsf
), in case the login fails. Here is the code snippet for it:
<login-config> <auth-method>FORM</auth-method> <realm-name>file</realm-name> <form-login-config> <form-login-page>/login.jsf</form-login-page> <form-error-page>/error.jsf</form-error-page> </form-login-config> </login-config>
The login form must contain fields for entering a username and password. These fields must be named j_username
and j_password
respectively. The authentication form should post these values to the j_security_check
logical name. Here's a simple login.jsf
page that can be used for this purpose:
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> <html xmlns="http://www.w3.org/1999/xhtml"xmlns:h="http://java.sun.com/jsf/html"> <h1> Please Login </h1> <body> <form method="post" action="j_security_check" name="loginForm"> <h:panelGrid columns="2"> <h:outputLabel id="userNameLabel" for="j_username" value="Username:"/> <h:inputText id="j_username" /> <h:outputLabel id="passwordLabel" for="j_password" value="Password:"/> <h:inputSecret id="j_password" /> <h:panelGroup> <h:commandButton type="submit" value="Login"/> </h:panelGroup> </h:panelGrid> </form> </body> </html>
For the sake of brevity, we won't include the error page, which will simply alert that the user entered an incorrect combination of username and password. The expected outcome is the following login screen, which will intercept all user access to your application and grant access to the default home page if the username
and password
credentials are correct.
The UserRoles
login module is a good starting point for learning how to put together all the pieces required for securing a web application. In real-world cases, there are better alternatives to protect your applications, such as the Database
login module. A database security domain follows the same logic exposed in the earlier example; it just stores the credentials within the database. In order to run this example, we will refer to a data source defined in Chapter 5, Combining Persistence with CDI (bound at the JNDI name java:jboss/datasources/jbossas7development
), which needs to be deployed on the application server:
<security-domain name="mysqldomain" cache-type="default"> <authentication> <login-module code="Database" flag="required"> <module-option name="dsJndiName" value=" java:jboss/datasources/jbossas7development"/> <module-option name="principalsQuery" value="select passwd from USERS where login=?"/> <module-option name="rolesQuery" value="select role, 'Roles' from USER_ROLES where login=?"/> </login-module> </authentication> </security-domain>
In order to get working with this configuration, you have to first create the required tables and insert some sample data in it:
CREATE TABLE USERS(login VARCHAR(64) PRIMARY KEY, passwd VARCHAR(64)); CREATE TABLE USER_ROLES(login VARCHAR(64), role VARCHAR(32)); INSERT into USERS values('admin', 'admin'), INSERT into USER_ROLES values('admin', 'Manager'),
As you can see, the admin
user will map again to the Manager
role. One caveat of this configuration is that it uses clear-text passwords in the database; so before rolling this module into production, you should consider adding additional security to your login module. Let's see how in the next section.
Storing passwords in the database as a clear-text string is not considered a good practice; as a matter of fact, a database has even more potential security holes than a regular file system. Imagine, for example, that a DBA added a public synonym for some tables, forgetting that one of those tables was holding sensitive information like application passwords! You then need to be sure that no potential attackers will ever be able to deliver the following query:
Fortunately, securing application passwords is relatively easy; you can add a few extra options to your Login Module, specifying that the stored passwords are encrypted using a message digest algorithm. For example, in the mysqlLogin
module, you should add the following highlighted options at the bottom:
<login-module code="Database" flag="required"> <module-option name="dsJndiName" value="java:jboss/datasources/jbossas7development"/> <module-option name="principalsQuery" value="select passwd from USERS where login=?"/> <module-option name="rolesQuery" value="select role, 'Roles' from USER_ROLES where login=?"/> <module-option name="hashAlgorithm" value="MD5"/> <module-option name="hashEncoding" value="BASE64"/> <module-option name="hashStorePassword" value="true"/> </login-module>
Here we have specified that the password will be hashed against an MD5 hash algorithm; you can alternatively use any other algorithm allowed by your JCA provider, such as SHA.
For the sake of completeness, we include here a small application, which uses the java.security.MessageDigest
and org.jboss.security.Base64Utils
class (contained in the picketbox-4.0.7.Final.jar
file that is part of the JBoss AS 7 modules), to generate the Base-64 hashed password that is to be inserted in Database
:
public class Hash { public static void main(String[] args) throws Exception{ String password = args[0]; MessageDigest md = null; md = MessageDigest.getInstance("MD5"); byte[] passwordBytes = password.getBytes(); byte[] hash = md.digest(passwordBytes); String passwordHash = org.jboss.security.Base64Utils.tob64(hash); System.out.println("password hash: "+passwordHash); }
Running the main program with admin
as the argument will generate the hash X8oyfUbUbfqE9IWvAW1/3
. This hash will be your updated password, which needs to be updated in your database as shown in the following screenshot.
UPDATE USERS SET PASSWD = 'X8oyfUbUbfqE9IWvAW1/3' WHERE LOGIN = 'admin';
You can update it from any SQL client of your liking.
Securing applications by means of a web login form is the most frequently used option in Enterprise applications. Nevertheless, the HTTP protocol is not the only choice available to access applications. For example, EJBs can be accessed by remote clients using the RMI-IIOP protocol. In such a case, you should further refine your security policies by restricting access to the EJB components, which are usually involved in the business layer of your applications.
One vast area of improvement introduced in Java EE 5 concerns the use of annotations, which can also be used to perform the basic security checks. There are five available annotations, as follows:
@org.jboss.ejb3.annotation.SecurityDomain
: This specifies the security domain that is associated with the class/method.@javax.annotation.security.RolesAllowed
: This specifies the list of roles permitted to access a method(s) in an EJB application.@javax.annotation.security.RunAs
: This assigns a role dynamically to the EJB application during the invocation of the method. It can be used, for example, if we need to temporarily allow a permission to access certain methods.@javax.annotation.security.PermitAll
: This specifies that an EJB application can be invoked by any client. The purpose of this annotation is to widen security access to some methods in situations where you don't exactly know what role will access the EJB application (imagine that some modules have been developed by a third party and they access your EJB application with some roles that are not well-identified).@javax.annotation.security.DenyAll
: This specifies that an EJB application cannot be invoked by external clients. It has the same considerations as those for @PermitAll
.Here is an example of how to secure the TheatreBookerBean
SFSB, which we discussed in Chapter 4, Learning Context Dependency Injection:
@RolesAllowed("Manager") @SecurityDomain("mysqldomain") @Stateful @Remote(TheatreBooker.class) public class TheatreBookerBean implements TheatreBooker { }
Annotations can also be applied at the method level; for example, if we want to secure just the bookSeat
object of the TheatreBookerBean
class, we would tag the method as follows:
@RolesAllowed("Manager") @SecurityDomain("mysqldomain") public String bookSeat(int seatId) throws SeatBookedException { }
What about if you don't want to use annotations for establishing security roles? For example, if you have a security role that is used crosswise by all your EJB applications, perhaps it is simpler to use a plain old XML configuration instead of tagging all EJBs with annotations. In this scenario, you have to declare the security constraints first in the generic META-INF/ejb-jar.xml
file:
<method-permission> <role-name>Manager</role-name> <method> <ejb-name>*</ejb-name> <method-name>*</method-name> </method> </method-permission>
Then, inside the META-INF/jboss-ejb3.xml
configuration file, just add a reference to your security domain:
<jboss:ejb-jar> <assembly-descriptor> <s:security> <ejb-name>*</ejb-name> <s:security-domain>mysqldomain</s:security-domain> </s:security> </assembly-descriptor> </jboss:ejb-jar>
Here's a snapshot illustrating the EJB-file-based role configuration:
Web service authorization can basically be carried out in two ways, depending on if we are dealing with a POJO-based web service or an EJB-based web service.
Security changes to POJO web services are identical to those we have introduced for servlets/JSP: consistent in defining the security-constraints
element in web.xml
and the login modules in jboss-web.xml
.
If you are using a web client to access your web service, it is all you need to get authenticated. If you are using a standalone client, you will need in turn to specify the credentials to the JAX-WS Factory. Here is an example of accessing the secured POJOWebService
instance, which was described in Chapter 8, Adding Web Services to Your Applications:
JaxWsProxyFactoryBean factory = new JaxWsProxyFactoryBean(); factory.getInInterceptors().add(new LoggingInInterceptor()); factory.getOutInterceptors().add(new LoggingOutInterceptor()); factory.setServiceClass(POJOWebService.class); factory.setAddress("http://localhost:8080/pojoService"); factory.setUsername("admin"); factory.setPassword("admin"); POJOWebService client = (POJOWebService) factory.create(); client.doSomething();
What about EJB-based web services? The configuration is slightly different; since the security domain is not specified into web descriptors, we have to provide it by means of annotations:
@Stateless @WebService(targetNamespace = "http://www.packtpub.com/", serviceName = "TicketWebService") @WebContext(authMethod = "BASIC", secureWSDLAccess = false) @SecurityDomain(value = "mysqldomain") public class TicketSOAPService implements TicketSOAPServiceItf, Serializable { . . . . }
As you can see, the @org.jboss.ws.api.annotation.Webcontext
annotation basically reflects the same configuration options as that of POJO-based web services, with BASIC authentication and unrestricted WSDL access.
The @org.jboss.ejb3.annotation.SecurityDomain
annotation should be familiar to you since we have introduced it to illustrate how to secure an EJB. As you can see, it's a replacement for the information contained in the jboss-web.xml
file, except that the security domain is referenced directly by mysqldomain
(instead of java:/jaas/mysqldomain
).
3.144.30.62