In the Web module we can declare authentication, authorization, and transport-level encryption using the provided annotations and deployment descriptors.
Authentication and Authorization in Web Module
The following snippet instructs the application server to only let the “manager” role access a resource with a URL matching /mgr/* in our Web application.
<security-constraint>
<display-name>mgr resources</display-name>
<web-resource-collection>
<web-resource-name>managers</web-resource-name>
<description/>
<url-pattern>/mgr/*</url-pattern>
<http-method>GET</http-method>
<http-method>PUT</http-method>
<http-method>POST</http-method>
</web-resource-collection>
<auth-constraint>
<description/>
<role-name>manager</role-name>
</auth-constraint>
</security-constraint>
<security-role>
<description>All Manager </description>
<role-name>manager</role-name>
</security-role>
We defined a security constraint, defined a collection of resources matching the /mgr/* URL, and defined a constraint over the GET, PUT, and POST methods. Then we permitted the manager role to access the constrained resources. We can include as many roles as we need in the auth-constraint element. In case multiple roles are specified the caller will be granted the permission to access the constrained resources if the user has any of those roles; in other words, “OR” semantics apply.
Any security role referenced in the auth-constraint elements should be defined using a security-role element as we did for the manager role.
So far we defined which role has access to the secured resource but we still need to let the application server know how to authenticate the users and later on how to determine which roles the user has.
Java EE containers provide via the Servlet spec four standard authentication mechanisms for usage in the web module. Servlet containers should also provide public interfaces for additional (custom) authentication mechanisms. These mechanisms with their specification names are as follows:
- HTTP Basic Authentication: BASIC
- Digest Authentication: DIGEST
- HTTPS Client Authentication: CLIENT-CERT
- Form-Based Authentication: FORM
- Custom/Additional: JASPIC
In the first two mechanisms, BASIC and DIGEST, the mechanism initiates an HTTP basic authentication process and usually the user agent (the browser) then shows a standard dialog to collect the username and the password. The only difference is that when using DIGEST, the client sends a digest of the password instead of sending it in clear text. To use any of these methods we only need to include the following snippet in web.xml.<login-config> <auth-method>BASIC</auth-method> <realm-name>file-realm</realm-name></login-config>
In the CLIENT-CERT mechanism, clients authenticate the server by asking the server for its digital certificate and the server also asks the client to provide its digital certificate to authenticate its identity. In this mode nothing is required to be done except that the client and the server must have a certificate issued by a certificate authority trusted by the other side.Example: Configure Liberty profile to accept a certificate loginTo enable Liberty profile to authenticate with a digital certificate the request must:
The request must come in on an HTTPS connection.
The SSL/TLS feature ssl-1.0 must be enabled, and Liberty SSL must be configured to have clientAuthentication or clientAuthenticationSupported.
The certificate must map to a user in the user registry.
When SSL/TLS client authentication is configured the server will need to trust the client’s certificate. The client’s trusted certificate will need to be added to the server’s truststore.
<server>
<featureManager>
<feature>ssl-1.0</feature>
</featureManager>
<!-- Server SSL configuration -->
<ssl id="defaultSSLConfig" keyStoreRef="defaultKeyStore"
trustStoreRef="defaultTrustStore"
clientAuthenticationSupported="true" />
<!-- keystore configuration -->
<keyStore id="defaultKeyStore" location="key.jks"
type="JKS" password=<file password> />
<keyStore id="defaultTrustStore" location="trust.jks"
type="JKS" password=<file password> />
</server>
The digital certificate used on the SSL connection must map to a user in the registry. For a file-based user registry the CN value of the certificate must be the username of a user in the registry. The LDAP user registry by default must contain a user entry that matches the complete DN of the certificate. LDAP can be configured to look at specific attributes on the certificate's DN. Custom user registries will have to be written to take into account a certificate must map to a user.
The FORM mechanism lets the developer have more control over authentication by letting them provide their own credentials collecting pages. For that we basically create a login and an error page and let the authentication mechanism know about our pages. The authentication mechanism will then use these pages to collect the user credentials. To use this method we should include the following snippet into web.xml.
<login-config>
<auth-method>FORM</auth-method>
<realm-name>file-realm</realm-name>
<form-login-config>
<form-login-page>auth/login.jsp</form-login-page>
<form-error-page>auth/login-error.jsp</form-error-page>
</form-login-config>
</login-config>
The simplest content for the login.jsp page is as follows:
<form method=post action="j_security_check"><br /><input type="text" name="j_username"><br /><input type="password" name="j_password"><br /></form>
The login-error.jsp page can contain any sort of information you feel necessary for the users to understand they provided the wrong credentials.
The CUSTOM mechanism option lets application developers provide their own implementation of an authentication mechanism, e.g. to transparently integrate OAuth or OpenID. The interfaces for this and their semantics are described in the Servlet Container Profile of the JASPIC (JSR 196) spec. Introduced in Java EE 6, the custom mechanism is now supported by all full Java EE servers and additionally TomEE, Tomcat, and Jetty.
Custom authentication mechanisms can be bundled inside the web application archive and registered via a programmatic API. There is currently (in Java EE 7) no declarative (web.xml or annotation) option available to do this. If a custom authentication mechanism is registered in this way, any login-config present in web.xml is overridden. To avoid confusion it's therefore best to remove such login-config.
The following shows an example of a very basic Java EE custom authentication mechanism that unconditionally authenticates the user without delegating to an identity store:
public class TestServerAuthModule implements ServerAuthModule {
private CallbackHandler handler;
private Class<?>[] supportedMessageTypes = new Class[] {
HttpServletRequest.class, HttpServletResponse.class };
@Override
public void initialize(MessagePolicy requestPolicy,
MessagePolicy responsePolicy, CallbackHandler handler,
@SuppressWarnings("rawtypes") Map options) throws
AuthException { this.handler = handler; }
@Override
public AuthStatus validateRequest(MessageInfo messageInfo,
Subject clientSubject, Subject serviceSubject) throws
AuthException {
CallerPrincipalCallback callerPrincipalCallback =
new CallerPrincipalCallback(clientSubject, "Bob");
GroupPrincipalCallback groupPrincipalCallback =
new GroupPrincipalCallback(
clientSubject, new String[] { "users" }
);
try {
handler.handle(new Callback[] { callerPrincipalCallback,
groupPrincipalCallback });
} catch (IOException | UnsupportedCallbackException e) {
e.printStackTrace();
}
return SUCCESS;
}
@Override
public Class<?>[] getSupportedMessageTypes() {
return supportedMessageTypes;
}
@Override
public AuthStatus secureResponse(MessageInfo messageInfo,
Subject serviceSubject) throws AuthException {
return SEND_SUCCESS;
}
@Override
public void cleanSubject(MessageInfo messageInfo, Subject subject) throws AuthException {}
}
After the authentication mechanism shown above is installed, the validateRequest()
will be called before every Filter or Servlet in the web application is called and will set the user/caller Principal to “bob” with group “users”.
For more info about custom authentication mechanisms see:
For the authentication mechanisms that delegate the credential validation check, it is now time to let the application server know where the user’s credentials are stored so it can authenticate the received credentials with them and decide whether the user is authentic or not.
This is done by the identity store, but in Java EE, identity stores are not standardized. Individual servers use different terminology for this concept. The list below gives some examples:
- Tomcat – Realm
- Liberty – UserRegistry
- JBoss/WildFly – LoginModule
- Resin – Authenticator
- Jetty- LoginService
- GlassFish/Payara – LoginModule + Realm
Identity stores are set in a vendor-specific way, which is typically a proprietary deployment descriptor, admin console, and/or CLI. The following table shows several examples for a simular type of identity store: a filebased one containing hardcoded users, credentials, and groups. These stores are typically not used for production, but merely for testing and examples.
Server |
Example |
Tomcat |
server.xml <Realm className="org.apache.catalina.realm.MemoryRealm" />
tomcat-users.xml <tomcat-users>
<role rolename="admins"/>
<role rolename="users"/>
<user username="Bob" password="secret" roles="users"/>
<user username="Paula" password="secret1" roles="admins,users"/>
</tomcat-users>
|
Liberty |
server.xml <basicRegistry id="basic" realm="basicRealm">
<user name="Bob" password="secret" />
<user name="Paula" password="secret1" />
<group name="admins">
<member name="Paula" />
</group>
<group name="users">
<member name="Bob" />
<member name="Paula" />
</group>
</basicRegistry>
|
Jboss/WildFly |
standalone.xml <login-module code="org.jboss.security.auth.spi.UsersRolesLoginModule" flag="required">
<module-option name="usersProperties">users.properties</module-option>
<module-option name="rolesProperties">roles.properties</module-option>
</login-module>
users.properties Bob=secret
Paula=secret1
roles.properties Bob=users
Paula=admins,users
|
GlassFish / Payara |
domain.xml <auth-realm classname="com.sun.enterprise.security.auth.realm.file.FileRealm" name="file">
<property name="file" value="${com.sun.aas.instanceRoot}/config/keyfile"></property>
<property name="jaas-context" value="fileRealm"></property>
</auth-realm>
Keyfile Bob;{SSHA256}DZdjK7z7p+3kZxlF8Fk5XNT7pu/51LpVhgYqNZ8idgImiubk7gnOCQ==;users Paula;{SSHA256}g8Dxl1w5y8FSXqhPufokwCdNTuZmhoKgklQqA4f/j3Arm2nCXvId5g==;admin,users
|
After credentials have been located and validated, it's time to map groups to roles and optionally assign some users roles directly. For some servers, groups must be mapped, for some servers it's optional, and some servers don't support mapping at all.
Liberty is a special case. Proprietary group mapping is mandated when groups are set by a proprietary authentication mechanism implementation (using a proprietary identity store called “user registry”), but is not supported when groups are set by a Java EE standard authentication mechanism (a JASPIC SAM).
In the examples below we map the group “users” to role “employees” using a proprietary deployment descriptor from within the application archive:
Server |
Example |
Liberty (mandatory when not using JASPIC, otherwise not supported) |
META-INF/ibm-application-bnd.xml <?xml version="1.0" encoding="UTF-8"?>
<application-bnd xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance xsi:schemaLocation="http://websphere.ibm.com/xml/ns/javaee http://websphere.ibm.com/xml/ns/javaee/ibm-application-bnd_1_1.xsd"
xmlns="http://websphere.ibm.com/xml/ns/javaee"
version="1.1">
<security-role name="employees">
<group name="users" />
</security-role>
</application-bnd>
|
GlassFish / Payara (mandatory, unless disabled) |
WEB-INF/glassfish-web.xml <?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE glassfish-web-app PUBLIC "-//GlassFish.org//DTD GlassFish Application Server 3.1 Servlet 3.0//EN" "http://glassfish.org/dtds/glassfish-web-app_3_0-1.dtd">
<glassfish-web-app>
<security-role-mapping>
<role-name>employees</role-name>
<group-name>users</group-name>
</security-role-mapping>
</glassfish-web-app>
|
WebLogic (optional) |
WEB-INF/weblogic.xml <?xml version = "1.0" encoding = "UTF-8"?>
<weblogic-web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.bea.com/ns/weblogic/weblogic-web-app.xsd"
xmlns="http://www.bea.com/ns/weblogic/weblogic-web-app">
<security-role-assignment>
<role-name>employees</role-name>
<principal-name>users</principal-name>
</security-role-assignment>
</weblogic-web-app>
|
Besides the simple mapping shown above, most vendors support more advanced mappings and support mapping externally to the application. The following shows an example of this for Liberty:
Liberty: server.xml file sample role mapping
<application type="war" id="myapp" name="myapp"
location="${server.config.dir}/apps/myapp.war">
<application-bnd>
<security-role name="Employee">
<user name="user1" />
<group name="group1" />
</security-role>
<security-role name="Manager">
<user name="user2" />
<group name="group2" />
</security-role>
<security-role name="AllAuthenticated">
<special-subject type="ALL_AUTHENTICATED_USERS" />
</security-role>
</application-bnd>
</application>
So far we specified how we can perform authentication and authorization using the container-provided features and vendor-specific mappings of roles. Now we need to address other considerations to secure your web applications.
Passwords and usernames are not protected from eavesdropping when we use FORM or BASIC authentication methods. To protect them from being viewed and intercepted by third parties we should enforce transport security.
Enforcing Transport Security
Transport security ensures that no one can tamper with the data being sent to a client or data we receive from a client. Java EE specification lets us enforce the transport security in two levels.
CONFIDENTIAL: By using SSL, this level guarantees that our data is encrypted so that it cannot be deciphered by third parties and the data remains confidential.
INTEGRAL: By using SSL, this level guarantees that the data will not be modified in transit by third parties.
NONE: This level does not apply SSL, and lets the data transport happen as usual.
We can enforce transport security in web.xml using the user-data-constraint
element, which we should place inside the security-constraint
tag containing the resource needing transport protection. For example, we can add the following snippet inside security-constraint
:
<user-data-constraint>
<transport-guarantee>CONFIDENTIAL</transport-guarantee>
</user-data-constraint>
We can define as many security-constraints as required and each one of them can use a different user-data-constraint level.
When we specify CONFIDENTIAL or INTEGRAL as transport guarantee level, the application server will use the HTTPS listener (HTTP listener with SSL enabled) to communicate with the client. Different application servers use a variety of methods to define and configure the HTTPS listeners. Each listener will have a dedicated port like 8080 for HTTP and 8181 for HTTPS.
In a production environment we usually front the application server with a Web server or a dedicated hardware appliance to accelerate the SSL access among other tasks like hosting static content, load distribution, decorating HTTP headers, and so on.
For security purposes, the frontend Web server or appliance can be used to accelerate SSL certificate processing, unify the access port to both HTTP and HTTPS, act as a firewall, and so on.
Other Security Elements of Web application Deployment Descriptors
Other elements we can use in web.xml for security purposes are listed here:
Element |
Description |
security-role
|
Each role must be referenced in a security-role tag before it can be used in the auth-constraint element of a security-constraint. For example: <security-role>
<description>All Manager </description>
<role-name>manager</role-name>
</security-role>
|
session-config
|
To specify for how long a session should remain valid. For example: <session-config>
<session-timeout>120</session-timeout>
</session-config>
|
run-as
|
To use an specific internal role for any out going call from the Servlet. <run-as>
<role-name>internal_role</role-name>
</run-as>
This element resides inside the Servlet tag. |
security-role-ref
|
We can alias a role with a more meaningful title and then link the alias to real realm using this element. For example: <security-role-ref>
<role-name>mid_level_managers</role-name>
<role-link>manager</role-link>
</security-role-ref>
|
http-method-omission
|
Specify the http-methods where the specific security constraints should be omitted or not enforced. For example, the following constraint excludes access to all methods except GET and POST at the resources matched by the pattern /company/*: <!-- SECURITY CONSTRAINT #5 -->
<security-constraint>
<display-name>Deny all HTTP methods except GET and POST</display-name>
<web-resource-collection>
<url-pattern>/company/*</url-pattern>
<http-method-omission>GET</http-method-omission>
<http-method-omission>POST</http-method-omission>
</web-resource-collection>
<auth-constraint/>
</security-constraint>
|
deny-uncovered-http-methods
|
Specify the deny-uncovered-http-methods element to deny access to any of the methods not specified (covered) in the security constraints. <deny-uncovered-http-methods/>
|
special role ** |
Specify the ** role to allow any authenticated user to access the resource. When the special role name “**” appears in an authorization constraint, it indicates that any authenticated user, independent of role, is authorized to perform the constrained requests. In the example below, any authenticated user can access the GET method: <security-constraint>
<display-name>SecurityConstraint1</display-name>
<web-resource-collection>
<web-resource-name>WebResourceCollection1</web-resource-name>
<http-method>GET</http-method>
</web-resource-collection>
<auth-constraint id="AuthConstraint_1">
<role-name>**</role-name>
</auth-constraint>
</security-constraint>
|
We use the run-as element or its counterpart annotation to assign a specific role to all outgoing calls from a Servlet or an EJB. We use this element to ensure that an internal role required to access some secured internal EJBs is never assigned to a client and stays fully in control of the developers.
Using Annotations to Enforce Security in Web modules
We can use annotations to enforce security in a Web module. For example, we can specify which roles can access a Servlet by adding some annotations in the Servlet, or we can mark a method in a Servlet stating that no one can access it.
Here is a list of all Java EE 6 annotations and their descriptions:
Annotation |
Description |
@DeclareRoles
|
Prior to referencing any role, it should be defined. @DeclareRoles acts like the security-role element in defining the roles used in application. |
@RunAs
|
Specifies the run-as role for the given Components. |
@ServletSecurity
|
The @ServletSecurity can optionally get @HttpMethodConstraint and @HttpConstraint as its parameters. The @HttpMethodConstraint is an array specifying the HTTP methods specific constraint while @HttpConstraint specifies the protection for all HTTP methods which are not specified in the @HttpMethodConstraint . |
@PermitAll
|
Permits users with any role to access the given method, EJB, or Servlet |
@DenyAll
|
If placed on a method, no one can access that method. In case of class-level annotation, all methods of annotated EJB are inaccessible to all roles unless a method is annotated with a @RolesAllowed annotation. |
@RolesAllowed
|
In case of method-level annotation, it permits the included roles to invoke the method. In case of class-level annotation, all methods of annotated EJB are accessible to included roles unless the method is annotated with a different set of roles using @RolesAllowed annotation.
|
Each of the annotations included here can be placed on different targets like methods, classes, or both, and on different Java EE components like Servlets and EJBs. The next table shows what kind of targets are supported for each one of these annotations.
Annotation |
Targets Level |
Target Kind |
@DeclareRoles
|
Class |
EJB, Servlet |
@RunAs
|
Class |
EJB, Servlet |
@ServletSecurity
|
Class |
Servlet |
@PermitAll
|
Class, Method |
EJB |
@DenyAll
|
Method |
EJB |
@RolesAllowed
|
Class, Method |
EJB |
Some of the security annotations can not target a method like
@DeclareRoles
while others can target both methods and classes like @PermitAll
. Annotation applied on a method will override the class level annotations. For example if we apply
@RolesAllowed("employee")
on an EJB class, and we apply
@RolesAllowed("manager")
on one specific method of that EJB, only the manager role will be able to invoke the marked method while all other methods will be available to the employee role.
A role can be mapped to one or more specific principals, groups, or to both of them. The principal or group names must be valid in the specified security realm. The role name we use in the mapping element must match the role-name in the security-role element of the deployment descriptor [web.xml, ejb-jar.xml] or the role name defined in the
@DeclareRoles annotation.
Programmatic Security in Web Module
We can access some of the container security context programmatically from our Java source code. The next table shows the seven methods of the HTTPServletRequest class, which we can use to extract security-related attributes of the request and decide manually how to process the request.
Method |
Descriptions |
String getRemoteUser()
|
If the user is authenticated, returns the username, otherwise returns null. |
boolean isUserInRole(String role)
|
Returns whether the current user has the specified roles or not. |
Principal getUserPrincipal()
|
Returns a java.security.Principal object containing the name of the current authenticated user. |
String getAuthType()
|
Returns a String containing the authentication method used to protect this application. |
void login(String username, String password)
|
This method authenticates the provided username and password against the security realm which the application is configured to use. We can say this method does anything that the BASIC or FORM authentication does but gives the developer total control over how it is going to happen. |
Void logout()
|
Establish null as the value returned when getUserPrincipal , getRemoteUser , and getAuthType is called on the request. |
String getScheme()
|
Returns the schema portion of the URL, for example HTTP or HTTPS. |
The following snippet shows how we check the user role and decide where to redirect him.
protected void processRequest(HttpServletRequest request,
HttpServletResponse response) throws ServletException,
IOException {
if (request.isUserInRole("manager")){
response.sendRedirect("/mgr/index. jsp");
} else {
response.sendRedirect("/guests/index.jsp");
}
}
The next snippet demonstrates the use of the login method to programmatically log in a user using the container security.
String userName = request.getParameter("user");
String password = request.getParameter("password");
try {
request.login(userName, password);
} catch (ServletException ex) {
//Handling Exception
return;
}
In the sample code, which can happen inside the doGet
or doPost
of a Servlet, we are extracting the username and password from the request and then we use the login
method to ask the container to authenticate the given username and password against the configured realm.
{{ parent.title || parent.header.title}}
{{ parent.tldr }}
{{ parent.linkDescription }}
{{ parent.urlSource.name }}